-
Notifications
You must be signed in to change notification settings - Fork 41
Fix autonumbering locking with SQL named locks and Django row locking #7455
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
base: main
Are you sure you want to change the base?
Conversation
|
Hey @melton-jason, can you test this PR out before I open it up to other testers. |
|
I created some other solutions that try to avoid race conditions when doing autonumbering, but none are fully satisfactory:
|
Triggered by e604dec on branch refs/heads/issue-6490-1
|
TODO: add indexes to all the common used autonumbering fields. |
melton-jason
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for taking so long on this.
I haven't yet finished reviewing the autonumbering/locking changes, but here are my comments so far regarding the migrations!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(just a note/consideration)
Very nice, glad we're considering some more indices here! 🏆
I would love to see other indices (like those proposed in Add missing field indexes (#7482)) if the benefit of adding them would outweight the cost, although that could definitely be a future PR.
I will warn that indexes can result in a performance loss for write, insert, and delete operations, as each index on a table needs to be maintained (i.e., the B-Tree or similar data structure updated).
In the case of Specify for example, this could slow down (non-negligibly or perhaps even significantly, depending on the index data structure and data being inserted/updated) WorkBench and BatchEdit operations along with common DataEntry operations.
If this is a fine trade-off (such as the case were there will be overall significantly more reads than write and the index is well-structured for the intended optimization), I say shoot for the stars 🚀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya, sounds good. I'm looking through the issue, and creating a separate PR for create those specific indexes. The difference for this PR, is that the indexes are always on two values (scoping_field, autonumbering_field). So far we added these two indexes:
operations = [
migrations.AddIndex(
model_name='accession',
index=models.Index(fields=['division_id', 'accessionnumber'], name='AccScopeAccessionsnumberIDX'),
),
migrations.AddIndex(
model_name='collectionobject',
index=models.Index(fields=['collectionmemberid', 'catalognumber'], name='ColObjScopeCatalognumberIDX'),
),
]For this PR, I'm going through all the proposed indexes and picking out the ones that are common for autonumbering. Let me know if there are any specific ones you want to include?
Fixes #6490
Look at Django docs, it seems that explicitly locking mysql tables from within a Django atomic transaction can lead to issues of tables not getting unlocked. The
post_resourcefunction has thetransaction.atomicdecorator, which calls create_obj -> autonumber_and_save -> do_autonumbering -> lock_tables. This needs to be avoid in order to evade the issue.LOCK TABLES implicitly commits any active transaction, and while tables are locked the connection is restricted to those tables only. When Django later tries to manage the savepoint/transaction stack, the connection state can no longer matches expectations. This can leave the connection stuck.
This solution tries using
GET_LOCKandRELEASE_LOCK, acting like a mutex. Using these instead oflock tablesshould avoid problems of query transactions getting stuck. These sql named locks are scoped so that only one autonumbering operation can only be performed on a table at a time. This will avoid race-conditions between autonumbering operations.In addition to the sql named lock, we'll use Django's
select_for_updatefunction to perform row-level locking during the autonumbering operation. This implementation will replace the need to create explicit table locks. While the sql named lock will prevent race-conditions between autonumbering process, this row-level locking will ensure that other processes will not create/edit records that the autonumbering operation depends on while running. Indexes are added to the common autonumbering fields in order to get the row-level locking to work with Django.Checklist
self-explanatory (or properly documented)
Testing instructions