feat(control): thread transaction id through cross-node dispatch (F1)#175
feat(control): thread transaction id through cross-node dispatch (F1)#175emanzx wants to merge 2 commits into
Conversation
An explicit transaction's staged writes live in per-core overlays keyed by the session's transaction id. Before this change the id was threaded only on local/single-shard paths: the gateway's cross-node hop dropped it, so when a coordinator was not the leader for a collection's shard, in-transaction reads could not see the transaction's own staged rows and a forwarded StageWrite was stranded (rejected: "StageWrite dispatched without a txn_id"). Changes: - ExecuteRequest carries an Option<u64> txn_id; QueryContext, DispatchRouteStreamParams, and RemoteDispatchArgs carry Option<TxnId>. Autocommit call sites pass None; every transactional path threads the session id through the gateway, remote executor, and all-cores fan. - gather_all_vshards stops dropping txn_id at its QueryContext boundary — the cross-node in-transaction read now reaches the owning leader keyed. - dispatch_local routes in-transaction plans via a new dispatch_to_data_plane_in_txn so the id reaches the Data Plane overlay. - The pgwire whole-batch gateway-forward fast path is now skipped inside a transaction block: it is an autocommit optimization, and an in-transaction write forwarded through it reached the leader as a bare Raft-committed write, bypassing the staging overlay (broke read-your-own-writes and left ROLLBACK unable to undo it). In-block tasks fall through to the staging gate, which stages writes and forwards reads/stage-ops to the leader. - Transaction ids are made globally unique (node id in the high 16 bits) so two coordinators' overlays never collide on a shard that hosts both; the overlay-drop meta-op now carries the id so ROLLBACK's DropTxnOverlay routes to the owning leader, not a local replica. Cross-node RYOW is exercised by a new 3-node e2e that runs BEGIN / staged INSERT / point + scan RYOW / ROLLBACK / post-rollback-empty from every node, so at least two iterations drive the non-leader forward path. Single-node overlay regressions, cluster calvin e2e, and codec roundtrips stay green; clippy clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
F1-only scope is fine — F10 and U7 are correctly out. Two blocking defects inside F1's own diff, plus a test gap. Please continue on this branch. Blocker 1 — cross-node COMMIT applies staged writes on the wrong nodeRemoving the pgwire whole-batch fast path (
Net: This is not the "still rejects cross-shard COMMIT" carve-out — that guards write sets spanning ≥2 vShards. This is a single-shard txn whose one shard happens to be led elsewhere. It classifies as Not a regression on the native transport: Fix: route the Blocker 2 —
|
…lock churn Review follow-ups on the F1 branch: - Blocker 2: TxnId packs node_id into the high 16 bits, but node_id is a u64 and validate() only rejected zero, so node_id >= 65536 silently lost its high bits (65537 collided with 1) — exactly the collision the packing exists to prevent. ClusterSettings::validate now rejects node_id >= 1<<16 with a typed Error::Config; a debug-and-release shift never masks. Tests cover the boundary (65535 ok, 65536/65537/2^20 rejected). - Refresh the now-stale NEXT_TXN_ID doc comment to describe the low-48-bits / node-id-high-16 split and the cross-node global-uniqueness invariant. - Revert the fluxbench source/checksum churn in Cargo.lock (leaked local [patch.crates-io] resolution state) back to origin/main — orthogonal to F1. Blocker 1 (non-leader single-shard COMMIT applies locally + WALs outside Raft) is confirmed and NOT yet fixed here — its fix mechanism is an open question raised on the PR (no atomic transaction-batch Raft entry exists, so the choice between N replicated proposals vs a new batch entry type has single-shard-commit atomicity implications). Held pending that decision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed
Blocker 1 — need your call on the mechanism before I write it. Confirmed the diagnosis: non-leader single-shard
So the two ways to make the leader durably own the commit both have a tradeoff I won't pick unilaterally on the txn core:
Which do you want? I lean (2) to keep single-shard COMMIT atomic, but it's your durability contract. Once you confirm, I'll land it with the (Re CI: no workflow is configured for PRs from the fork — happy to add one, or run whatever suite you point me at.) |
What
Implements F1 — transaction-id threading through cross-node dispatch from the locked cross-shard transaction design.
An explicit transaction's staged writes live in per-core overlays keyed by the session's transaction id. Before this change the id was threaded only on local/single-shard paths — the gateway's cross-node hop dropped it. So when a coordinator was not the leader for a collection's shard:
StageWritewas stranded (rejected: "StageWrite dispatched without a txn_id").Changes
ExecuteRequestcarries anOption<u64>txn_id;QueryContext,DispatchRouteStreamParams, andRemoteDispatchArgscarryOption<TxnId>. Autocommit call sites passNone; every transactional path threads the session id through the gateway, remote executor, and all-cores fan.gather_all_vshardsstops droppingtxn_idat itsQueryContextboundary — the cross-node in-transaction read now reaches the owning leader correctly keyed.dispatch_localroutes in-transaction plans via a newdispatch_to_data_plane_in_txnso the id reaches the Data Plane overlay.ROLLBACKunable to undo it. In-block tasks fall through to the staging gate, which stages writes and forwards reads/stage-ops to the leader.ROLLBACK'sDropTxnOverlayroutes to the owning leader, not a local replica.Testing
New 3-node e2e
cluster_txn_cross_node_ryow.rsruns, from every node,BEGIN/ stagedINSERT/ point + scan RYOW /ROLLBACK/ post-rollback-empty — so at least two iterations drive the non-leader forward path.Green: the new e2e,
sql_transactions_*_overlay+native_transactions_staging(single-node overlay regressions),calvin_cluster_pgwire_e2e+calvin_ollp_cross_node+single_node_calvin,rpc_codecroundtrips, session unit tests.clippyclean onnodedb+nodedb-cluster.Scope / not in this PR
SessionStores, which are not reachable fromSharedStatewhere thespawn_loopsweeper pattern lives. Flagging for a design decision rather than guessing the wiring.