Repository navigation
fix: drop row locks from bulk criterion updates - #914
jesperhodge wants to merge 1 commit into
Conversation
bulk_update_competency_criteria() locked the group lineage and the criteria it edits. The lineage includes the competency's root group, shared by every course that uses the competency, so one bulk update serialized every other bulk update for that competency and, through InnoDB's shared lock on foreign-key parents, stalled criterion inserts under it in any course. The locks guarded against a concurrent archive, and no code path archives groups or criteria. Without them, each extra identical concurrent request writes one redundant history row. The saves run in primary-key order, so two overlapping requests cannot deadlock on each other's criterion rows. The tag and course-run locks in associate_competency_criterion() stay: they protect one-root-per-tag, duplicate criteria, and the per-course dominance check, none of which a database constraint can express. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for the pull request, @jesperhodge! This repository is currently maintained by Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review. 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. 🔘 Update the status of your PRYour PR is currently marked as a draft. After completing the steps above, update its status by clicking "Ready for Review", or removing "WIP" from the title, as appropriate. Where can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources: When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
Description
bulk_update_competency_criteria()took twoselect_for_update()locks: one on the leaf group's lineage (leaf, course-level group, and the competency's root group) and one on the criteria it edits. This PR replaces both with plain reads and keeps everything else in the function, including itstransaction.atomic()block and both archived checks.The locks were added in #874 to keep a concurrent archive from slipping in after the archived check, and to make a concurrent identical request wait and find nothing to change. On
mainthere is no code path that archives a group or a criterion, so the first purpose has no writer to defend against; when an archive path lands it will need its own locks, in a compatible order, for a lock here to protect anything. The second purpose buys one fewer redundantsimple_historyrow in a rare double submit.What the locks cost is larger than that. The lineage includes the competency's root group, a row shared by every course that uses the competency, so one bulk update serialized every other bulk update for that competency across the whole instance. InnoDB also takes a shared lock on the parent row for every foreign-key check, so while a bulk update held exclusive locks on the root and course-level group rows, every leaf-group or criterion insert under that competency, in any course, waited for it to commit. The two lock chains (tag then course run in
associate_competency_criterion(), group rows then criterion rows here) do not cycle today, but the second chain is a deadlock surface for any future path that locks a group and then needs the tag or course-run lock.Each
save()still takes an exclusive row lock on its criterion, so the function now saves in primary-key order and returns in request order. Two overlapping requests therefore always wait on each other in the same order instead of each holding a row the other needs. The remaining cost of removal: each extra identical concurrent request writes one redundantsimple_historyrow, and a cascading delete of a tag, group, or course run that commits between the read and asave()fails that one request with aDatabaseErrorand rolls it back. No delete path exists in this repo, so that race needs an administrative cascade.The two locks in
associate_competency_criterion()stay. They protect one root group per tag, no duplicate criterion for a derived leaf, and the per-course dominance check. None of those invariants can be expressed as a database constraint on MySQL, and both were designed in review on #847 and #875. The #847 request for a follow-up that replaces the Tag lock with a derived-column constraint is unchanged by this PR.Testing
No new tests. The change has no behavior visible on SQLite, where Django drops
FOR UPDATEsilently, and a MySQL-only test asserting that noFOR UPDATEis issued would pin the implementation, which review on #847 asked to avoid. The existingbulk_update_competency_criteriatests cover the archived checks and the "not criteria of this group" path, and the full CBE API test module passes.pylint,pycodestyle,pydocstyle,isort --check-only, andmypyexit 0 on the touched file.Related
Closes http://github.com/openedx/openedx-core/issues/916
🤖 Generated with Claude Code