Repository navigation
Commit 3c8b2b0
Gate replica cursor routing behind a proven freshness fence
Cursor pages routed to the read replica were only safe if every row the
page could contain had already replayed there. Commit order is not
created_at order: a transaction carrying an older client-signed
created_at can commit late, and a replica page paginating past that gap
skips the row permanently (the keyset cursor never revisits it).
Close that hole with a two-part proof:
1. Commit-time floor (migration 0021): a DEFERRABLE INITIALLY DEFERRED
constraint trigger on events re-checks, inside COMMIT processing and
against clock_timestamp() (not the transaction-frozen now()), that
channel-bearing rows are no older than buzz.created_at_floor seconds.
The relay's writer pool arms the GUC on every connection
(after_connect), turning the ingest-time +/-900s envelope into a
storage invariant that every insert path inherits — including the
writers that bypass ingest_event entirely (workflow sink, side
effects, moderation notices, audio, git transport, replace_*).
channel_id-NULL rows (push leases, discovery snapshots) are
structurally exempt; they never appear in keyset windows. Startup
fails closed if any events partition lacks the trigger.
2. Ordered LSN handshake (replica_fence): on one pinned writer
connection, three separately-awaited statements sample
S = clock_timestamp(), then scan pg_stat_activity (plus
pg_prepared_xacts) for the oldest open transaction, then capture
L = pg_current_wal_lsn() last. Once the replica reports
pg_last_wal_replay_lsn() >= L, every transaction falls into exactly
one of three buckets — replayed, bounded by oldest_xact_start, or
floor-guarded after S — so the fence advances to
min(oldest_xact_start, S) - floor - margin with no timeout
assumptions at all.
Everything fails closed: probe errors, masked pg_stat_activity rows
(an unprivileged view masks backend_type itself — detected before any
backend-type filter), NULL replay LSN, a not-in-recovery 'replica',
non-advancing replay, and probe staleness all close the fence and route
every cursor page back to the writer.
Routing now consults the fence: channel windows serve a cursor from the
replica only when the fence covers the cursor timestamp; thread pages
only when the page is full AND its tail is at or below the fence
(under-limit terminal pages still re-verify on the writer).
Adversarial fixtures pin both inversions (DESC middle hole, ASC
full-page skip), the held-transaction commit abort, the armed-pool
end-to-end rejection with SHOW verification, the masked-activity
fail-closed path, and the NULL-replay-LSN breaker.
Co-authored-by: Tyler Longwell <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>1 parent 50ae82a commit 3c8b2b0
6 files changed
Lines changed: 1108 additions & 27 deletions
File tree
- crates
- buzz-db/src
- buzz-relay/src
- migrations
- schema
0 commit comments