Skip to content

migration: add range version RPC handlers#1088

Open
bootjp wants to merge 2 commits into
design/hotspot-split-m2-store-exportfrom
design/hotspot-split-m2-cross-group
Open

migration: add range version RPC handlers#1088
bootjp wants to merge 2 commits into
design/hotspot-split-m2-store-exportfrom
design/hotspot-split-m2-cross-group

Conversation

@bootjp

@bootjp bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • add Internal ExportRangeVersions and ImportRangeVersions handlers backed by the local group store
  • wire per-group stores into Internal server registration
  • preserve staged migration route metadata in the serving route engine snapshot

Tests

  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./distribution ./kv ./store -run 'TestInternal|TestMigration|TestExportVersions|TestImportVersions|TestEngineApplySnapshot|TestShardStore|TestLeaderRoutedStore' -count=1 -timeout=180s\n- GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./adapter ./distribution ./kv ./store --timeout=5m\n\nAuthor: bootjp

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31423364-5734-4f70-8b8b-f299e983e015

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (64dc6f4):

  • distribution/engine.go
  • distribution/engine_test.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for range migrations by implementing ExportRangeVersions and ImportRangeVersions on the internal gRPC server, alongside updating the distribution Route metadata to track migration states. Feedback focuses on adding a defensive check in ExportRangeVersions to prevent infinite loops when the cursor does not progress, and removing an unreachable nil guard on i.leader in verifyInternalLeader to comply with guidelines against dead defensive code, which also requires updating the corresponding unit tests to use a mock leader.

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.

Comment thread adapter/internal.go
Comment thread adapter/internal.go
Comment thread adapter/internal_migration_test.go
Comment thread adapter/internal_migration_test.go Outdated
Comment thread adapter/internal_migration_test.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64dc6f4239

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adapter/internal.go Outdated
Comment thread adapter/internal.go
Comment thread adapter/internal.go
Comment thread adapter/internal.go
Comment thread adapter/internal.go Outdated
@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (59a1672):

  • distribution/engine.go
  • distribution/engine_test.go
  • kv/fsm.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Updated in 59a1672:

  • Route ImportRangeVersions through the target-group Raft/FSM path before ACK.
  • Observe the imported HLC floor before acknowledging non-empty imports.
  • Carry export key_family and bracket exclusions through the internal RPC.
  • Reject unbounded max_commit_ts exports and non-progressing export cursors.

Validation:

  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./kv ./store -run 'TestInternal|TestMigration|TestExportVersions|TestImportVersions|TestApply|TestRouteKeyFilter|TestShardStore|TestLeaderRoutedStore' -count=1 -timeout=180s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./distribution -count=1 -timeout=120s
  • GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./adapter ./distribution ./kv ./store --timeout=5m
  • git diff --check

Note: a broader go test ./adapter ./distribution ./kv ./store -count=1 -timeout=240s timed out in the adapter integration suite; the targeted touched-path tests above passed.

@bootjp
bootjp force-pushed the design/hotspot-split-m2-cross-group branch from 59a1672 to fbd7f56 Compare July 13, 2026 14:32
@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (fbd7f56):

  • distribution/engine.go
  • distribution/engine_test.go
  • kv/fsm.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Rebased onto the latest design/hotspot-split-m2-fence-drain head in fbd7f56 after #1087 advanced. The migration import/export fixes remain intact and now include the parent broad DEL_PREFIX fence fix.

Validation:

  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./kv ./store -run 'TestInternal|TestMigration|TestExportVersions|TestImportVersions|TestApply|TestRouteKeyFilter|TestShardStore|TestLeaderRoutedStore|TestRoutePrefixRange|TestFSMRejects.*WriteFenced|TestShardedCoordinatorRejects.*WriteFenced' -count=1 -timeout=180s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./distribution -count=1 -timeout=120s
  • GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./adapter ./distribution ./kv ./store --timeout=5m
  • git diff --check

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Updated in 0fe341d:

  • Serve GetRouteOwnership from the exact RouteHistorySnapshot requested by catalog_version.
  • Serve GetIntersectingRoutes from the same versioned snapshot, including open-ended ranges.
  • Return NotFound for catalog versions outside the retained route-history window and FailedPrecondition when the distribution engine is absent.

Validation:

  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter -run 'TestDistributionServer(GetRouteOwnership|GetIntersectingRoutes|OwnershipRPCs)' -count=1 -timeout=120s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./distribution -run 'TestDistributionServer|TestEngineSnapshotAt|TestEngineGetIntersectingRoutes|TestRouteHistorySnapshot' -count=1 -timeout=180s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./kv ./store -run 'TestInternal|TestMigration|TestExportVersions|TestImportVersions|TestApply|TestRouteKeyFilter|TestShardStore|TestLeaderRoutedStore|TestDistributionServer(GetRouteOwnership|GetIntersectingRoutes|OwnershipRPCs)' -count=1 -timeout=180s
  • GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./adapter ./distribution --timeout=5m
  • git diff --check

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (0fe341d):

  • distribution/engine.go
  • distribution/engine_test.go
  • kv/fsm.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (7d3b01c):

  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • kv/fsm.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Updated in 7d3b01c:

  • ImportRangeVersions now stages imported MVCC versions under the target-local !dist|migstage|<job_id>|<raw_key> shadow keyspace instead of writing raw user keys directly.
  • Added staged-data key helpers for job-scoped scans and future promotion/read-merge code to recover the original raw key.
  • Updated RPC/FSM tests to assert staged writes, raw-key absence before promotion, HLC floor preservation, and key/value clone safety.

