You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
PostgreSQL: concurrent transactions can take the same db_version, and a download then skips one of them #79
Follow-up to #69 (fixed in #78), which made every transaction of one backend take its own db_version. Two backends writing at the same time can still end up with the same one.
Cause
Each backend picks its next db_version from its own cache, reloaded as the MAX(db_version) of the meta tables. Another backend's uncommitted rows are not visible to that query, so two transactions in flight both compute the same next value. #78 also makes seq restart at 0 in every transaction, so both can store the same (db_version, seq).
Reproduction
On main at 0c20064, one table, a second session through dblink:
INSERT INTO t VALUES ('base','x'); -- base@1/0SELECT dblink_exec('a', 'BEGIN');
SELECT dblink_exec('a', 'INSERT INTO t VALUES (''a1'',''x'')'); -- in flightINSERT INTO t VALUES ('b1','x'); -- commits first-- served now: base@1/0 b1@2/0 -> since = 2SELECT dblink_exec('a', 'COMMIT');
-- after a1 commits: base@1/0 a1@2/0 b1@2/0-- db_version > 2: (empty)
a1 and b1 are separate transactions with the same (db_version, seq), 2/0.
A download taken after b1 committed ends at db_version 2. The next one asks for db_version > 2, so a1 is never delivered.
The same skip happens without a shared db_version whenever versions commit out of order. If A takes N and B takes N+1, and B commits and is served before A commits, A's changes land below a window that has already moved on.
Impact
Any server applying payloads or taking local writes on more than one connection at once, which includes a connection pool and concurrent apply jobs. Changes are lost for the peers downloading them: they are stored locally but never served. This predates #69; before #78, the ever-growing seq only hid the duplicate pairs, not the skip.
Directions
Serialize allocation: take a transaction-scoped lock when a transaction first takes a db_version, held until commit. Writers then commit in version order, at the cost of serializing all writing transactions of the database.
Serve only a safe horizon: keep allocation concurrent, but have the download windows (cloudsync_payload_chunks, the db_version > since readers) stop below the lowest db_version still held by an in-flight transaction.
Either needs a test with two sessions, like the reproduction above, and a check of what the chunk tiling and the positional resume assume about commit order.
Activity