What is the Rails 6 Way to prevent multi-model logic from starting if it's already running for a user?

275 Views Asked by At

I have some complex business logic that runs when a controller action is hit. The logic performs various updates to many different tables/records associated with a user. However, the model method called by the controller is prone to running multiple times simultaneously if the route is hit fast enough in succession.

I want to make sure that the model method doesn't start if it's already in-process due to a previous request.

I've done some reading, and it sounds like database locking might be what I want, but it's not clear to me what kind of locking I want, or if I really want it for my scenario. I'm inexperienced in problems of this sort.

My logic doesn't just update one record, it updates a user record and also many different records associated with that user, across many tables. It doesn't seem correct to me to add a lock_version to every single table, but maybe that is normal? I think what I want is for the user and the user's relevant associated records to be frozen while this method is running, so I'm looking for the best way to achieve that.

What's the Rails Way to handle this with (hopefully) as little added complexity as possible? If locking is what I want, do I need to add a lock_version column to every single model that is updated as a result of this logic?

1

There are 1 best solutions below

1
On

Create a mutex that is uniquely constrained by the user you are processing, either with Ruby or with a consistent database if you are building a distributed application.

Before an action begins processing, have it attempt to acquire a mutex. If it fails because another process already has it, halt.

Ensure that any process that has acquired a mutex releases it once it has stopped processing.

lock_version is important but it is merely a database lock. It ensures that an object cannot be saved if it has already been updated by another process.

If you are making atomic commits, lock_version will work, but it is better to check for concurrent processing before you get to the commit-stage. A "process-level" lock such as this is something that you yourself have to implement.