Validation:

  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./distribution -run 'TestMigrationStagedDataKeyRoundTrip|TestPlanMigrationBrackets|TestValidateMigrationRouteRange' -count=1 -timeout=120s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./kv ./store -run 'TestInternal|TestMigration|TestImportVersions|TestExportVersions|TestApplyMigration|TestDistributionServer(GetRouteOwnership|GetIntersectingRoutes|OwnershipRPCs)' -count=1 -timeout=180s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./distribution ./kv ./store -run 'TestInternal|TestMigration|TestExportVersions|TestImportVersions|TestApply|TestRouteKeyFilter|TestShardStore|TestLeaderRoutedStore|TestDistributionServer(GetRouteOwnership|GetIntersectingRoutes|OwnershipRPCs)' -count=1 -timeout=240s
  • GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./adapter ./distribution ./kv --timeout=5m
  • git diff --check

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (e7f69ef):

  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • kv/fsm.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Updated in e7f69ef:

  • Route reads with staged_visibility_active now merge live raw keys with target-local staged keys for GetAt, ScanAt, ReverseScanAt, and LatestCommitTS.
  • The merge chooses the newest MVCC version across live/staged candidates, preserves tombstone and TTL winner semantics, and maps staged keys back to their original raw keys.
  • Physical-limit scans on staged-visible routes now fail closed because the staged/live export merge cannot preserve the physical scan budget exactly.

Validation:

  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./kv -run 'TestShardStore(GetAt_MergesStagedVisibility|ScanAndLatestCommitTS_MergeStagedVisibility)' -count=1 -timeout=120s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./kv -run 'TestShardStore|TestMergeAndTrim|TestScanLockBounds' -count=1 -timeout=180s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./kv -count=1 -timeout=240s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./store -run 'TestInternal|TestMigration|TestImportVersions|TestExportVersions|TestApply|TestRouteKeyFilter|TestShardStore|TestLeaderRoutedStore|TestGRPCServer_Raw' -count=1 -timeout=180s
  • GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./kv --timeout=5m
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./distribution ./kv ./store -run 'TestInternal|TestMigration|TestExportVersions|TestImportVersions|TestApply|TestRouteKeyFilter|TestShardStore|TestLeaderRoutedStore|TestDistributionServer(GetRouteOwnership|GetIntersectingRoutes|OwnershipRPCs)' -count=1 -timeout=240s
  • git diff --check

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7f69efd9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/shard_store.go Outdated
func latestMVCCVersionAt(ctx context.Context, st store.MVCCStore, key []byte, ts uint64) (store.MVCCVersion, bool, error) {
result, err := st.ExportVersions(ctx, store.ExportVersionsOptions{
StartKey: key,
EndKey: nextScanCursor(key),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use exact-key export bounds for staged point reads

When the backing store is Pebble, ExportVersions converts EndKey to encodeKey(EndKey, MaxUint64) (store/lsm_migration.go), and nextScanCursor(key) (key + 0x00) sorts before normal encoded versions of key because the timestamp suffix begins with ^commitTS. On a staged-visible route backed by Pebble, both the live and staged point exports therefore return no candidates for ordinary keys, so GetAt can incorrectly return ErrKeyNotFound even though the key exists. Use a true single-key upper bound/exact-key filter instead of the scan cursor here.

Useful? React with 👍 / 👎.

Comment thread kv/shard_store.go Outdated
ts uint64,
reverse bool,
) ([]*store.KVPair, error) {
live, err := collectLatestLogicalVersions(ctx, g.Store, start, end, start, end, ts, liveLogicalVersionKey)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve scan limits while merging staged visibility

For staged-visible routes, this call starts an export loop that runs until result.Done, and the caller only applies limit after collecting both the full live range and the full staged prefix into maps. A user scan such as ScanAt(..., limit=100) over a migrated production range can therefore read and retain every version in the route before returning one page, which is a large latency/memory regression versus the previous bounded store scan. Please stop the merge once enough ordered candidates are available or otherwise page the merge by the requested limit.

Useful? React with 👍 / 👎.

Comment thread kv/shard_store.go
Comment thread kv/shard_store.go Outdated
Comment thread adapter/internal.go Outdated
Comment thread distribution/engine.go
@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (ca1a050):

  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • kv/fsm.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Updated in ca1a050:

  • Bound staged visibility point reads to the exact key and reject compacted staged reads before merging.
  • Preserve staged scan limits with bounded candidate windows, and fail closed for physical-limit scans when staged visibility is active.
  • Route S3 bucket meta/generation exports by decoded bucket name so route filters match logical ownership.
  • Enforce migration write timestamp floors at coordinator, FSM, and ShardStore write paths.

Validation:

  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./kv -run 'TestFSMRejects.*MigrationTimestampFloor|TestFSMRejectsCommitButNotPrepareAtMigrationTimestampFloor|TestShardStore(GetAt_MergesStagedVisibilityPebbleExactKey|StagedVisibilityReadTSCompacted|PhysicalLimitFailsClosedBeforeStagedVisibilityFallback|RejectsWritesAtMigrationTimestampFloor)|TestShardStoreScanAndLatestCommitTS_MergeStagedVisibility|TestShardedCoordinatorRejects(PointWrite|DelPrefix)AtMigrationTimestampFloor|TestShardedCoordinatorDispatchTxn_RejectsMigrationTimestampFloor' -count=1 -timeout=180s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter -run 'TestInternalExportRangeVersions(UsesDecodedS3BucketRouteFilter|UsesStoreAndRouteFilter|RejectsUnboundedExport)|TestInternalImportRangeVersionsAppliesStoreBatch' -count=1 -timeout=120s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./internal/s3keys -run 'TestBucketGenerationKey|TestParseBucketGenerationKey|TestBucketMetaKey' -count=1 -timeout=120s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./kv -count=1 -timeout=240s
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter ./distribution ./store ./internal/s3keys -run 'TestInternal|TestMigration|TestExportVersions|TestImportVersions|TestApply|TestRouteKeyFilter|TestLeaderRoutedStore|TestDistributionServer|TestRouteDescriptor|TestEngine|TestBucket' -count=1 -timeout=240s
  • GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./adapter ./distribution ./kv ./store ./internal/s3keys --timeout=5m
  • git diff --check

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca1a05070c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/shard_store.go
Comment thread kv/shard_store.go Outdated
Comment on lines +717 to +719
nextWindow := nextStagedVisibilityCandidateWindow(window)
if nextWindow == window {
return out, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Continue staged scans after capped windows

For staged-visibility routes with more than 8192 visible logical keys, any caller asking for a limit above stagedVisibilityMaxCandidateWindow can get a truncated result. Once window reaches 8192, a full underlying scan with len(out) < limit falls into nextWindow == window and returns immediately, even though there may be more keys after the current candidate window. This makes large ScanAt/ReverseScanAt calls silently miss keys during staged cutover.

Useful? React with 👍 / 👎.

Comment thread adapter/internal.go Outdated
if routeStart != nil && bytes.Compare(routeKey, routeStart) < 0 {
return false
}
return routeEnd == nil || bytes.Compare(routeKey, routeEnd) < 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat empty S3 route ends as unbounded

This decoded S3 predicate only treats nil as an unbounded route_end, unlike RouteKeyFilter and the route descriptor convention where nil or empty means +infinity. If an unbounded last route is passed as a non-nil empty byte slice, every decoded bucket route key compares greater than []byte{}, so bucket meta/generation exports for that route return no versions. Check len(routeEnd) == 0 here instead of only routeEnd == nil.

Useful? React with 👍 / 👎.

Comment thread kv/fsm_migration_import.go Outdated
if err := proto.Unmarshal(data, req); err != nil {
return errors.WithStack(err)
}
result, err := f.store.ImportVersions(ctx, store.ImportVersionsOptions{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep migration imports off the direct write gate

In encrypted clusters where storage-envelope cutover is active but a replica's writer registration is not yet committed, this FSM-apply path calls store.ImportVersions, whose Pebble implementation uses the direct write/encryption gate (encryptForKey(..., true)). That can return ErrWriterNotRegistered while replaying the Raft entry and fail the import on that replica; migration imports need a raft-apply variant that uses the non-gated FSM path instead of the direct ImportVersions path.

Useful? React with 👍 / 👎.

Comment thread kv/sharded_coordinator.go
Comment thread kv/shard_store.go Outdated

if engineForGroup(g) == nil {
return s.localGetAt(ctx, g, key, ts)
return s.localGetAt(ctx, g, distribution.Route{}, key, ts)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve staged visibility on explicit group reads

When callers use the explicit-group path (for example RawGet/RawScanAt with group_id for SQS partitioned ownership), this passes a zero route into the local read path, so routeHasStagedVisibility is always false and imported staged versions are ignored after cutover. Those reads can return not found or stale live data even though normal GetAt/ScanAt for the same logical route would merge staged data; the explicit group path needs the staged route metadata or must fail closed while staged visibility is active.

Useful? React with 👍 / 👎.

Comment thread adapter/internal.go
@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

Addressed the staged-visibility migration review items.

Changes:

  • Merge staged commit timestamps into OCC validation/read-key checks for both ShardStore and ShardedCoordinator paths.
  • Keep migration imports on the raft-apply store path so encryption writer-registration gates do not block replay.
  • Preserve staged visibility metadata for explicit-group RawGet/RawScan paths, failing closed if the route cannot be resolved while staged visibility is active.
  • Continue staged scans past capped candidate windows.
  • Enforce migration write timestamp floors after TSO raw timestamp stamping.
  • Harden S3 decoded route export filters and reject fully unbounded migration export requests.

Validation:

  • go test -count=1 ./kv -run 'TestShardStore(ExplicitGroupReads_MergeStagedVisibility|ScanAt_ContinuesStagedVisibilityAfterCandidateWindow|ApplyMutations_ValidatesStagedReadKeys|PhysicalLimitFailsClosedBeforeStagedVisibilityFallback)|TestShardedCoordinator(ValidateReadKeysOnShard_UsesStagedVisibility|DispatchTxn_AddsStagedReadKeyAlias|RejectsTSORawPointWriteAfterStamping|DispatchTxn_RejectsMigrationTimestampFloor)'\n- go test -count=1 ./adapter -run 'TestGRPCServer_Raw(Get|ScanAt)UsesExplicitGroup|TestInternalExportRangeVersions(RejectsUnboundedExport|UsesDecodedS3BucketRouteFilter|DecodedS3EmptyRouteEndIsUnbounded|AppliesStoreBatch)'\n- go test -count=1 ./store -run 'TestRegistrationGate(DirectPathFailsClosedBeforeRegistration|FSMApplyPathNeverGated)'\n- golangci-lint --config=.golangci.yaml run ./kv ./store ./adapter --timeout=5m\n- git diff --check\n\nBroader go test -count=1 ./kv ./store ./adapter passed ./kv and ./store, then ./adapter hit the package-level 10m timeout; the focused adapter coverage above passed.\n\n@codex review

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (d33bf35):

  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • kv/fsm.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d33bf3589a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/shard_store.go Outdated
if route, ok := s.engine.GetRoute(routeKey(key)); ok && route.GroupID == groupID {
return route, nil
}
if s.groupHasStagedVisibility(groupID) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fail closed when the resolved staged route moved groups

When an explicit-group RawGet arrives with a stale group_id after cutover (for example, the caller still targets the old source group while engine.GetRoute(key) now returns the target route with StagedVisibilityActive), this checks staged visibility on the requested group instead of the resolved route. If the old group has no staged route, the function falls through to fallback and reads the old group's live MVCC without the staged merge, serving stale pre-migration data instead of failing closed or rerouting.

Useful? React with 👍 / 👎.

Comment thread kv/shard_store.go Outdated
if len(matched) > 0 {
return matched, true, nil
}
if s.groupHasStagedVisibility(groupID) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject stale explicit scans over staged target routes

For explicit-group RawScan, a stale proxy can still send the old source group_id after the current intersecting route has moved to the target with staged visibility. In that case matched is empty, this check only inspects the requested group, and the fallback scans the old group's local store, so range reads can miss staged target data instead of failing closed; fail when any intersecting current route is staged but not matched to the requested group.

Useful? React with 👍 / 👎.

Comment thread kv/shard_store.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5affdac49a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adapter/internal.go
Comment thread kv/fsm.go Outdated
Comment thread kv/shard_store.go
@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (cb7b3de):

  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • kv/fsm.go
  • kv/sharded_coordinator.go
  • store/mvcc_store.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Latest HEAD cb7b3de merges the current parent and addresses the latest findings: decoded S3 auxiliary floor routing, resolver-bypass floor handling across all apply callers, and staged/live key-only scans. Validation: go test ./kv -count=1; go test ./... -run ^$ -count=1; golangci-lint run ./... --timeout=5m.

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements staged range migration support, introducing target-group migration import batches and staged-data promotion. It adds new RPCs for exporting, importing, and promoting staged versions, integrates staged visibility merging into shard store reads and scans, and enforces write timestamp floors to reject mutations below the migration floor. The review feedback is highly accurate, pointing out critical compilation errors in adapter/internal.go and adapter/internal_test.go where the undefined prefixScanEnd function is called instead of store.PrefixScanEnd or testPrefixScanEnd.

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.

Comment thread adapter/internal.go
Comment thread adapter/internal.go
Comment thread adapter/internal_test.go
@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements staged range migration support, introducing features to import, promote, and merge staged data versions with live keyspaces, alongside write timestamp floor validation to reject writes below the migration cutover floor. The review feedback highlights a critical lock-order inversion deadlock in PromoteVersions and a severe performance bottleneck in latestStagedVisibilityCandidates due to O(N) point lookups. Additionally, improvements are suggested to optimize slice allocations during key aliasing and to correctly handle empty-but-non-nil slices in scan bounds.

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.

Comment on lines +213 to +217
s.dbMu.RLock()
defer s.dbMu.RUnlock()

s.applyMu.Lock()
defer s.applyMu.Unlock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Concurrency Issue: Lock-Order Inversion

There is a lock-order inversion between s.dbMu and s.applyMu.

In other write paths (such as ApplyMutations), the serialization lock s.applyMu is acquired first, followed by the database read lock s.dbMu.RLock(). However, in PromoteVersions, s.dbMu.RLock() is acquired before s.applyMu.Lock().

If one goroutine is executing ApplyMutations (holding applyMu and waiting for dbMu) while another goroutine is executing PromoteVersions (holding dbMu and waiting for applyMu), a deadlock will occur.

To prevent this, always acquire s.applyMu before s.dbMu.

Suggested change
s.dbMu.RLock()
defer s.dbMu.RUnlock()
s.applyMu.Lock()
defer s.applyMu.Unlock()
s.applyMu.Lock()
defer s.applyMu.Unlock()
s.dbMu.RLock()
defer s.dbMu.RUnlock()

Comment thread kv/shard_store.go
Comment on lines +2038 to +2066
func (s *ShardStore) latestStagedVisibilityCandidates(
ctx context.Context,
st store.MVCCStore,
route distribution.Route,
liveKVs []*store.KVPair,
stagedKVs []*store.KVPair,
ts uint64,
) (map[string]store.MVCCVersion, error) {
keys := stagedVisibilityCandidateKeys(liveKVs, stagedKVs)
out := make(map[string]store.MVCCVersion, len(keys))
for _, key := range keys {
live, liveOK, err := latestMVCCVersionAt(ctx, st, key, ts)
if err != nil {
return nil, err
}
stagedKey := distribution.MigrationStagedDataKey(route.MigrationJobID, key)
staged, stagedOK, err := latestMVCCVersionAt(ctx, st, stagedKey, ts)
if err != nil {
return nil, err
}
if stagedOK {
staged.Key = bytes.Clone(key)
}
if winner, ok := newerMigrationVersion(live, liveOK, staged, stagedOK); ok {
out[string(key)] = winner
}
}
return out, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Efficiency Issue: O(N) Database Queries during Scan

The latestStagedVisibilityCandidates function performs two individual point lookups (latestMVCCVersionAt) for every single candidate key in the scan window. Under large scan windows (where stagedVisibilityMaxCandidateWindow can be up to 8192), this can result in up to 16,384 individual database queries (st.ExportVersions) within a single page scan. This will cause severe latency spikes and CPU overhead.

Suggested Optimization

Instead of performing O(N) point lookups, perform a single range-based ExportVersions scan for both the live and staged ranges, and then merge the two sorted version streams in memory. This reduces the database query complexity from O(N) to O(1) (specifically, just 2 range queries).

Comment thread kv/shard_store.go
Comment on lines +3292 to +3322
func (s *ShardStore) readKeysWithStagedVisibilityAliases(group *ShardGroup, readKeys [][]byte) [][]byte {
if len(readKeys) == 0 {
return readKeys
}
for _, key := range readKeys {
readKeys = s.appendStagedVisibilityAlias(group, readKeys, key)
}
return readKeys
}

func (s *ShardStore) readKeysWithStagedVisibilityMutationAliases(group *ShardGroup, readKeys [][]byte, mutations []*store.KVPairMutation) [][]byte {
for _, mut := range mutations {
if mut == nil {
continue
}
readKeys = s.appendStagedVisibilityAlias(group, readKeys, mut.Key)
}
return readKeys
}

func (s *ShardStore) appendStagedVisibilityAlias(group *ShardGroup, readKeys [][]byte, key []byte) [][]byte {
if s == nil || s.engine == nil || group == nil {
return readKeys
}
alias, ok := s.stagedVisibilityReadKeyAlias(group, key)
if !ok {
return readKeys
}
out := append([][]byte(nil), readKeys...)
return append(out, alias)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Efficiency & Maintainability: Redundant Slice Copying and Unused Helper

The current implementation of readKeysWithStagedVisibilityAliases and readKeysWithStagedVisibilityMutationAliases reassigns and ranges over the slice being modified, and calls appendStagedVisibilityAlias which performs a full slice copy (append([][]byte(nil), readKeys...)) on every single successful alias append. This results in O(M) slice allocations and copies (where M is the number of keys/mutations).

Additionally, by refactoring these functions to append directly to a single slice, the helper function appendStagedVisibilityAlias becomes completely unused and can be removed.

Suggested Improvement

We can lazily allocate the output slice only when the first alias is found, avoiding any allocations or copies when no aliases are present.

func (s *ShardStore) readKeysWithStagedVisibilityAliases(group *ShardGroup, readKeys [][]byte) [][]byte {
	if len(readKeys) == 0 {
		return readKeys
	}
	out := readKeys
	for _, key := range readKeys {
		if alias, ok := s.stagedVisibilityReadKeyAlias(group, key); ok {
			if out == readKeys {
				out = append([][]byte(nil), readKeys...)
			}
			out = append(out, alias)
		}
	}
	return out
}

func (s *ShardStore) readKeysWithStagedVisibilityMutationAliases(group *ShardGroup, readKeys [][]byte, mutations []*store.KVPairMutation) [][]byte {
	out := readKeys
	for _, mut := range mutations {
		if mut == nil {
			continue
		}
		if alias, ok := s.stagedVisibilityReadKeyAlias(group, mut.Key); ok {
			if out == readKeys {
				out = append([][]byte(nil), readKeys...)
			}
			out = append(out, alias)
		}
	}
	return out
}

Comment thread kv/shard_store.go
Comment on lines +2101 to +2112
func stagedVisibilityScanBounds(jobID uint64, start []byte, end []byte) ([]byte, []byte) {
prefix := distribution.MigrationStagedDataKeyPrefix(jobID)
scanStart := prefix
if start != nil {
scanStart = distribution.MigrationStagedDataKey(jobID, start)
}
if err != nil {
return nil, errors.WithStack(err)
scanEnd := prefixScanEnd(prefix)
if end != nil {
scanEnd = distribution.MigrationStagedDataKey(jobID, end)
}
lockStart, lockEnd := scanLockBoundsForKVsDirection(kvs, start, end, limit, reverse)
lockKVs, err := scanTxnLockRangeAt(ctx, g, lockStart, lockEnd, ts, limit)
if err != nil {
return nil, err
return scanStart, scanEnd
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Correctness: Handle Empty-but-Non-Nil Slices in Scan Bounds

The stagedVisibilityScanBounds function checks start != nil and end != nil to decide whether to append the keys to the staged prefix. However, in Go, an empty slice (e.g., []byte("")) is not nil (end != nil is true), but its length is 0.

If a caller passes an empty-but-non-nil slice as end, scanEnd will be resolved to distribution.MigrationStagedDataKey(jobID, []byte("")), which is identical to the start prefix. This results in an empty scan range [prefix, prefix) and scans zero staged keys.

Using len(start) > 0 and len(end) > 0 is much more robust and correctly handles both nil and empty slices.

Suggested change
func stagedVisibilityScanBounds(jobID uint64, start []byte, end []byte) ([]byte, []byte) {
prefix := distribution.MigrationStagedDataKeyPrefix(jobID)
scanStart := prefix
if start != nil {
scanStart = distribution.MigrationStagedDataKey(jobID, start)
}
if err != nil {
return nil, errors.WithStack(err)
scanEnd := prefixScanEnd(prefix)
if end != nil {
scanEnd = distribution.MigrationStagedDataKey(jobID, end)
}
lockStart, lockEnd := scanLockBoundsForKVsDirection(kvs, start, end, limit, reverse)
lockKVs, err := scanTxnLockRangeAt(ctx, g, lockStart, lockEnd, ts, limit)
if err != nil {
return nil, err
return scanStart, scanEnd
}
func stagedVisibilityScanBounds(jobID uint64, start []byte, end []byte) ([]byte, []byte) {
prefix := distribution.MigrationStagedDataKeyPrefix(jobID)
scanStart := prefix
if len(start) > 0 {
scanStart = distribution.MigrationStagedDataKey(jobID, start)
}
scanEnd := prefixScanEnd(prefix)
if len(end) > 0 {
scanEnd = distribution.MigrationStagedDataKey(jobID, end)
}
return scanStart, scanEnd
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

elastickv/adapter/grpc.go

Lines 255 to 258 in cb7b3de

if !req.GetReverse() && !req.GetRouteBoundsPresent() {
if keyScanner, ok := r.store.(rawReadFenceKeyScanner); ok {
keys, err := keyScanner.ScanKeysAtWithReadFence(ctx, req.StartKey, req.EndKey, limit, readTS, req.GetGroupId(), readRouteVersion)
return keys, errors.WithStack(err)

P1 Badge Avoid key-only proxy scans losing staged visibility

For a follower key-only scan on a staged-visibility route, proxyScanGroupID sends only GroupId to the leader; the gRPC key-only fast path then calls ScanKeysAtWithReadFence with an explicit group but no route descriptor, so the receiver constructs Route{GroupID: ...} and cannot merge MigrationJobID staged rows. This makes follower SCAN/keys-only reads omit keys that exist only in staged data during cutover, while the value scan path below would materialize through ScanAtWithReadFence and return them.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/fsm.go
Comment on lines +556 to +560
if _, bypass := writeFenceBypassKeys[string(mut.Key)]; !bypass {
if err := f.verifyRouteNotFencedForKey(mut.Key); err != nil {
return err
}
if err := f.verifyRouteWriteTimestampFloorForKey(mut.Key, commitTS); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not bypass timestamp floors with fence bypass keys

When a mutation is present in WriteFenceBypassKeys (used for resolver/explicit-group routing), this block skips both the write-fence check and the new MinWriteTSExclusive check. If a route's floor advances between timestamp allocation/dispatch and Raft apply, a bypassed write with commitTS <= MinWriteTSExclusive can still land on followers, defeating the post-cutover stale-write fence; the bypass should only suppress ownership/write-fence routing checks, not the timestamp-floor validation.

Useful? React with 👍 / 👎.

Comment thread adapter/internal.go
MaxBytes: chunkBytes,
MaxScannedBytes: maxScannedBytes,
KeyFamily: req.GetKeyFamily(),
AcceptKey: i.migrationExportFilter(req),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use value-aware filtering for legacy list-delta exports

Legacy list delta keys share the !lst|meta|d| raw prefix with list metadata whose user key begins d|, and distribution.MigrationBracket.ContainsRoutedVersion is the only code that disambiguates by value. This RPC only wires an AcceptKey predicate, so ExportVersions route-filters MigrationFamilyLegacyListMetaDelta rows using the key-only kv.RouteKeyFilter; actual legacy deltas can be omitted from their owning route or copied under the wrong split, while the ListMeta bracket also excludes the ambiguous metadata. A split migration containing such legacy list rows will lose or misplace list metadata/deltas unless an AcceptVersion value-aware filter is passed for that family.

Useful? React with 👍 / 👎.

Comment thread kv/shard_store.go
if len(out) <= limit {
return out
if dedupeByKey {
out = appendReplacingKVsByKey(out, kvs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor staged tombstones in auxiliary scans

For S3 bucket-meta/generation raw scans during staged visibility, non-staged raw routes are scanned first and then staged routes are merged with dedupeByKey, but this replacement only happens when the staged route emits a visible KV. If the staged/new route has a newer tombstone for an auxiliary key while the old raw-route group still has an older live row, visibleLogicalKVs emits nothing for that key, so this merge leaves the stale live KV in SCAN/reverse-scan results even though point GetAt would hide it; the merge needs to carry delete decisions, not just visible replacements.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (cfff9b7):

  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • kv/fsm.go
  • kv/sharded_coordinator.go
  • store/mvcc_store.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

elastickv/kv/shard_store.go

Lines 3987 to 3989 in cfff9b7

GroupId: groupID,
ReadRouteVersion: readRouteVersion,
KeysOnly: true,

P2 Badge Send staged route metadata for key proxies

When a key-only scan for a staged-visible route is served by a follower, this proxy request carries only group_id and read_route_version; the leader receives KeysOnly without route bounds and the gRPC path calls ShardStore.ScanKeysAtWithReadFence with a bare distribution.Route{GroupID: groupID}. In that context routeHasStagedVisibility is false, so keys that exist only under !dist|migstage|... are omitted from backups/key-only pages during staged visibility. Fresh evidence after the prior “fixed in b9ac0cd” reply is that current proxyRawScanKeysAt still does not populate RouteStart, RouteEnd, or RouteBoundsPresent, unlike the value-scan proxy.


out = mergeAndTrimScanKeys(out, filterTxnInternalKeys(keys), limit)

P2 Badge Filter staged control keys from key scans

When a key-only scan or BackupScanner page covers the reserved !dist|migstage| prefix before a route has staged visibility active, the underlying store can return staged shadow rows as ordinary keys, and this refill path only removes transaction-internal keys. Value scans now use isScanInternalKey to suppress MigrationStagedDataKey rows, but key-only scans still expose those physical shadow keys to callers and backups instead of keeping them internal until they are decoded through a staged-visible route.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/fsm.go
Comment on lines +605 to +607
rkey := routeKey(key)
if snap.WriteFencedForKey(rkey) {
return errors.Wrapf(ErrRouteWriteFenced, "key %q routeKey %q", key, rkey)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check S3 auxiliary fences before raw routes

For S3 bucket metadata/generation writes, a write-fenced raw !s3|... route still trips this raw routeKey check before the decoded !s3route|<bucket> auxiliary range is considered. In the same scenario covered by the raw-floor tests—an unrelated raw S3 route is fenced/floored while the bucket's auxiliary owner is active—bucket metadata writes are rejected at Raft apply even though ownership for these auxiliary keys is decided by the auxiliary route; the coordinator's rejectWriteFencedPointKey has the same raw-first ordering, so both admission and apply need to skip unrelated raw fences for these keys.

Useful? React with 👍 / 👎.

@bootjp
bootjp force-pushed the design/hotspot-split-m2-store-export branch from e870fb5 to 4061c00 Compare July 19, 2026 12:49
@bootjp
bootjp force-pushed the design/hotspot-split-m2-cross-group branch from cfff9b7 to 30ce8c7 Compare July 19, 2026 13:12
@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (30ce8c7):

  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • kv/fsm.go
  • kv/sharded_coordinator.go
  • store/mvcc_store.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto #1085 HEAD 87d5776 and fixed both current-head P2 findings in 30ce8c7. Staged-visible key-only follower scans now carry the selected group and route bounds, and every key-only refill path filters staged control keys through the shared scan-internal predicate.

Evidence:

  • old remote cfff9b7 tree and sanitized pre-rebase 06ffd96f tree both f81b6eae97913ff1ef9d106795117a0ab695810c; tree diff was empty
  • go test ./kv -run 'TestShardStore(ScanKeysRouteAtLeaderRefillsAfterStagedControlKeys|ProxyScanKeysAtCarriesStagedRouteBounds|ScanKeysRouteAtLeaderIncludesStagedOnlyKeys|ScanKeysAtIncludesStagedOnlyKeys|ProxyScanKeysAtUsesSelectedGroup)$' -count=1\n- go test ./adapter ./distribution ./kv ./store -count=1\n- go test ./... -run '^$' -count=1\n- golangci-lint run ./adapter ./distribution ./kv ./store --timeout=5m --allow-parallel-runners\n- buf generate from proto/ left no generated diff\n- caller audit completed for key-only filters/proxies and migration write-floor checks\n- all PR-visible commits have author/committer bootjp contact@bootjp.me and valid signatures\n\n@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@bootjp
bootjp force-pushed the design/hotspot-split-m2-cross-group branch from 30ce8c7 to 1d0a97d Compare July 19, 2026 13:27
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Follow-up HEAD 1d0a97d fixes the Jepsen pre-build failure from the rebase. stampTxnTimestamps now preserves the current (commitTS, error) contract while applying the migration write-floor check, and both affected test callers were updated. The merged filesystem/staged scan preparation was also extracted to keep cyclomatic complexity within the repository limit.

Evidence:

  • go build -o /tmp/elastickv-pr1088 .
  • go test ./... -run '^$' -count=1\n- go test ./adapter -run '^TestStamp(Txn|Raw)Timestamps' -count=1\n- focused staged key proxy/filter tests pass\n- golangci-lint run ./adapter ./kv --timeout=5m --allow-parallel-runners (0 issues)\n- full adapter integration suite reaches its existing multi-node test timeout locally on macOS; Linux CI is rerunning\n\n@codex review

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (1d0a97d):

  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • kv/fsm.go
  • kv/sharded_coordinator.go
  • store/mvcc_store.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@bootjp
bootjp force-pushed the design/hotspot-split-m2-cross-group branch from 1d0a97d to 0a1b9c8 Compare July 19, 2026 13:41
@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

This PR touches files that the TLA+ safety spec has an anchor on (per
docs/design/2026_05_28_implemented_tla_safety_spec.md §3),
so an AI review is requested below to verify the implementation has not drifted
from the model.

Anchored files changed in this PR head (0a1b9c8):

  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • kv/fsm.go
  • kv/sharded_coordinator.go
  • store/mvcc_store.go

What to check, by subsystem:

  • kv/hlc*.goNext() must respect the HLC-4 preconditions (i)/(ii)/(iii) from the design doc: bounded skew, logical-counter handoff on leader change (strategy (c) Observe(MaxAppliedHLC)), and the commit-time ceiling fence (fail-closed when wall_now >= physicalCeiling). Any change to the bit layout (48/16), the CAS loop, or the ceiling getter/setter is in scope.
  • kv/coordinator.go, kv/sharded_coordinator.goRunHLCLeaseRenewal, hlcRenewalInterval, hlcPhysicalWindowMs constants, and the new-term detection that calls Observe(fsm.MaxAppliedHLC()) (strategy (c)). Any change to renewal cadence, group selection, or fail-closed behaviour is in scope.
  • kv/transaction.go, kv/lock_resolver.go — OCC commit-ts assignment, lock-map encoding (key, lock_ts) -> start_ts, and the LockResolver action OCC-3 depends on. (M2 spec will land OCC-1..OCC-5; until then the spec doc §5.2 is the contract.)
  • kv/fsm.go — FSM apply of HLC lease entries (SetPhysicalCeiling), and any future MaxAppliedHLC() accessor that strategy (c) needs.
  • store/mvcc_store.go — version visibility, snapshot install, and the MVCC-1..MVCC-4 invariants (M3 scope).
  • distribution/** — route catalog versioning, SplitRange atomicity, and CatalogWatcher async fan-out (M4 scope).

If the change is correct but requires a spec update, edit tla/hlc/HLC.tla (or the corresponding M2..M5 module once landed) and the design doc in the same PR. The tla-check workflow runs the TLC model check on the same paths.


@claude review please verify TLA+ spec divergence per the checklist above.

@codex review please verify TLA+ spec divergence per the checklist above.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Rebased the latest #1088 changes onto parent #1085 HEAD f624305.

Evidence:

  • latest HEAD: 0a1b9c8
  • git range-diff 87d5776d..refs/archive/pr1088-before-f624-rebase f624305e..HEAD: both PR commits are patch-identical
  • the only aggregate tree change from the parent update is the six-line received-snapshot peer fixture from store: add migration version import export #1085
  • focused staged-route and forwarded-timestamp adapter tests pass
  • go test ./... -run "^$" -count=1 passes
  • golangci-lint run ./adapter ./kv --timeout=5m --allow-parallel-runners: 0 issues
  • caller audit covered both scan-route filter call paths, all internal-key filtering paths, and the timestamp stamping/floor validation chain
  • both PR commits have author/committer bootjp <contact@bootjp.me> and valid signatures

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a1b9c890c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kv/sharded_coordinator.go
Comment on lines +1308 to +1310
rkey := routeKey(key)
if route, ok := c.engine.GetRoute(rkey); ok && route.MinWriteTSExclusive != 0 && commitTS <= route.MinWriteTSExclusive {
return errors.Wrapf(ErrRouteWriteTimestampTooLow, "key %q routeKey %q commit_ts=%d floor=%d", key, rkey, commitTS, route.MinWriteTSExclusive)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip resolver-owned keys in the floor precheck

For partition-resolved keyspaces such as HT-FIFO SQS, routeKey collapses concrete partition keys to the global SQS route; the write-fence precheck above deliberately bypasses resolver-recognized keys, and the Raft request carries WriteFenceBypassKeys so the FSM floor check skips them. This new timestamp-floor precheck does not, so when that global route has MinWriteTSExclusive during migration, legitimate writes to resolver-owned partition groups with commitTS <= floor are rejected before routing. Please mirror the partition-resolver/bypass handling here and in the forwarded stamping path.

Useful? React with 👍 / 👎.

Comment thread kv/shard_store.go
}

func shouldMarkRouteGroupOnScan(start []byte, explicitGroup bool, routeStart []byte, routeEnd []byte) bool {
return !explicitGroup && !routeScanBoundsPresent(routeStart, routeEnd) && isBroadLegacyListDeltaScan(start)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve route group on exact legacy list-delta scans

When the scan start is exactly store.LegacyListMetaDeltaScanPrefix(userKey), isBroadLegacyListDeltaScan returns false, so this condition stops marking scanned delta KVs with their owning RouteGroupID. Redis cleanup/compaction paths build delete elems from those scan results with GroupID: pair.RouteGroupID; leaving it zero makes legacy delta keys route as raw !lst|meta|d|... metadata keys instead of the logical list key, so after a split the deletes can be sent to the wrong shard and stale deltas remain. Please keep marking exact legacy delta scans as well.

Useful? React with 👍 / 👎.

Comment thread kv/fsm.go

startTS := r.Ts
uniq, err := uniqueMutations(muts)
uniq, err := f.uniqueMutationsAboveFloor(muts, r.GetWriteFenceBypassKeys(), startTS)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check the floor against the resolved commit timestamp

On the 2PC path, dispatchTxn resolves and preflights the transaction's commitTS before sending PREPARE, but the PREPARE request still has r.Ts == startTS. Passing startTS here makes a transaction that began before a migration floor but commits after it fail during PREPARE even though the later COMMIT/one-phase paths are checked against the actual commit timestamp; this aborts otherwise valid cross-shard transactions during or after cutover. Please skip the floor check for PREPARE or carry the resolved commit timestamp into this validation.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant