https://commitfest.postgresql.org/patch/5151/
This is AI-build report below
Is pgEdge/spock affected by CF #5151 (DirtySnapshot index scan skips concurrently updated tuples)?
Yes — in three places. Two of them are fixed for free once the core patch lands; one is a
private copy inside spock that the core fix cannot reach.
Analyzed: pgEdge/spock master @ 7f42238, SPOCK_VERSION 6.0.0.
1. Direct inheritance (fixed by the core patch)
FindReplTupleInLocalRel() (src/spock_apply_heap.c:394) calls exactly the two functions the
patch rewrites to use GetLatestSnapshot():
src/spock_apply_heap.c:417 RelationFindReplTupleByIndex(localrel, localidxoid, ...)
src/spock_apply_heap.c:431 RelationFindReplTupleSeq(localrel, LockTupleExclusive, ...)
It is reached from all three apply paths: INSERT (:878), UPDATE (:984), DELETE (:1101).
No spock change needed — but only on minor releases that carry the core fix.
2. Symptoms differ from vanilla logical replication, and are partly worse
The "tuple not found" branch behaves differently per operation:
- UPDATE —
elog(ERROR, "logical replication did not find row to be updated ...")
(src/spock_apply_heap.c:1038) plus SPOCK_CT_UPDATE_MISSING in the exception log.
Not a silently lost update: a sporadic, unexplainable apply failure on a hot row
(worker behaviour then depends on spock.exception_behaviour).
- DELETE —
spock_report_conflict(SPOCK_CT_DELETE_MISSING, ...) (src/spock_apply_heap.c:1210);
the delete is silently dropped. Exactly the "lost delete" + wrong-conflict-type case
from the thread.
- INSERT — the existing row is not found, so
ExecSimpleRelationInsert() runs and raises a
duplicate key violation instead of resolving an insert_exists conflict. For a
multi-master deployment this breaks the very feature spock is deployed for.
3. Private copy the core patch will NOT fix
SpockRelationFindReplTupleByIndex() — src/spock_common.c:333. The only InitDirtySnapshot()
in the whole tree (src/spock_common.c:394), followed by the same
index_beginscan / index_getnext_slot / XactLockTableWait + goto retry pattern as the
pre-patch core function.
Caller: FindReplTupleByUCIndex() (src/spock_apply_heap.c:471) — INSERT conflict lookup across
all unique indexes other than PK/RI, gated by spock.check_all_uc_indexes (off by default).
The same fix applies. The thread's "no performance regression" argument holds literally here:
PushActiveSnapshot(GetLatestSnapshot()) already sits a few lines below, before
table_tuple_lock (src/spock_common.c:442).
4. Existing mitigation and its limits
UPDATE and DELETE wrap the lookup in a retry loop, spock.read_retry_count, default 5
(src/spock.c:179). Each retry re-scans with a fresh dirty snapshot, so once the concurrent
writer has committed the row is found — the window is materially narrower than in vanilla.
It is not a fix:
- the INSERT path has no retry at all;
wait_for_previous_transaction() (src/spock_apply.c:294) waits for the previous transaction
of the same origin by commit timestamp, not for the local concurrent writer — so these are
effectively five back-to-back re-scans with no delay, and a continuously updated row can lose
all five to the same race;
- the GUC minimum is 0.
5. Not affected
The companion thread's check_exclusion_or_unique_constraint path does not apply: spock never
calls ExecCheckIndexConstraints and does not use speculative insertion. Its INSERTs go through
ExecInsertIndexTuples / _bt_check_unique, which is not a dirty-snapshot scan.
https://commitfest.postgresql.org/patch/5151/
This is AI-build report below
Is pgEdge/spock affected by CF #5151 (DirtySnapshot index scan skips concurrently updated tuples)?
Yes — in three places. Two of them are fixed for free once the core patch lands; one is a
private copy inside spock that the core fix cannot reach.
Analyzed: pgEdge/spock master @ 7f42238, SPOCK_VERSION 6.0.0.
1. Direct inheritance (fixed by the core patch)
FindReplTupleInLocalRel()(src/spock_apply_heap.c:394) calls exactly the two functions thepatch rewrites to use
GetLatestSnapshot():It is reached from all three apply paths: INSERT (:878), UPDATE (:984), DELETE (:1101).
No spock change needed — but only on minor releases that carry the core fix.
2. Symptoms differ from vanilla logical replication, and are partly worse
The "tuple not found" branch behaves differently per operation:
elog(ERROR, "logical replication did not find row to be updated ...")(src/spock_apply_heap.c:1038) plus
SPOCK_CT_UPDATE_MISSINGin the exception log.Not a silently lost update: a sporadic, unexplainable apply failure on a hot row
(worker behaviour then depends on
spock.exception_behaviour).spock_report_conflict(SPOCK_CT_DELETE_MISSING, ...)(src/spock_apply_heap.c:1210);the delete is silently dropped. Exactly the "lost delete" + wrong-conflict-type case
from the thread.
ExecSimpleRelationInsert()runs and raises aduplicate key violation instead of resolving an
insert_existsconflict. For amulti-master deployment this breaks the very feature spock is deployed for.
3. Private copy the core patch will NOT fix
SpockRelationFindReplTupleByIndex()— src/spock_common.c:333. The onlyInitDirtySnapshot()in the whole tree (src/spock_common.c:394), followed by the same
index_beginscan/index_getnext_slot/XactLockTableWait+goto retrypattern as thepre-patch core function.
Caller:
FindReplTupleByUCIndex()(src/spock_apply_heap.c:471) — INSERT conflict lookup acrossall unique indexes other than PK/RI, gated by
spock.check_all_uc_indexes(off by default).The same fix applies. The thread's "no performance regression" argument holds literally here:
PushActiveSnapshot(GetLatestSnapshot())already sits a few lines below, beforetable_tuple_lock(src/spock_common.c:442).4. Existing mitigation and its limits
UPDATE and DELETE wrap the lookup in a retry loop,
spock.read_retry_count, default 5(src/spock.c:179). Each retry re-scans with a fresh dirty snapshot, so once the concurrent
writer has committed the row is found — the window is materially narrower than in vanilla.
It is not a fix:
wait_for_previous_transaction()(src/spock_apply.c:294) waits for the previous transactionof the same origin by commit timestamp, not for the local concurrent writer — so these are
effectively five back-to-back re-scans with no delay, and a continuously updated row can lose
all five to the same race;
5. Not affected
The companion thread's
check_exclusion_or_unique_constraintpath does not apply: spock nevercalls
ExecCheckIndexConstraintsand does not use speculative insertion. Its INSERTs go throughExecInsertIndexTuples/_bt_check_unique, which is not a dirty-snapshot scan.