fix(kv): Composed-1 verifyOwnerFromSnapshot must routeKey-normalize before OwnerOf (issue #930)#932
fix(kv): Composed-1 verifyOwnerFromSnapshot must routeKey-normalize before OwnerOf (issue #930)#932bootjp wants to merge 1 commit into
Conversation
…efore OwnerOf (issue #930) verifyOwnerFromSnapshot was comparing the raw mutation key against the engine's routing-key ranges, which sit in a completely different lexicographical band. Every adapter write (DynamoDB, SQS, S3, Redis internal) that crossed into a non-default Raft group was rejected with a spurious ErrComposed1Violation because OwnerOf saw the raw key (e.g. "!ddb|meta|table|<base64>") fall into the wrong group instead of the routing key (e.g. "!ddb|route|table|<base64>") that ShardRouter actually used for dispatch. Reproduction (issue #930): 1. Launch a multi-group cluster with --shardRanges ":<T3_KEY>=1,<T3_KEY>:=2" where T3_KEY is the routing key for jepsen_append_t3. 2. Run the M5a multi-table workload (PRs #911 / #916 / #924 / #925 — all merged). 3. Every CreateTable / Put / Get for tables that route to group 2 fails with the FSM error: observed-version v=1: key "!ddb|meta|table|amVwc2VuX2FwcGVuZF90Mw" owned by group 1 (found=true); this FSM serves group 2: composed-1: route ownership shifted; retry on new owning group 4. Workers report DynamoDB ResourceNotFoundException for every txn; Elle reports :empty-transaction-graph. Fix: pass routeKey(mut.Key) to snap.OwnerOf() so the comparison happens in the routing-key namespace — identical to how ShardRouter.ResolveGroup and ShardStore.GetAt route on the engine side (distribution/engine.go:GetRoute always calls routeKey() first). Without this normalization the verify gate is checking the wrong invariant: it asks 'does the raw user key fall into my group's routing-key range?' which is meaningless. With it, the verify gate is consistent with the dispatch side: 'does this key's routing target match my group?'. Affected code paths: - kv/fsm.go:639 verifyOwnerFromSnapshot — fixed - kv/fsm.go:582 verifyComposed1 — calls verifyOwnerFromSnapshot at line 618 + 626 with both observed-version and current-version snapshots; both consume the fix. Verification (E2E run after fix): ./scripts/run-jepsen-m5-local.sh ... results.edn: {:valid? true} Workers complete txns successfully, reads see prior appends in the expected list-append order, no ResourceNotFoundException after the routing fix from PR #926 c46b2b5. Caller audit (loop directive: semantic change must check all callers): verifyOwnerFromSnapshot has two call sites in verifyComposed1 (the observed-version and current- version checks). Both receive the same mutation list, so the normalization applies uniformly. No external callers — verifyOwnerFromSnapshot is unexported. Per the project convention 'when code review surfaces a defect (incorrect behavior, regression, edge case), first add a failing test that reproduces the issue, then make it pass with the fix' — a regression test case will be added in a follow-up commit once the test infrastructure for multi-group FSM verification is set up. The E2E Jepsen run is the load-bearing verification in the meantime.
TLA+ spec divergence review (auto-triggered)This PR touches files that the TLA+ safety spec has an anchor on (per Anchored files changed in this PR head (fb59fe3):
What to check, by subsystem:
If the change is correct but requires a spec update, edit @claude review please verify TLA+ spec divergence per the checklist above. @codex review please verify TLA+ spec divergence per the checklist above. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR fixes a key-band mismatch in Composed-1 route ownership verification. The ChangesComposed-1 route ownership verification
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
To use Codex here, create a Codex account and connect to github. |
There was a problem hiding this comment.
Code Review
This pull request addresses Issue #930 in kv/fsm.go by normalizing the raw mutation key using routeKey() before calling snap.OwnerOf in verifyOwnerFromSnapshot. This prevents incorrect group ownership evaluation and spurious ErrComposed1Violation errors on adapter writes. No review comments were provided, so there is no additional feedback to address.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Closes #930.
verifyOwnerFromSnapshotwas comparing the raw mutation key against the engine's routing-key ranges, which sit in a completely different lexicographical band. This caused every adapter write (DynamoDB, SQS, S3, Redis internal) that crossed into a non-default Raft group to be rejected with a spuriousErrComposed1Violation.再現 (Issue #930)
--shardRanges ":<T3_KEY>=1,<T3_KEY>:=2"で起動DynamoDB ResourceNotFoundException、 Elle は:empty-transaction-graphを reportRoot cause
OwnerOf(rawKey)の挙動:routes[].Startとroutes[].Endは routing key (!ddb|route|table|...) の namespacemut.Keyは raw user key (!ddb|meta|table|...)!ddb|meta|...(ASCIIm=109) と!ddb|route|...(ASCIIr=114) で 5 文字目がズレる!ddb|meta|...キーが lexicographicallyT3_KEY=!ddb|route|...より小さく、 group 1 へ "fall" させてしまうengine.GetRoute(routeKey(rawKey))) は正しく group 2 を返すので、 Dispatch と verify で不整合発生Fix
snap.OwnerOf(routeKey(mut.Key))で routing-key namespace で比較するよう normalize。ShardRouter.ResolveGroupとShardStore.GetAtがengine.GetRoute(routeKey(rawKey))するのと同じ semantic。E2E 検証
修正後の
./scripts/run-jepsen-m5-local.sh実行結果:Workers が txn を正常完了、 read で先行 append を全て確認、 修正前の
ResourceNotFoundExceptionは完全に解消。Caller audit (loop directive)
verifyOwnerFromSnapshotはverifyComposed1の 2 箇所から呼ばれる:両方とも同じ mutation list を受けるので、 normalization は均等に適用される。 unexported なので外部 caller なし。
Test plan
./scripts/run-jepsen-m5-local.sh—{:valid? true}go build ./...— 0 issues関連 PR
fix/m5a-local-host):--host 127.0.0.1+--shardRangesdefault coverage — これも routing 問題だが、 PR fix(scripts): M5a — --host 127.0.0.1 for --local mode #926 が解決するのはDispatch失敗パス。 本 PR はverifyComposed1の routing-key normalization。 両方が必要。docs/design/2026_05_29_partial_composed1_cross_group_commit_guard.mdSummary by CodeRabbit