Skip to content

Use UnorderedOrdering for PatientFolder to avoid ZODB conflicts#135

Merged
ramonski merged 2 commits into
masterfrom
unordered-patients-content
May 20, 2026
Merged

Use UnorderedOrdering for PatientFolder to avoid ZODB conflicts#135
ramonski merged 2 commits into
masterfrom
unordered-patients-content

Conversation

@xispa
Copy link
Copy Markdown
Member

@xispa xispa commented May 11, 2026

Description

Switch the IOrdering adapter for PatientFolder from plone.folder.default.DefaultOrdering to plone.folder.unordered.UnorderedOrdering.

PatientFolder is folderish. With the default adapter, Plone keeps a plone.folder.ordered.order annotation — a flat persistent.list.PersistentList of every child id, mutated on every _setObject via notifyAdded(obj_id) (order.append(...)). PersistentList does not implement _p_resolveConflict(), so two concurrent registrations on the same folder always conflict on this single annotation OID, and the conflict propagates all the way to the publisher's 3-retry loop. The cost of each retry grows linearly with the size of the list, because PersistentList rewrites the whole pickle on every mutation.

For clinical-lab installations the PatientFolder accumulates one Patient per registered sample, so its child count grows on the same trajectory as the sample volume — into the tens or hundreds of thousands. Concurrent sample registration is exactly the workload that maximises contention on this single annotation: every new Patient created during sample-add hits the same order.append.

With UnorderedOrdering the adapter's notifyAdded/notifyRemoved become no-ops; idsInOrder() falls back to aq_base(folder).objectIds(ordered=False), which reads the BTreeFolder2 internal tree (scales, has built-in conflict resolution).

The change is interface-scoped to IPatientFolder only. Patient content (which is itself folderish) and any other folderish type provided by senaite.patient are unaffected — they continue to use DefaultOrdering.

Patient listings never depend on the parent folder's manual ordering — they sort catalog brains. An audit of the package found only upgrade-step iterations over portal.patients.objectValues(), none of which assume insertion order.

Migration

Existing PatientFolder instances still carry the plone.folder.ordered.order PersistentList and plone.folder.ordered.pos OIBTree annotations from prior writes. They are no longer maintained but remain on disk. The new upgrade step drop_patientfolder_ordering_annotations (profile version 1602 → 1603) pops both annotations from portal.patients.

The adapter override is what stops new writes from touching the annotations; the upgrade step is hygiene to free their storage.

Related

This follows the same pattern proposed upstream in senaite/senaite.core (PR for IClientUnorderedOrdering): Use UnorderedOrdering for Clients to avoid ZODB conflicts #2897. Both folders share the same growth profile under high concurrent sample-registration load.

--
I confirm I have tested this PR thoroughly and coded it according to PEP8
and Plone's Python styleguide standards.

@xispa xispa marked this pull request as draft May 11, 2026 12:51
@ramonski ramonski marked this pull request as ready for review May 20, 2026 21:21
Copy link
Copy Markdown
Contributor

@ramonski ramonski left a comment

Choose a reason for hiding this comment

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

Thanks!

@ramonski ramonski merged commit 3e2d9c2 into master May 20, 2026
2 checks passed
@ramonski ramonski deleted the unordered-patients-content branch May 20, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants