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
This PR replaces the old per-transaction conflict_ranges exclusion-constraint approach (btree_gist, GIST-indexed ranges, Postgres advisory locking) with a centralized leader-resolver pattern: one elected node drains a udb_commit_requests queue, resolves conflicts in-memory via the shared TransactionConflictTracker, applies writes in batched Postgres transactions, and replies to followers via NOTIFY. The architecture is much cleaner and avoids the distributed deadlock / GiST contention issues of the old approach.
Four confirmed findings below, ranked by severity.
1. Fresh-database cold-window spuriously rejects all writes for 5 seconds
recovery_floor reads last_value from udb_version_seq to determine the highest version a prior leader could have committed. On a brand-new database where no nextval has ever been called, PostgreSQL initialises last_value = 1 (the sequence start value) with is_called = false. recovery_floor therefore returns max(durable=0, seq_high=1) = 1, setting recovery_version = 1.
During the 5-second cold window (TXN_TIMEOUT), any commit with start_version < recovery_version is rejected. Every first-boot follower opens a snapshot with read_version = durable_version = 0, so start_version = 0 < 1 = recovery_version → all writes fail as conflicts and retry. All write transactions stall for the full 5-second cold window on a fresh install.
Fix: Account for is_called:
SELECT CASE WHEN is_called THEN last_value ELSE 0 END FROM udb_version_seq
2. LISTEN race during reconnect silently drops notifications for new subscribers
Set *client.lock() = Some(new_client) ← client set last
If a concurrent listen("new_channel") call arrives between steps 2 and 4, it finds client = None and skips the LISTEN. The channel IS inserted into scc::HashMap, so it won't be picked up by the re-LISTEN loop on the current reconnect — that loop already ran. No LISTEN is ever issued for this channel until the next connection drop.
The inline comment says "the lifecycle task re-LISTENs on reconnect" — but for channels added in this window, that reconnect already happened.
Fix: Set *client = Some(new_client)before the re-LISTEN loop. New listen() calls that arrive after that point will immediately issue their own LISTEN on the active client.
3. wait_for_leader polls with sleep instead of using the existing watch::Receiver
This is the loop { check; sleep } pattern that CLAUDE.md prohibits: "If it is inside a loop { check; sleep } body, it is polling and should be event-driven instead."PostgresShared already has a watch::Sender<Option<LeaseInfo>> / watch::Receiver pair (lease_tx / lease_rx) that fires whenever set_lease is called — the infrastructure for event-driven notification is in place but not exposed to the call site.
Fix: Add a method to PostgresShared to subscribe to the lease watch channel, then rewrite as:
"DELETE FROM udb_commit_requests WHERE created_at < now() - ($1::bigint * interval '1 second')"
The constant is documented as: "Terminal and orphaned commit-request rows older than this are garbage collected." But the SQL has no status filter — it deletes every row, including pending ones.
A pending row stuck for >60 seconds (e.g., pool exhaustion, leader stall) is silently deleted. The waiting follower in await_result receives Ok(None) and returns NotCommitted, causing a retry — which is safe today — but this diverges from the documented invariant and would silently break any future assertion or metric that expects live pending rows to always exist.
Fix: Either add AND status != 'pending' to the DELETE, or update the constant's doc comment to reflect the actual behaviour.
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
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.
No description provided.