Repository navigation
Conversation
_add_refs_for_oids queued the object_refs_added INSERT before the DELETEs of the object's old rows. The batcher runs deletes before inserts only within a flush, and insert_into can flush immediately, so the insert could reach the database while the old row was still there: duplicate key value violates unique constraint "object_refs_added_pkey". Any object changed since the previous pack can hit it (the table is kept between packs). Fixes zodb#482. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #482 (and its duplicates #448, #493): history-free
zodbpackfailing in pre-pack withCause
HistoryFreePackUndo._add_refs_for_oidsqueues, per object, theobject_refs_addedINSERT first and then the DELETEs of that object's oldobject_refs_added/object_refrows.RowBatcher.flush()runs deletes before inserts, but only within one flush, andinsert_into()calls_flush_if_needed()straight away. When that insert is the row that reachesrow_limit(or the size or bind limit), the batch is flushed with the insert but without the delete, which hasn't been queued yet. The insert then hits the object's existing row.An object has an existing row whenever it changed since its references were last read, because
object_refs_addedis kept between packs. That explains the pattern reported on #482: truncating the table makes packs work again, then the failure returns a few days (or hours) later, at whichever object happens to land on a batch boundary.The history-preserving
_add_refs_for_tidalready queues its delete first.Fix
Queue the two deletes before the
object_refs_addedinsert, so a flush triggered by the insert always carries the deletes for that object.Tests
src/relstorage/adapters/tests/test_packundo.pydrives_add_refs_for_oidsthrough a realRowBatcherwithrow_limit=1(every queued row flushes) overMockCursor, and asserts each table's DELETE executes before its INSERT. Before the fix:After the fix, both tests pass, along with
relstorage.adapters.tests.test_batchandrelstorage.tests.test_zodbpack(38 tests) and the SQLite pack tests (--package relstorage.adapters.sqlite -t Pack: 80 tests, 0 failures, 14 skipped). I ran them against the 4.3.0 wheel with the patchedpackundo.py; master'spackundo.pyis identical to 4.3.0.Found on a production history-free PostgreSQL database (RelStorage 4.1.1, ~855k objects), whose
object_refs_addedstill held 2.67M rows from an earlier interrupted pack.