Skip to content

Complete standalone hotspot split automation - #1104

Open
bootjp wants to merge 11 commits into
mainfrom
design/hotspot-split-m3-auto-scheduler
Open

Complete standalone hotspot split automation#1104
bootjp wants to merge 11 commits into
mainfrom
design/hotspot-split-m3-auto-scheduler

Conversation

@bootjp

@bootjpbootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • complete standalone same-group hotspot split automation on top of detector PR Add autosplit detector core #1097
  • align committed KeyViz windows with per-label Top-K evidence and apply deterministic p50, Top-K, compound, cooldown, hysteresis, route-cap, and history-gap rules
  • gate scheduling by catalog-key and shard-group leadership, chain committed catalog versions, and reconcile sampler route membership on every node
  • add validated startup controls, authenticated Admin.SetAutoSplitEnabled runtime control, bounded metrics, and production/demo route-aware sampling
  • prove split, disable/re-enable, real leadership transfer, stale-history rejection, and confidence re-earn in a three-node etcd-Raft test
  • rename the M3 design to implemented with requirement evidence; leave cross-group target selection explicitly deferred to M3-PR4 after M2

Review roots addressed

  • r3607985785
  • r3608020346
  • r3608020348
  • r3608020351
  • r3608101599
  • r3608101602
  • r3608101604

Tests

  • make gen
  • golangci-lint run ./... --timeout=5m
  • go test -race -count=1 -timeout=20m ./kv/... ./distribution/...
  • go test -count=1 -timeout=10m ./distribution/autosplit ./keyviz ./kv
  • go test -count=1 -timeout=5m -run relevant Admin and DistributionServer SplitRange tests ./adapter
  • go test -count=1 -timeout=5m -run TestAutoSplitE2EThreeNodeSplitKillSwitchAndLeadershipReset .
  • GitHub Actions: build, proto, lint, TLA check, test, and test ubuntu-latest all pass on the current head

Summary by CodeRabbit

  • 新機能

    • ホットスポットを検知し、同一グループ内の範囲を自動分割する機能を追加しました。
    • 自動分割の有効・無効を運用中に切り替えられるようになりました。
    • 自動分割のスケジュール、リーダーシップ、クールダウンを考慮した安全な実行に対応しました。
    • 自動分割の候補、失敗、スキップ状況を監視できるメトリクスを追加しました。
    • ホットキーの収集と分割処理の連携を改善しました。
  • バグ修正

    • 古いスナップショットを適用した際の不要なエラーを解消しました。
    • カタログ更新通知の重複を防止しました。
  • ドキュメント

    • 自動ホットスポット分割の実装状況と運用範囲を更新しました。

@coderabbitai

coderabbitaiBot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@bootjp, you've reached your PR review limit, so we couldn't start this review.

Next review available in:26 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a641bc43-a57d-4fb1-8473-db1871ad5173

📥 Commits

Reviewing files that changed from the base of the PR and between e023ed4 and 3e848ca.

📒 Files selected for processing (27)
  • adapter/admin_grpc.go
  • adapter/distribution_server.go
  • adapter/distribution_server_test.go
  • adapter/internal.go
  • adapter/internal_test.go
  • cmd/server/demo.go
  • cmd/server/demo_test.go
  • distribution/autosplit/detector.go
  • distribution/autosplit/detector_test.go
  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/engine.go
  • distribution/watcher.go
  • distribution/watcher_test.go
  • docs/design/2026_02_18_partial_hotspot_shard_split.md
  • docs/design/2026_06_11_implemented_hotspot_split_milestone3_automation.md
  • docs/design/2026_06_12_proposed_scaling_roadmap.md
  • docs/design/2026_06_23_proposed_scaling_roadmap.md
  • keyviz/sampler.go
  • kv/coordinator.go
  • kv/coordinator_dispatch_test.go
  • kv/sharded_coordinator.go
  • kv/sharded_coordinator_sampler_test.go
  • kv/sharded_coordinator_txn_test.go
  • main.go
  • main_autosplit.go
  • main_autosplit_test.go
📝 Walkthrough

Walkthrough

自動分割の検出器・スケジューラ・ランタイム制御を追加し、KeyViz のホットキー観測、カタログ監視、分配サーバー、管理 gRPC と接続しました。Top-K 隔離、証拠フェンス、Prometheus 指標、E2E テスト、設計文書も更新されています。

Changes

Autosplit 検出とスケジューリング

Layer / File(s)Summary
検出器の拡張
distribution/autosplit/detector.go, distribution/autosplit/detector_test.go, distribution/autosplit/detector_rapid_test.go
履歴平滑化、Top-K 隔離、複合分割、リーダーシップ境界、拒否理由を追加し、テストを拡張しました。
スケジューラと観測
distribution/autosplit/scheduler.go, distribution/autosplit/metrics.go, distribution/autosplit/runtime_switch.go, distribution/autosplit/*_test.go
カタログ評価、分割実行、複合分割の保留・再試行、クールダウン、キルスイッチ、Prometheus 指標を追加しました。
KeyViz 連携
keyviz/*, kv/coordinator.go, kv/sharded_coordinator.go, kv/raft_engine.go
境界付きホットキー・サンプラーウィンドウを追加し、読み書き観測とリーダーシップ情報を提供します。
実行時配線
main.go, main_autosplit.go, cmd/server/demo.go, main_autosplit_e2e_test.go
CLI 設定、監視、ルート再整合、SplitRange 委譲、管理サービス登録、3 ノード E2E を追加しました。
管理 API とカタログ監視
proto/admin.proto, adapter/admin_grpc.go, distribution/watcher.go, adapter/distribution_server.go
自動分割切替 RPC、スナップショット通知、stale snapshot の抑制を追加しました。
テスト・設計更新
docs/design/*, main_bootstrap_e2e_test.go
設計状況を更新し、E2E テストの待機・終了処理を整理しました。

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • bootjp/elastickv#639: KeyViz の MemSampler、リングバッファ、フラッシャー基盤に直接関連します。
  • bootjp/elastickv#1097: autosplit 検出器コアと今回の検出器拡張に直接関連します。
  • bootjp/elastickv#1117: カタログ監視と SyncOnce の変更領域が重なります。

Sequence Diagram(s)

sequenceDiagram
participant AdminClient
participant AdminServer
participant RuntimeSwitch
participant Scheduler
AdminClient->>AdminServer: SetAutoSplitEnabled(enabled)
AdminServer->>RuntimeSwitch: SetEnabled(enabled)
AdminServer-->>AdminClient: Enabled()
Scheduler->>RuntimeSwitch: KillSwitch()
RuntimeSwitch-->>Scheduler: enabled state
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 16.47% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedタイトルはPRの主目的であるスタンドアロンのhotspot split自動化完了を正確に要約しています。
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

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.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

@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 (48d83f0):

  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/catalog.go
  • distribution/catalog_test.go
  • kv/coordinator.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.

@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 (8b22ccf):

  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/catalog.go
  • distribution/catalog_test.go
  • kv/coordinator.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

Copy link
Copy Markdown
OwnerAuthor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@gemini-code-assistgemini-code-assistBot 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 the standalone automatic hotspot range split scheduler (autosplit) for same-group splits, integrating it with the existing SplitRange and KeyViz sampler. It introduces a durable SplitAtHLC route lineage field to reconstruct cooldowns on leadership changes, updates the route catalog codec to v2 with backward compatibility, and adds a transaction commit timestamp patching mechanism (CommitTSValueOffset). A critical issue was identified in the scheduler's execution loop where sequential split decisions are executed with a stale catalog version, which will cause subsequent splits in the same cycle to fail due to optimistic concurrency control (OCC) mismatches. Updating the catalog version dynamically after each successful split is recommended to resolve this.

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 threaddistribution/autosplit/scheduler.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 (a18b63d):

  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/catalog.go
  • distribution/catalog_test.go
  • kv/coordinator.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

Copy link
Copy Markdown
OwnerAuthor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@bootjp

Copy link
Copy Markdown
OwnerAuthor

@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 (00e9a82):

  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/catalog.go
  • distribution/catalog_test.go
  • kv/coordinator.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-connectorchatgpt-codex-connectorBot 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:00e9a82fb8

ℹ️ 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 threadcmd/server/demo.go Outdated
Comment threaddistribution/autosplit/scheduler.go Outdated
Comment threadmain_autosplit.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 (4817c25):

  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/catalog.go
  • distribution/catalog_test.go
  • kv/coordinator.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.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

Updated the scheduler follow-up on latest head. Addressed current findings around demo sampling, sampler route descriptor re-registration, and catalog-key leadership gating. Local checks: go test ./distribution/autosplit -count=1; go test . -count=1; go test ./cmd/server -count=1; go test ./kv -count=1; go test ./... -run '^$'; go test ./... -timeout=20m; golangci-lint --config=.golangci.yaml run --fix; git diff --check.\n\n@codex review

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:4817c25fdb

ℹ️ 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 threadadapter/distribution_server.go
Comment threadcmd/server/demo.go Outdated
Comment threaddistribution/autosplit/scheduler.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 (9f3f8a9):

  • distribution/autosplit/detector.go
  • distribution/autosplit/detector_rapid_test.go
  • distribution/autosplit/detector_test.go
  • distribution/autosplit/metrics.go
  • distribution/autosplit/metrics_test.go
  • distribution/autosplit/runtime_switch.go
  • distribution/autosplit/runtime_switch_test.go
  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/catalog.go
  • distribution/catalog_test.go
  • distribution/watcher.go
  • distribution/watcher_test.go
  • kv/coordinator.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.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

Current-head evidence for 9f3f8a93c5995db8925b398ed9df1b9ef533aa17:

Requirement audit:

  • Exact committed (WindowStart, At] columns now carry aligned per-label Top-K evidence; the ring deep-copies it.
  • Detector covers per-label aggregation, normalized per-column plus smoothed scores, chronological hysteresis/reset, Top-K lower/error/degraded/absolute gates, p50 and compound/edge forms, structural splittability, route-delta cap, cooldown, and bounded state.
  • Scheduler is gated by catalog-key leadership and per-route shard-group leadership terms, fences stale/straddling/history-gap evidence, chains committed catalog versions, retries and revalidates compound finalization, and always uses same-group targets for standalone M3.
  • Catalog watcher reconciliation updates sampler membership on every node; demo traffic resolves the current route dynamically.
  • Startup flags are validated, Admin.SetAutoSplitEnabled atomically controls runtime scheduling, and metric names/reason labels are bounded.
  • main_autosplit_e2e_test.go proves a real three-node etcd-Raft split, runtime disable/re-enable, actual leadership transfer, stale-history rejection, and post-transfer confidence re-earn.
  • The design is renamed to 2026_06_11_implemented_hotspot_split_milestone3_automation.md with an implementation evidence matrix; M3-PR4 remains explicitly deferred post-M2.

Current-head fixes for review roots:

  • r3608020346: production/demo sampler writes use current route resolution.
  • r3608020348: descriptor changes re-register routes.
  • r3608020351: scheduler uses catalog-key leadership.
  • r3608101599: an engine-ahead stale apply no longer turns a committed split into an RPC failure.
  • r3608101602: demo no longer pins the startup route ID.
  • r3608101604: watcher reconciliation runs on followers as well as the catalog leader.
  • r3607985785: multi-split and compound calls chain the returned committed catalog version.

Verification:

  • make gen: pass.
  • golangci-lint run ./... --timeout=5m: 0 issues; signed commit hook lint also 0 issues.
  • go test -race -count=1 ./kv/... ./distribution/...: 646 tests pass.
  • go test -count=1 ./distribution/autosplit ./keyviz ./kv: 640 tests pass.
  • Focused admin and DistributionServer.SplitRange tests: pass.
  • Real three-node auto-split E2E: pass.
  • Full ./... attempt reached 5515 tests; the only failures were temporary disk exhaustion. Isolated adapter then reached 1625 tests with one unrelated Lua election flake, and that exact test passed on immediate isolated rerun.

Commit signature: Good EDDSA signature; author and committer are bootjp <contact@bootjp.me>.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

@codex review

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
OwnerAuthor

Superseded by the verified current-head evidence comment: #1104 (comment)

@bootjpbootjp changed the title Add autosplit p50 scheduler wiringComplete standalone hotspot split automationJul 18, 2026
@bootjp

Copy link
Copy Markdown
OwnerAuthor

@coderabbitai review

@coderabbitai

coderabbitaiBot commented Jul 18, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:9f3f8a93c5

ℹ️ 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 threadmain_autosplit.go Outdated
Comment threadkv/coordinator.go
Comment threadmain_autosplit.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 (3bd6a97):

  • distribution/autosplit/detector.go
  • distribution/autosplit/detector_rapid_test.go
  • distribution/autosplit/detector_test.go
  • distribution/autosplit/metrics.go
  • distribution/autosplit/metrics_test.go
  • distribution/autosplit/runtime_switch.go
  • distribution/autosplit/runtime_switch_test.go
  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/catalog.go
  • distribution/catalog_test.go
  • distribution/watcher.go
  • distribution/watcher_test.go
  • kv/coordinator.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.

@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 (9d4d857):

  • distribution/autosplit/detector.go
  • distribution/autosplit/detector_rapid_test.go
  • distribution/autosplit/detector_test.go
  • distribution/autosplit/metrics.go
  • distribution/autosplit/metrics_test.go
  • distribution/autosplit/runtime_switch.go
  • distribution/autosplit/runtime_switch_test.go
  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/watcher.go
  • distribution/watcher_test.go
  • kv/coordinator.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

Copy link
Copy Markdown
OwnerAuthor

Current HEAD: 9d4d85744887e254c917d5ad7b7b6c304b68a5a3

Resolved on this head:

  • rebased onto current origin/main and resolved conflicts
  • fixed disabled --autoSplit to return a true nil runtime interface
  • counted forwarded writes on the leader-side Internal.Forward path
  • normalized sampled keys before route resolution / sampler observation
  • enforced autosplit route cap against the full catalog live route count
  • copied the sharded coordinator group map on construction
  • kept Top-K snapshots aligned with matrix rows via the existing flushMu boundary and pinned it with the current flusher test

Validation:

  • go test . ./adapter ./distribution ./distribution/autosplit ./keyviz ./kv ./cmd/server -run "TestAutoSplit|TestSetupDistributionWatcherAndAutoSplitReturnsNilRuntimeWhenDisabled|TestInternalForwardObservesCommittedWrites|TestRouteCap|TestCoordinateSampler|TestNewShardedCoordinatorCopiesGroupMap|TestCatalogWatcher|TestSchedulerHistoryGapClearsPriorConfidence|TestSchedulerCompoundUsesNormalizedCommittedBoundary|TestEffectiveKeyViz"
  • go test . ./adapter ./distribution ./distribution/autosplit ./keyviz ./kv ./cmd/server -run "^$"
  • go test ./distribution/autosplit
  • go test ./distribution ./keyviz ./kv -run "TestCatalogWatcher|TestKeyViz|TestCoordinateSampler|TestNewShardedCoordinatorCopiesGroupMap"
  • golangci-lint run ./adapter ./distribution/... ./keyviz ./kv ./cmd/server --timeout=5m

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:9d4d857448

ℹ️ 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 threadmain.go
Comment threadkv/coordinator.go
Comment threadmain_autosplit.go Outdated
Comment on lines +162 to +163
if *autoSplitDefaultBuckets <= keyviz.DefaultKeyBucketsPerRoute {
return errors.New("--autoSplitDefaultBuckets must be greater than 1")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Allow explicit bucket configs to bypass unused defaults

When --autoSplit is enabled and the operator explicitly supplies --keyvizKeyBucketsPerRoute, --autoSplitDefaultBuckets is not used to build the sampler, but this validation still rejects the process if that unused default is set to 1 (or otherwise outside the allowed range). In that explicit-bucket configuration the node fails startup even though the effective sampler bucket count is valid, so the check should only apply when the auto-split default will actually be selected.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

Resolved in 2a886f2. The autoSplitDefaultBuckets range check now runs only when that implied default bucket count will actually be used; explicit keyvizKeyBucketsPerRoute configs bypass unused invalid defaults. The demo path was updated the same way.

Comment threaddistribution/autosplit/scheduler.go Outdated
Comment on lines +616 to +621
snapshot.Version = result.CatalogVersion
refreshed, refreshErr := s.source.Snapshot(ctx)
if refreshErr != nil {
s.cfg.Logger.WarnContext(ctx, "autosplit: refresh after compound finalization failed",
slog.Any("err", refreshErr))
break

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 Stop evaluating after failed compound refresh

When the catalog read transiently fails immediately after a pending compound finalization commits, this advances only snapshot.Version and then breaks with snapshot.Routes still describing the pre-finalization intermediate route. The rest of the tick reconciles/evaluates that mixed-version snapshot; since the distribution engine has already applied the committed children, observations resolve to the new child RouteIDs while the sampler has just been reconciled back to the old route, so writes to the newly-created ranges are dropped until another successful catalog refresh, and any spare cycle budget can be spent on stale route decisions.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

Resolved in 2a886f2. After a pending compound finalization commits, a catalog refresh failure now stops the tick before sampler reconciliation, detector evaluation, or additional split scheduling can run against a mixed-version snapshot. Covered by TestSchedulerStopsTickAfterCompoundRefreshFailure.

@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 (2a886f2):

  • distribution/autosplit/detector.go
  • distribution/autosplit/detector_rapid_test.go
  • distribution/autosplit/detector_test.go
  • distribution/autosplit/metrics.go
  • distribution/autosplit/metrics_test.go
  • distribution/autosplit/runtime_switch.go
  • distribution/autosplit/runtime_switch_test.go
  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/watcher.go
  • distribution/watcher_test.go
  • kv/coordinator.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.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

Current HEAD: 2a886f2050119b89ee9d8cc44c789719b47dbd32

Resolved on this head:

  • Registered the sharded forwarded-write observer for the production sharded coordinator path.
  • Skipped transaction metadata mutations during forwarded-write sampling.
  • Allowed explicit KeyViz bucket configuration to bypass an unused invalid autoSplitDefaultBuckets default, including the demo path.
  • Stopped autosplit ticks after compound-finalization refresh failure before mixed-version reconciliation or evaluation.

Validation:

  • go test . ./cmd/server ./adapter ./kv ./distribution/autosplit -run 'TestInternalForwardObservesCommittedWrites|TestValidateAutoSplitSamplerConfigAllowsExplicitBucketsWithUnusedInvalidDefault|TestValidateDemoAutoSplitSamplerConfigAllowsExplicitBucketsWithUnusedInvalidDefault|TestCoordinateObserveForwardedRequestsSkipsTxnMetadata|TestShardedCoordinatorObservesForwardedRequests|TestSchedulerStopsTickAfterCompoundRefreshFailure|TestCoordinateSamplerResolvesRouteForEveryObservedKey|TestShardedCoordinatorObservesEveryDispatchedMutation|TestSchedulerRetriesPendingCompoundWithoutNewSamplerColumn'
  • go test . ./cmd/server ./kv ./distribution/autosplit -run '^$'
  • golangci-lint run . ./cmd/server ./adapter ./distribution/... ./keyviz ./kv --timeout=5m
  • go test ./cmd/server ./kv ./distribution/autosplit
  • git diff --check

@codex review

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:2a886f2050

ℹ️ 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".

pending.intermediate = intermediate
s.pendingCompounds[parentID] = pending
attempted++
result, err := s.executePendingCompound(ctx, snapshot.Version, pending)

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 Recheck shard leadership before finalizing compounds

When a compound split is pending and this node loses leadership for pending.intermediate.GroupID before the next tick, this path finalizes the second split before routesLedLocally has a chance to poll GroupLeadership and drop the pending entry. SplitRange itself only verifies catalog-key leadership, so a catalog leader can still mutate the catalog for a shard it no longer leads, using evidence from the prior shard leader term. Please gate pending finalization on current group leadership or run it after the group-leadership reconciliation.

Useful? React with 👍 / 👎.

Comment on lines +208 to +209
if snapshot.Version != engineVersion {
return 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 Notify observers after partial delta batches

When a node catches up across more deltas than one watcher batch, applyCatalogDeltas advances the engine to the batch's last version, but notifyLatestSnapshotObserver loads the latest catalog snapshot and returns here because that version is still ahead of the engine. The route reconciler therefore is not notified about the route table the engine is actually serving until all batches are applied, so writes routed to newly-applied route IDs can be dropped from KeyViz/autosplit during catch-up. Notify with a snapshot at the applied engine version instead of skipping partial batches.

Useful? React with 👍 / 👎.

Comment on lines +2225 to +2227
for _, mut := range req.Mutations {
if mut == nil || isTxnMetaKey(mut.Key) {
continue

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 abort cleanups in forwarded write sampling

When a forwarded transaction cleanup reaches the leader as a Phase_ABORT request, the mutation list still contains the user keys after the txn metadata so the FSM can clear their intents; this loop only skips the metadata and records the remaining keys as committed writes. A follower-routed transaction that aborts can therefore create hot-key evidence for keys whose writes were rolled back, and the autosplit scheduler can split on cleanup traffic. Skip abort-phase requests, or otherwise restrict this observer to phases that represent user writes.

Useful? React with 👍 / 👎.

Comment threadcmd/server/demo.go
runtime.reconciler.Reconcile(initialSnapshot.Routes)
runtime.autoSplitCfg.Reconciler = runtime.reconciler
}
coordinator.WithSamplerRouteResolver(runtime.sampler, demoSamplerRouteResolver(runtime.engine))

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 Forward demo writes into the leader sampler

When demo auto-split is enabled and clients write through a follower, this only wires the sampler into that follower's Coordinate; the demo Internal server is still constructed without WithInternalForwardWriteObserver. Since the demo scheduler runs only on the catalog/Raft leader and reads that node's local sampler, follower-ingressed writes never influence automatic splits unless clients happen to hit the current leader. Register coordinator.ObserveForwardedRequests with the demo Internal server as the production path does.

Useful? React with 👍 / 👎.

Comment threadkv/sharded_coordinator.go Outdated
if c == nil || c.sampler == nil || mut == nil {
return
}
c.sampler.Observe(routeID, mut.Key, keyviz.OpWrite, len(mut.Value), c.keyVizObserveLabel(label))

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 Normalize sampled sharded keys before bucketing

For adapter-backed traffic whose storage keys normalize for routing, this records the raw mutation key even though routeID was found with routeKey(mut.Key) and the sampler slots were registered with catalog route boundaries in the normalized keyspace. With Redis, for example, user key z is observed as !redis|str|z, so after a split at m it sorts before the route start and all writes clamp into the first sub-bucket, causing autosplit to choose boundaries near m instead of the hot user key. Pass the normalized route key to the sampler for sharded observations so the evidence and route descriptors use the same ordering.

Useful? React with 👍 / 👎.

Two problems in the leader-side sampling of follower-forwarded writes.
observeMutation passed the raw storage key to the sampler even though
routeID came from routeKey and the sampler's sub-buckets are laid out on
catalog route boundaries, which live in the normalized keyspace. A Redis
user key "z" arrives as "!redis|str|z", which sorts before a route
starting at "m", so every such write clamped into the first sub-bucket
and autosplit picked a boundary near the route start rather than the hot
user key. Sample the route key instead. The single-group path already did
this through samplerConfig.routeForKey, so this aligns the sharded path
with the established convention.
ObserveForwardedRequests also counted ABORT cleanups. An abort still
lists the user keys after the txn metadata so the FSM can clear their
intents, but those writes were rolled back, so a follower-routed
transaction that aborts could manufacture hot-key evidence and get the
scheduler to split on cleanup traffic. Skip abort-phase requests in both
the sharded and single-group observers.
Claude-Session: https://claude.ai/code/session_013rNHooj7NF3giihWVba8QE
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

Codex P2 2 件 — kv/sharded_coordinator.go:2211 (Normalize sampled sharded keys before bucketing) と :2227 (Skip abort cleanups in forwarded write sampling) に対応しました(d4d6a55e)。

1. サンプリングキーの正規化 (:2211)

observeMutationrouteIDrouteKey(mut.Key) から解決しているのに、サンプラーには生のストレージキーを渡していました。サンプラーの sub-bucket はカタログのルート境界(=正規化済みキー空間)で構築されるため、ご指摘のとおり Redis のユーザキー z!redis|str|z として観測され、m 始まりのルートより手前にソートされて全書き込みが先頭 sub-bucket にクランプされます。結果、autosplit の境界がホットなユーザキーではなくルート先頭付近に寄ります。

c.sampler.Observe(routeID, routeKey(mut.Key), ...) に修正しました。

補強材料: 単一グループ側の samplerConfig.routeForKey (kv/coordinator.go:1292-1299) は既に sampleKey := RouteKey(key) として正規化済みキーを返しています。つまり正規化がこのコードベースの既定の規約で、sharded 側だけが外れていました。今回の修正はその規約に揃える形です。

2. abort クリーンアップの除外 (:2227)

ObserveForwardedRequestsPhase を見ていませんでした。ABORT は intent 解消のために txn メタデータの後ろにユーザキーを並べるので、ロールバックされた書き込みがホットキーの evidence として計上され、follower 経由で abort したトランザクションが cleanup トラフィックで split を誘発し得ます。forwardedRequestRecordsUserWrites を追加して ABORT フェーズをスキップします。

同一欠陥が単一グループ側にもありましたCoordinate.ObserveForwardedRequests (kv/coordinator.go:1240) も Phase を見ていなかったため、同じヘルパーで揃えています(指摘は sharded 側の行でしたが、同じクラスの不具合なので両方直しました)。

Caller audit

  • ShardedCoordinator.observeMutation の呼び出し元は groupMutations (:2305) と ObserveForwardedRequests (:2240) の 2 箇所。どちらも routeIDrouteKey 経由で解決しているので、正規化キーを渡す方が一貫します。
  • ObserveForwardedRequests の本番呼び出し元は main.go:2850 の 1 箇所(インターフェース経由)。sharded / 単一グループ両方の実装を修正済み。
  • Coordinate.observeMutation は既に正規化済みのため未変更です。
  • keyviz のホットキー出力が生キーから正規化キーに変わります。これは意図した変更で、evidence から導かれる split key はルーティングキー空間になければ意味がないためです。

Test evidence

  • 追加 TestShardedCoordinatorSkipsAbortCleanupInForwardedSampling — ABORT では evidence が出ず、同じ経路の COMMIT では出ることを検証。
  • 追加 TestShardedCoordinatorSamplesNormalizedRouteKey!redis|str|zRouteKey 適用後のキーで観測され、生キーとは異なることを検証。
  • 修正前に両方が失敗することを確認済み:
    --- FAIL: TestShardedCoordinatorSkipsAbortCleanupInForwardedSampling
    Should be empty, but was [{100 1 [114 111 108 108 101 100 45 98 97 99 107] 11 0}] ("rolled-back" が計上されていた)
    --- FAIL: TestShardedCoordinatorSamplesNormalizedRouteKey
    Not equal
    
  • 既存 TestShardedCoordinatorObservesForwardedRequests は素のキー(正規化しても不変)を使っているためそのまま通ります。
  • go test -race ./kv/ ./keyviz/ ./distribution/ → ok (13.1s / 1.3s / 1.2s)
  • golangci-lint run kv/... keyviz/... → 0 issues

Self-review

  1. データ損失: なし。観測経路のみ。
  2. 並行性 / 分散障害: なし。Observe の呼び出し形は不変。
  3. パフォーマンス: routeKey 呼び出しが 1 回増えますが、routeAndGroupForKey が既に同じ正規化を実行している経路であり、サンプラー無効時は observeMutation の早期 return で従来どおりゼロコストです。
  4. データ整合性: 本指摘の主目的。evidence と route descriptor が同じ順序空間に揃い、ロールバック済み書き込みが evidence に混ざらなくなります。
  5. テスト網羅: 新規 2 分岐(ABORT スキップ / キー正規化)を回帰テストで固定し、修正前の失敗も確認。

残りの P2 3 件(distribution/autosplit/scheduler.go:615distribution/watcher.go:209cmd/server/demo.go:596)は次の tick で対応します。

@claude review

@claude

claudeBot commented Aug 22, 2026

Copy link
Copy Markdown

Claude finished @bootjp's task in 2m 39s —— View job


Review of d4d6a55e — fixes for the 2 Codex P2 findings

  • Read trigger comment and gather context
  • Inspect kv/sharded_coordinator.go normalization fix (observeMutation)
  • Inspect ABORT-phase skip fix in ObserveForwardedRequests (sharded + single-group)
  • Check the new regression tests
  • Verify RouteKey/routeKey consistency and caller audit claims
  • Post review feedback

Both fixes are correct and consistent with the rest of the codebase.

1. Normalized sampling key (kv/sharded_coordinator.go:2216)

Traced routeAndGroupForKey (kv/sharded_coordinator.go:1990-2002): routeID is resolved via c.engine.GetRoute(routeKey(key)), so passing routeKey(mut.Key) into observeMutation/Observe now uses the same normalized keyspace the route was looked up in — that's the right fix, and it matches the single-group convention (kv/coordinator.go:1296, RouteKey(key) in routeForKey) exactly as claimed. normalizeRouteKey (kv/shard_key.go:68) falls through to the original key unchanged when no adapter prefix matches, so plain RawKV traffic sees zero behavior change — only prefixed adapter keys (Redis/Dynamo/SQS/S3/fs/list) are affected.

One thing worth flagging (not a blocker, pre-existing from the single-group path, not introduced by this commit): dynamoRouteKey/sqsRouteKey collapse every item in a table (or all SQS traffic) to one table-level/global route key (kv/shard_key.go:102-133, :142-150). That's fine for sub-bucket range assignment (matches route boundaries), but it means the Top-K hot-key tracking (observeHotKey, keyviz/sampler.go:704) can no longer distinguish which Dynamo item or SQS message is hot — they all report as the same collapsed key. Since this was already true before this PR for the single-group path, it's a known limitation rather than a regression, but it does mean hot-key isolation (this PR's headline feature) is effectively Redis-only in practice today. Worth a follow-up note in the M3 doc if not already there, no action needed for this PR.

2. Skip ABORT-phase forwards (kv/sharded_coordinator.go:2244, kv/coordinator.go:1245)

Confirmed via adapter/internal.go:76-86: Internal.Forward calls forwardWriteObserver unconditionally after transactionManager.Commit succeeds, for any phase — including Phase_ABORT, whose mutation list carries the rolled-back user keys so the FSM can clear intents (kv/lock_resolver.go:415, kv/shard_store.go:1591). So the bug was real: a follower-routed abort previously fed rolled-back writes into the sampler as legitimate hot-key evidence. forwardedRequestRecordsUserWrites correctly gates on req.GetPhase() != pb.Phase_ABORT, and Phase_NONE (raw, non-txn forwards) is unaffected since it's not ABORT. Applied identically to both ShardedCoordinator.ObserveForwardedRequests and Coordinate.ObserveForwardedRequests — good, since both had the same defect class.

Tests

TestShardedCoordinatorSkipsAbortCleanupInForwardedSampling and TestShardedCoordinatorSamplesNormalizedRouteKey (kv/sharded_coordinator_sampler_test.go) both look correctly targeted: the first asserts zero evidence from an ABORT with a real committed control case in the same test, the second asserts the sampled key differs from the raw key and equals RouteKey(raw). Per the write-up these failed pre-fix, which is the right TDD ordering the repo's CLAUDE.md asks for. I could not independently re-run go test/go build in this session (the sandbox blocked those commands even with dangerouslyDisableSandbox), so I've verified this by static tracing of the call graph rather than execution — worth a go test -race ./kv/... confirmation if that hasn't already run in CI.

Minor: forwardedRequestRecordsUserWrites takes *pb.Request and calls .GetPhase(), which is nil-safe, but the caller already null-checks req == nil first in both sites so this is fine either way — no change needed.

No other issues found in this diff. The remaining 3 P2s (distribution/autosplit/scheduler.go:615 group-leadership recheck, distribution/watcher.go:209 partial-batch notify, cmd/server/demo.go:596 demo forward-write observer) are explicitly deferred to a later commit per the trigger comment — not evaluated here since they're unchanged.

Three review findings.
The compound finalization loop ran before routesLedLocally had a chance
to reconcile group leadership, so a node that lost leadership for the
intermediate route's group since the last tick could still finalize the
second split. SplitRange only verifies catalog-key leadership, so a
catalog leader could mutate the catalog for a shard it no longer leads
using evidence from the prior shard leader term. Recheck leadership on
each pending entry, and reject a changed term so a lose-and-regain
between ticks does not carry the old term's evidence forward.
notifyLatestSnapshotObserver returned without notifying whenever the
persisted catalog was ahead of the engine, which is exactly the state
during a multi-batch catch-up. The reconciler therefore never learned
about the route table the engine was actually serving until every batch
applied, so writes to newly applied route IDs went unattributed in
KeyViz and autosplit for the whole catch-up. Notify with the engine's
applied routes instead, via the new Engine.AppliedCatalogSnapshot.
The demo Internal server was built without a forward-write observer, so
follower-ingressed writes never reached the leader-local sampler the demo
scheduler reads. Wire coordinator.ObserveForwardedRequests as
main.internalTimestampOptions already does.
Claude-Session: https://claude.ai/code/session_013rNHooj7NF3giihWVba8QE
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

残る Codex P2 3 件に対応しました(586231cc)。これで本 PR の未解決指摘 5 件はすべて対応済みです。

1. distribution/autosplit/scheduler.go:615 — Recheck shard leadership before finalizing compounds

ご指摘のとおり、compound の finalize ループは routesLedLocally(group leadership を照合して pending を落とす)より先に走るため、前回 tick 以降に intermediate ルートのグループのリーダーを失っていても 2 段目の split を確定できていました。SplitRange はカタログキーのリーダーシップしか検証しないため、カタログリーダーが「もう率いていない shard」のカタログを、前 shard leader term の evidence で書き換え得ます。

leadsPendingCompoundGroup を追加し、pending ごとに finalize 直前へ leadership 再確認を入れました。term も比較しています(tick 間に「失って再取得」した場合、pending は前の term で記録されたものなので evidence が現 term に属しません)。あわせて finalizePendingCompounds の cyclop 超過を避けるため検証部を resolvePendingCompound に切り出しています。

2. distribution/watcher.go:209 — Notify observers after partial delta batches

notifyLatestSnapshotObserver は「永続カタログが engine より先行している」場合に必ず return していましたが、それはまさに複数バッチのキャッチアップ中の状態です。結果、全バッチ適用が終わるまで reconciler は engine が実際に配っているルートテーブルを知らず、新規適用された route ID 宛の書き込みが KeyViz / autosplit で取りこぼされていました。

Engine.AppliedCatalogSnapshot() を追加し(engine が実際に serving しているルート集合 + その適用バージョン)、部分バッチ時はそれで通知します。engineVersion > w.observedVersion を条件にして重複通知は避けています。ReadTS はカタログの時点読みではないためゼロのままにしてあります。

3. cmd/server/demo.go:596 — Forward demo writes into the leader sampler

demo の Internal サーバは forward-write observer なしで構築されていました。demo スケジューラはカタログ/Raft リーダー上でのみ動き、そのノードのローカルサンプラーを読むため、follower 経由で入った書き込みは自動 split に一切influence しませんでした。本番の main.internalTimestampOptions と同じく adapter.WithInternalForwardWriteObserver(coordinator.ObserveForwardedRequests) を配線しました。

Caller audit

  • leadsPendingCompoundGroup / resolvePendingCompound は新規で、呼び出し元は finalizePendingCompounds のみ。GroupLeadership 未設定時は従来どおり通す(テスト・非シャード構成の互換)。
  • Engine.AppliedCatalogSnapshot は新規で、呼び出し元は notifyLatestSnapshotObserver のみ。Engine の既存メソッドは未変更。
  • notifySnapshotObserver は不変。observedVersion の更新も従来どおり同関数内で行われます。
  • CatalogSnapshotObserver の本番配線は main.go:3098 の 1 箇所で、autosplit.RouteReconciler.ReconcileRouteID / Start / End / GroupID しか参照しないため、AppliedCatalogSnapshotParentRouteID / SplitAtHLC を埋めなくても影響ありません。

Test evidence

  • 追加 TestSchedulerSkipsCompoundFinalizationWithoutShardLeadership / TestSchedulerSkipsCompoundFinalizationAfterShardTermChange
  • 追加 TestCatalogWatcherNotifiesAfterPartialDeltaBatchWithCatalogWatcherBatchSize(1) で engine をカタログより遅らせ、engine のバージョンで通知されることを検証)
  • 3 件とも修正前に失敗することを確認済み:
    --- FAIL: TestSchedulerSkipsCompoundFinalizationWithoutShardLeadership
    Should be empty, but was [{7 10 [109 0] 0 [109] [122] 1}] (split が実際に発行されていた)
    --- FAIL: TestSchedulerSkipsCompoundFinalizationAfterShardTermChange
    Should be empty, but was [{7 10 [109 0] 0 [109] [122] 1}]
    --- FAIL: TestCatalogWatcherNotifiesAfterPartialDeltaBatch
    Should NOT be empty, but was [] (通知が一切来ていなかった)
    
  • go test -race ./distribution/... ./kv/ ./keyviz/ → ok
  • golangci-lint run distribution/... kv/... cmd/server/... → 0 issues

Self-review

  1. データ損失: なし。3 件とも制御プレーン/観測経路のみ。
  2. 並行性 / 分散障害: 1 が本丸。リーダーシップを失った(あるいは term が変わった)ノードが shard のカタログを書き換える経路を塞ぎ、fail-closed 側に倒しています。
  3. パフォーマンス: 2 でキャッチアップ中の通知が増えますが、observedVersion ガードでバージョンあたり 1 回に制限しています。1 は pending ごとに関数呼び出し 1 回の追加のみ。
  4. データ整合性: 2 により evidence とルート記述子の同期がキャッチアップ中も保たれます。
  5. テスト網羅: 3 分岐すべてに回帰テストを追加し、修正前の失敗も確認済み。

@claude review

@claude

claudeBot commented Aug 22, 2026

Copy link
Copy Markdown

Claude finished @bootjp's task in 2m 22s —— View job


Review of 586231cc — three remaining scheduler evidence gaps

  • Read trigger comment and gather context
  • Inspect distribution/autosplit/scheduler.go compound-finalization leadership recheck fix
  • Inspect distribution/watcher.go partial-batch notify fix
  • Inspect cmd/server/demo.go forwarded-write observer wiring
  • Check new regression tests
  • Post review feedback

All three fixes are correct and match their write-up.

1. Recheck shard leadership before finalizing compounds (distribution/autosplit/scheduler.go:582-689)

Traced the tick ordering: prepareTick calls finalizePendingCompoundsbeforesyncCatalogSnapshotroutesLedLocally runs for that same tick (scheduler.go:286-297). So a pending compound created in tick N-1's finishTick survives into tick N's finalizePendingCompounds, which now runs before this tick's leadership reconciliation — that's exactly the window the fix closes. leadsPendingCompoundGroup does a live call to s.cfg.GroupLeadership(groupID) (not the cached map) and only uses s.groupLeadership[groupID] (populated by the previous tick's routesLedLocally) for the term-drift comparison, which is the right source for both values — one fresh, one prior.

The term-change guard is correctly scoped: known && previous.leader && term != 0 && previous.term != 0 && term != previous.term. Since locallyLedGroupState (scheduler.go:853-887) always calls dropPendingCompoundsForGroup on any transition (loss or term change) by the end of a tick, a pending entry can only reach leadsPendingCompoundGroup in tick N holding a term that was current as of the end of tick N-1 — so a fresh mismatch here can only mean leadership flipped between ticks, which is precisely the gap described. resolvePendingCompound was correctly extracted to keep finalizePendingCompounds under the repo's cyclop budget.

One latent (pre-existing, not introduced by this commit) edge case: leadsPendingCompoundGroup checks leadership for pending.intermediate.GroupID as captured when the pending entry was created, not the live route's current group. If a route's group could change between compound stages this would check the wrong group's leadership. Since M3 standalone is same-group-only (cross-group TargetGroupID is explicitly deferred to M3-PR4 per the design doc), routes shouldn't change group mid-compound today — not a blocker, just worth a note if cross-group support lands later.

2. Notify observers after partial delta batches (distribution/watcher.go:196-227, distribution/engine.go:419-441)

Confirmed RouteReconciler.Reconcile (scheduler.go:772-798) only reads RouteID/Start/End/GroupID from a RouteDescriptor, so AppliedCatalogSnapshot() leaving ParentRouteID/SplitAtHLC zero and CatalogSnapshot.ReadTS zero is safe for its one consumer. The duplicate-notification guard is sound: notifySnapshotObserver sets w.observedVersion = snapshot.Version, and since AppliedCatalogSnapshot().Version == e.catalogVersion == engineVersion, the next poll's engineVersion > w.observedVersion check correctly suppresses a re-notify until the engine actually advances further. Verified the only other caller of CatalogSnapshotObserver (main.go:3094) doesn't depend on the omitted fields either.

3. Forward demo writes into the leader sampler (cmd/server/demo.go:643-651)

Confirmed WithInternalForwardWriteObserver (adapter/internal.go:26) and Coordinate.ObserveForwardedRequests (kv/coordinator.go:1240) both exist and this now mirrors main.internalTimestampOptions's production wiring exactly.

Tests

All three new/changed tests target the actual gaps described:

  • TestSchedulerSkipsCompoundFinalizationWithoutShardLeadership / TestSchedulerSkipsCompoundFinalizationAfterShardTermChange — correctly seed scheduler.groupLeadership and pendingCompounds directly to simulate the cross-tick race without needing a real multi-node cluster.
  • TestCatalogWatcherNotifiesAfterPartialDeltaBatch — uses WithCatalogWatcherBatchSize(1) to force multi-batch catch-up and asserts the observed version matches the engine's, not the catalog's.

I could not execute go build/go test/golangci-lint in this sandbox (commands requiring env-var prefixes or dangerouslyDisableSandbox were blocked from approval), so this review is based on static tracing of the diff and call graph rather than execution — the PR's own reported local runs (go test -race ./distribution/... ./kv/ ./keyviz/, golangci-lint run distribution/... kv/... cmd/server/...) should be treated as the actual verification for this commit.

No other issues found in this diff.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

CI について: test (ubuntu-latest) が 1 度失敗しましたが、既存の flaky テストと判断し再実行しました(現在実行中)。

失敗内容:

--- FAIL: TestRedis_DelList_RemovesLargeListAndInternalKeys (2.19s)
adapter/redis_multi_test.go:433: Received unexpected error: NOTLEADER leader not found

本 PR の変更とは無関係と判断した根拠:

  1. この差分がこのテストに到達しませんcreateNodeadapter/test_util.go:202)で構築されるテスト用ノードには sampler・ObserveForwardedRequests・CatalogWatcher・autosplit のいずれも配線されていません。したがって

    • observeMutationc.sampler == nil で早期 return するため、キー正規化の変更は到達不能
    • ObserveForwardedRequests は呼ばれないため、ABORT スキップも到達不能
    • autosplit scheduler / catalog watcher は動作せず、cmd/server/demo.go も関与しません
  2. ローカルで 3/3 成功go test -race -count=1 -run TestRedis_DelList_RemovesLargeListAndInternalKeys ./adapter/、本 PR HEAD 586231cc 上)。

  3. 同種の flaky が他ブランチでも発生しています(いずれもリーダー選出・タイミング依存):

runブランチ失敗テスト
30988971914mainTestStartKeyVizFlusherReturnsAfterCancel
32535157759renovate/aws-sdk-go-v2-monorepo(依存更新のみ)TestLeaderAwareRedisBackend_InitialCommandWaitsForLeaderDiscovery
32521589024design/hotspot-split-m2-wireTestLeaderAwareRedisBackend_RefreshOutlivesCallerDeadline

依存バージョンを上げただけの renovate ブランチや main でも同様に落ちているため、コード変更に起因するものではなく、-race かつ負荷のかかった runner 上での Raft 選出タイミングに起因する既存の不安定性と考えられます。

再実行の結果が緑にならなければ、改めて調査します。

Only the two hotspot-split design docs conflicted; no code conflicts.
main still described M3 automation as partial and referenced the doc
under its _partial_ name. This branch is the one that completes M3 and
renamed that doc to _implemented_, so the implemented status and the
renamed path win. main's M2 SplitJob-catalog bullet is new here and had
no counterpart on this side, so it is kept alongside the M3 bullet.
Claude-Session: https://claude.ai/code/session_013rNHooj7NF3giihWVba8QE
@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 (3e848ca):

  • distribution/autosplit/detector.go
  • distribution/autosplit/detector_rapid_test.go
  • distribution/autosplit/detector_test.go
  • distribution/autosplit/metrics.go
  • distribution/autosplit/metrics_test.go
  • distribution/autosplit/runtime_switch.go
  • distribution/autosplit/runtime_switch_test.go
  • distribution/autosplit/scheduler.go
  • distribution/autosplit/scheduler_test.go
  • distribution/engine.go
  • distribution/watcher.go
  • distribution/watcher_test.go
  • kv/coordinator.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.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

新規の指摘・CI 失敗は無かったため、マージを塞いでいた main との CONFLICTING を解消しました(3e848ca0)。

競合の範囲

main の 123 commits を取り込みましたが、競合したのは hotspot-split の設計ドキュメント 2 本のみで、コードの競合はゼロでした。

  • docs/design/2026_02_18_partial_hotspot_shard_split.md
  • docs/design/2026_06_11_implemented_hotspot_split_milestone3_automation.mdmain 側では _partial_ 名のまま。rename 競合)

解決の方針

M3 のステータスは本ブランチ側(implemented)を採用しました。main はまだ M3 を partial と記述し _partial_ 名でリンクしていますが、M3 automation を完成させているのがこの PR そのものなので、implemented 記述とリネーム後のパスが正です。docs/ 配下に partial_hotspot_split_milestone3 への参照が残っていないことも確認済みです。

片側だけ捨てる解決はしていません。main 側には本ブランチに存在しない M2 SplitJob catalog substrate の項目が追加されていたので、これは M3 の項目と併記して残しました。ステータス日付は取り込み時点に更新しています。

検証(main 側 123 commits を取り込んでいるため、ドキュメント変更だけでは済ませていません)

  • go build ./... → OK
  • go test -race ./distribution/... ./kv/ → ok
  • go test -race ./store/ . → ok(. は 66s)

merge state は DIRTY → BLOCKED(=競合解消済み、CI 実行待ち)になりました。

なお #1128 / #1084 / #1058 / #1056 も CONFLICTING ですが、そちらは proto/service.pb.go(生成物)・main.go・adapter 実装を含む重い競合で、#1104 のようなドキュメントのみの解決とは別物です。勝手に進めず、進め方の指示を待ちます(特に #1084.codex/worktrees/pr1084-m2-wire に中断されたマージが残っています)。

@claude review

@claude

claudeBot commented Aug 22, 2026

Copy link
Copy Markdown

Claude encountered an error after 7s —— View job


I'll analyze this and get back to you.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:3e848ca0c6

ℹ️ 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 on lines +2253 to +2254
func forwardedRequestRecordsUserWrites(req *pb.Request) bool {
return req.GetPhase() != pb.Phase_ABORT

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 Count forwarded transactional writes only once

For a follower-routed multi-shard transaction, prewriteTxn forwards a Phase_PREPARE request containing every user mutation and the commit path later forwards a Phase_COMMIT request containing those keys again. Because this predicate accepts both phases, Internal.Forward invokes this observer after each successful phase and the shard leader samples every committed user key twice; it can therefore cross the autosplit threshold at half the configured traffic rate. Restrict transactional sampling to one phase or otherwise deduplicate the transaction's keys.

Useful? React with 👍 / 👎.

if !ok {
continue
}
c.observeMutation(routeID, mut, keyviz.LabelLegacy)

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 labels for forwarded write samples

When KeyViz labels are enabled and an adapter write enters through a follower, the leader-side observer unconditionally records it as LabelLegacy, even though the ingress OperationGroup was labeled Redis, DynamoDB, S3, and so on. Since the forwarded protobuf currently carries no label, the leader's label-specific KeyViz data misattributes all follower-routed traffic; propagate the label with the forwarded request or preserve it through another forwarding-side mechanism.

Useful? React with 👍 / 👎.

@bootjp

Copy link
Copy Markdown
OwnerAuthor

CI 失敗 test (ubuntu-latest) について — 本 PR の変更に起因しない既知の flake と判断し、再実行しました

失敗は TestLuaNegativeTypeCache_BoundedSize の 1 本のみ:

--- FAIL: TestLuaNegativeTypeCache_BoundedSize (156.23s)
redis_lua_negative_type_cache_test.go:166:
Received unexpected error: leader not found
| kv.(*LeaderRoutedStore).ExistsAt kv/leader_routed_store.go:227

無関係と判断した根拠

  1. 失敗の性質: leader not found は 3 ノードテストハーネスの Raft leader election が間に合わなかったというタイミング障害です。アサーション内容の不一致ではありません。
  2. 所要時間: CI で 156.23s(ローカルでは 38s)。runner の競合でハーネス起動が遅延したことを示しています。
  3. コード経路が交差しない: 本 PR の変更点は sampler / autosplit / watcher です。adaptercreateNode ハーネスは sampler を一切 wire しませんShardedCoordinator の sampler 経路は c.sampler == nil で早期 return する nil ガード付きなので(kv/sharded_coordinator.go:2209, 2275)、このテストでは本 PR のコードは実質的に走りません。
  4. main 自体でも同種の失敗が出ています: run 32570519909main @ d9a031d0)で TestRedis_DelList_RemovesLargeListAndInternalKeys が同じ leader election 起因で FAIL しています。本 PR 固有ではありません。
  5. ローカル再現なし: マージ後のツリーで -count=1 × 3 回 → 3/3 pass(38.1s / 38.0s / 41.5s)。

以上より本 PR の regression ではないと判断し、失敗ジョブを再実行しました。testCOMPLETED/SUCCESStest (ubuntu-latest) は再実行中です。

なお、この leader-election flake は #1084 / #1104 / main にまたがって繰り返し観測されています。テストハーネス側で leader 確立を待つリトライを入れる修正を別 PR で出せますが、この PR のスコープ外なので勝手には触っていません。必要であれば指示ください。

@bootjp

Copy link
Copy Markdown
OwnerAuthor

再実行後、別のテストが落ちたので追記します。こちらも本 PR とは無関係と判断し、再度再実行しました。

--- FAIL: TestGRPCS3BlobClusterAuthenticatedCapabilityPushAndFetch (0.02s)
s3_blob_m1_test.go:595: expected: 0x10 actual: 0x2

s3_blob_m1_test.go:595require.Equal(t, codes.Unauthenticated, status.Code(err)) です。Unauthenticated = 16 (0x10) を期待して Unknown = 2 (0x2) が返っています。負荷下で RPC が サーバ側の認証インターセプタに到達する前にトランスポート層で失敗し、gRPC status が付かないエラーになったためと見られます。

無関係と判断した根拠

  1. adapter/s3_blob_m1_test.gomain と完全に同一です(git diff origin/main...HEAD で差分ゼロ)。
  2. 本 PR の差分に S3 / blob / auth / sigv4 に該当するファイルは 1 つもありません
  3. ローカルで -count=1 × 3 回 → 3/3 pass(各 0.03s)。
  4. 失敗は 0.02s で、テスト自体のタイムアウトではなくエラーコードの差異です。一方 adapter パッケージ全体は CI で 420s かかっており、runner の負荷が高い状態でした。

補足: これで #1104 の CI では 2 本目の異なる flake です

  • 1 回目: TestLuaNegativeTypeCache_BoundedSizeleader not found(Raft election 待ち)
  • 2 回目: TestGRPCS3BlobClusterAuthenticatedCapabilityPushAndFetch — gRPC status code の差異

どちらも本 PR が触っていない経路で、adapter パッケージが高負荷(292s / 420s)のときに出ています。adapter の重いテスト群が CI の負荷下で不安定という共通パターンです。前回コメントで触れたハーネス側の安定化(leader 確立待ち + トランスポート起因エラーのリトライ)を別 PR で出す価値がより高まったと考えますが、本 PR のスコープ外なので指示待ちです。

@bootjp

Copy link
Copy Markdown
OwnerAuthor

@codex review

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:3e848ca0c6

ℹ️ 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 on lines +787 to +790
if ok {
r.registrar.RemoveRoute(route.RouteID)
}
r.registrar.RegisterRoute(route.RouteID, route.Start, route.End, route.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.

P2 Badge Rebuild sampler layouts when route bounds change

When a catalog update changes the bounds of an existing RouteID, this immediate RemoveRoute/RegisterRoute sequence reclaims the retired MemSampler slot inside its grace window. Fresh evidence in MemSampler.RegisterRoute and TestSamplerGraceWindowReRegistrationKeepsStaleLayout confirms that reclamation intentionally preserves the old immutable sub-range layout while only replacing the displayed Start/End metadata. Subsequent observations are therefore bucketed using the old range, so KeyViz rows and autosplit p50 decisions can be attributed to incorrect boundaries; use a replacement path that creates a new layout for descriptor changes while draining the old counters separately.

Useful? React with 👍 / 👎.

Comment on lines +779 to +783
live := make(map[uint64]struct{}, len(routes))
for _, route := range routes {
live[route.RouteID] = struct{}{}
registered, ok := r.registered[route.RouteID]
next := registeredRouteFromDescriptor(route)

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 Remove retired routes before registering split children

When a split occurs with the sampler near MaxTrackedRoutes, this pass registers both new children while the removed parent still occupies an individual slot because obsolete routes are removed only in the later loop. For example, splitting 1 of 1023 routes with a 1024-route capacity transiently requires 1025 slots, so MemSampler.RegisterRoute folds one child into a virtual aggregate even though removing the parent immediately afterward leaves enough final capacity. The reconciler then records that child as registered and skips it on later equal snapshots, leaving its traffic permanently unavailable as individual KeyViz/autosplit evidence; remove obsolete RouteIDs before adding replacements or retry coarsened registrations after capacity is freed.

Useful? React with 👍 / 👎.

Comment on lines +480 to +481
for _, decision := range decisions {
nextCatalogVersion, err := s.executeDecision(ctx, catalogVersion, decision)

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 Recheck shard leadership before executing decisions

When the route's shard leadership transfers after routesLedLocally evaluates it but before this loop executes, the normal decision still calls SplitRange using evidence from the previous shard-leader term. Unlike catalog leadership, which DistributionServer.verifyCatalogLeader checks again, SplitRange does not verify leadership of decision.RouteGroupID; the catalog leader can therefore mutate a route it no longer leads. The pending-compound path already performs this execution-time group/term check, so ordinary decisions need the same fence immediately before each call.

Useful? React with 👍 / 👎.

Sign up for freeto 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

@bootjp