Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new adapter to work the job with multiple database #103

Merged
merged 1 commit into from
Aug 14, 2024

Conversation

ankithads
Copy link
Contributor

@ankithads ankithads commented Jun 13, 2024

What?
Added a new adapter which uses 2 databases to work the job.
database 1 will have the job enqueued and will fetch the jobs to be worked
database 2 will be used to acquire the advisory lock on the job.

This adapter can be used with a database that does not support advisory locking. In the above scenario, database 1 does not support advisory locking.

@ankithads ankithads force-pushed the add-lock-databse branch 8 times, most recently from d92a84a to 59aab33 Compare June 18, 2024 15:25
@ankithads ankithads force-pushed the add-lock-databse branch 3 times, most recently from 5c6353d to f29a8b8 Compare June 25, 2024 09:52
Gemfile Outdated Show resolved Hide resolved
@ankithads ankithads force-pushed the add-lock-databse branch 3 times, most recently from e1f6f21 to 31bf0fe Compare June 25, 2024 15:52
Copy link
Contributor

@ameykusurkar ameykusurkar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done a first pass of the implementation, looking sensible. Suggested some improvements

lib/que/adapters/yugabyte.rb Outdated Show resolved Hide resolved
lib/que/adapters/yugabyte.rb Outdated Show resolved Hide resolved
lib/que/adapters/yugabyte.rb Outdated Show resolved Hide resolved
lib/que/adapters/yugabyte.rb Outdated Show resolved Hide resolved
lib/que.rb Outdated Show resolved Hide resolved
lib/que/adapters/yugabyte.rb Outdated Show resolved Hide resolved
lib/que/adapters/yugabyte.rb Outdated Show resolved Hide resolved
lib/que/adapters/yugabyte.rb Outdated Show resolved Hide resolved

if locked?(result.first['job_id'])
return result
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An idea I had. We can do something like this to make this a bit more efficient, basically we use the row itself as a temporary lock to prevent a lot of race conditions. This way, a lot of the work is done within the main database itself.

Let me know what you think.

loop do
  job_connection.transaction do
    # This locks the row only while we are checking the advisory lock database.
    # This saves duplicate attempts by different works to acquire the lock.
    result = Que.execute("SELECT ... FOR UPDATE SKIPPED LOCK")

    return result if result.empty?

    if locked?(result.first['job_id'])
      return result
    end
    # lock is released after the advisory lock check as the transaction completes
  end
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will have cross database transaction in this case. If that's not an issue then this would be good

Copy link
Contributor

@ameykusurkar ameykusurkar Jun 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will have cross database transaction in this case

Cross database transactions are mainly a problem when you're trying to maintain consistency of writes between two databases (eg. if one commits but the other rolls back).

I don't think this is a problem here, so should be safe to ignore: technically acquiring the a lock is a write, but the SELECT FOR UPDATE is temporary and not the source of truth, and the lock is released at the end of the transaction anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tried this, but for some reason, all the specs are getting hanged. It would require more time to debug what's going on. Will address this separate PR

lib/que/adapters/yugabyte.rb Outdated Show resolved Hide resolved
@ankithads ankithads force-pushed the add-lock-databse branch 5 times, most recently from d872b0e to 6b3bac9 Compare July 2, 2024 14:48
lib/que.rb Outdated Show resolved Hide resolved
lib/que/adapters/active_record_with_lock.rb Outdated Show resolved Hide resolved
lib/que/adapters/active_record_with_lock.rb Outdated Show resolved Hide resolved
spec/spec_helper.rb Outdated Show resolved Hide resolved
spec/spec_helper.rb Outdated Show resolved Hide resolved
@ankithads ankithads force-pushed the add-lock-databse branch 2 times, most recently from 30ee504 to adbbcb6 Compare July 4, 2024 11:17
@ankithads ankithads marked this pull request as ready for review July 4, 2024 11:18
@ankithads ankithads changed the title add lock databse Add a new adapter to work the job with multiple database Jul 4, 2024
Copy link
Contributor

@ameykusurkar ameykusurkar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great! Just some minor stylistic stuff and I think it's good to go

lib/que/adapters/active_record_with_lock.rb Show resolved Hide resolved
lib/que/adapters/active_record_with_lock.rb Outdated Show resolved Hide resolved
lib/que/adapters/active_record_with_lock.rb Outdated Show resolved Hide resolved
@@ -60,7 +60,6 @@ def initialize(queue:, cursor_expiry:, window: nil, budget: nil, secondary_queue
@queue_expires_at = {}
@secondary_queues = secondary_queues
@consolidated_queues = Array.wrap(queue).concat(secondary_queues)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing a bunch of diffs that are just changing whitespace/formatting. Should we just get rubocop to standardise all of this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: let's keep this empty line? Keeps a separation from the comment

lib/que/sql.rb Show resolved Hide resolved
spec/lib/que/locker_spec.rb Outdated Show resolved Hide resolved
spec/spec_helper.rb Show resolved Hide resolved
lib/que/adapters/active_record_with_lock.rb Outdated Show resolved Hide resolved
lib/que/adapters/active_record_with_lock.rb Outdated Show resolved Hide resolved
lib/que/adapters/active_record_with_lock.rb Outdated Show resolved Hide resolved
@ankithads ankithads force-pushed the add-lock-databse branch 5 times, most recently from e51cd42 to b277323 Compare August 7, 2024 16:26
@@ -60,7 +60,6 @@ def initialize(queue:, cursor_expiry:, window: nil, budget: nil, secondary_queue
@queue_expires_at = {}
@secondary_queues = secondary_queues
@consolidated_queues = Array.wrap(queue).concat(secondary_queues)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: let's keep this empty line? Keeps a separation from the comment

@@ -96,3 +114,5 @@ jobs:
- name: Run specs
run: |
bundle exec rspec
- name: Run Specs With ActiveRecordWithLock Adapter
run: ADAPTER="ActiveRecordWithLock" bundle exec rspec
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this a different job? That way we can put the ADAPTER in its own env, and it's useful to see that the specs passed for one adapter and not for the other, rather than failing the whole job

@ameykusurkar
Copy link
Contributor

Let's also squash commits before merging, there's quite a few!

@ankithads ankithads force-pushed the add-lock-databse branch 5 times, most recently from fb78ba2 to 31aed1a Compare August 8, 2024 13:09
@ZimbiX
Copy link

ZimbiX commented Aug 8, 2024

Can I ask why you're working on this? Do you have evidence that the advisory locking is significantly slowing down your database? Given advisory locking is in-memory, I'd've thought it'd be pretty inconsequential in terms of load.

@ankithads
Copy link
Contributor Author

Can I ask why you're working on this? Do you have evidence that the advisory locking is significantly slowing down your database? Given advisory locking is in-memory, I'd've thought it'd be pretty inconsequential in terms of load.

This change here is to support Que functionality on a database that does not support advisory locking. This PR adds a new Que adapter where the jobs will be processed on 1 database and an advisory lock will be taken on the small database instance that supports the advisory locking

…t does not support advisory locking

What?
This is an attempt to use que with the database that does not support advisory locking.
It will use 2 databases. The primary one will be processing the jobs and uses a 2nd database to acquire the
advisory lock on the job.
@ankithads ankithads merged commit 851b629 into master Aug 14, 2024
14 checks passed
@ankithads ankithads deleted the add-lock-databse branch August 14, 2024 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants