Skip to content

Speed up large raft apply conflict checks#1078

Open
bootjp wants to merge 4 commits into
mainfrom
fix/raft-apply-conflict-iterator
Open

Speed up large raft apply conflict checks#1078
bootjp wants to merge 4 commits into
mainfrom
fix/raft-apply-conflict-iterator

Conversation

@bootjp

@bootjp bootjp commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • Reuse a single Pebble iterator for write/read conflict checks across a sorted batch.
  • Preserve the original input-order conflict key when multiple keys conflict.
  • Add regression coverage for large batches and conflict-key ordering.

Root cause

Large Raft apply batches repeatedly created Pebble iterators while checking each mutation key. During catch-up this made nodes spend excessive time in conflict checking before they could finish opening and serve traffic.

Tests

  • go test ./store ./kv
  • golangci-lint --config=.golangci.yaml run ./store ./kv --timeout=5m

Author: bootjp

Summary by CodeRabbit

  • 新機能
    • バッチでのキー取得(欠損・削除済み除外)に対応しました。
    • バックグラウンド常駐機能(ロック解決器/FSMコンパクタ)を環境変数で有効化・無効化できるようになりました。
  • バグ修正
    • トランザクションの競合検出を改善し、競合結果のキー選択と順序の安定性を向上しました。
    • 冷スタート時のWALスナップショット復元スキップ判定と指標整合を改善しました。
  • テスト
    • 大規模バッチ/順序保持、冷スタートskip-gate、環境変数解釈などの検証を追加・更新しました。

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

OCC競合検出をバッチ・単一イテレータ方式へ変更し、WAL復元のskip判定、volatile再生、raft適用高速化、トランザクションロック検査、バックグラウンド機能の環境変数制御を更新します。

Changes

MVCCストアのOCC処理

Layer / File(s) Summary
バッチ読取と競合探索
store/lsm_store.go, store/mvcc_store.go
GetAtBatch、キー整列、単一イテレータによるOCC判定、コミット境界判定を追加します。
raft適用とOCC境界
store/lsm_store.go, store/lsm_store_applied_index_test.go
既着地raft適用の高速経路、メタ更新、競合検査の境界を追加します。
バッチ処理と競合順序の検証
store/*_test.go
バッチ読取、大規模put、競合キー順序、補助関数を検証します。

WAL冷スタート復元

Layer / File(s) Summary
コールドスタート再生とPreVote
internal/raftengine/etcd/engine.go, internal/raftengine/etcd/engine_test.go
volatileエントリ再生、暗号payload処理、PreVote環境変数を更新します。
復元閾値と観測値
internal/raftengine/etcd/wal_store.go
コミット終端とスナップショットインデックスに基づくskip判定、ギャップ指標を更新します。
復元skip契約の検証
internal/raftengine/etcd/wal_store_skip_gate_test.go
skip時の適用インデックス、FSM復元、observer記録を検証します。

トランザクションロック検査

Layer / File(s) Summary
一括ロック競合検査
kv/fsm.go
ミューテーション単位の検査を一括検査へ変更し、バッチ取得またはスキャンで競合を判定します。
ロックキー順序の検証
kv/fsm_txn_test.go
競合時のロック対象キーが入力順を保持することを検証します。

バックグラウンド機能制御

Layer / File(s) Summary
条件付きバックグラウンド起動
main.go, main_background_env_test.go
ロック解決器とFSMコンパクタを環境変数で条件付き起動し、真偽値解釈をテストします。

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ApplyMutations
  participant ConflictChecker
  participant PebbleIterator
  participant WriteConflictError
  ApplyMutations->>ConflictChecker: mutations/readKeys と startTS を渡す
  ConflictChecker->>ConflictChecker: キーをソート・グルーピングする
  ConflictChecker->>PebbleIterator: 最新コミット時刻を取得する
  PebbleIterator-->>ConflictChecker: 可視コミット時刻を返す
  ConflictChecker->>WriteConflictError: 最初の競合キーを渡す
Loading
sequenceDiagram
  participant loadWalState
  participant restoreSnapshotState
  participant restoreSnapshotStateFromToken
  participant reportColdStart
  loadWalState->>restoreSnapshotState: committedTailIndex を渡す
  restoreSnapshotState->>restoreSnapshotStateFromToken: トークンとコミット終端を渡す
  restoreSnapshotStateFromToken->>restoreSnapshotStateFromToken: snapshot.Metadata.Index で判定する
  restoreSnapshotStateFromToken->>reportColdStart: skip/execute とギャップ指標を報告する
Loading

Possibly related PRs

  • bootjp/elastickv#915: ApplyMutationsRaftAt の既着地処理と metaAppliedIndex 検証に関連します。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.63% 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 name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 大規模な Raft 適用時の競合チェック高速化という変更の主旨を簡潔に表しており、内容とも整合しています。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

bootjp commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

@codex 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 optimizes the conflict checking logic in the LSM store by sorting mutation and read keys and utilizing a single Pebble iterator to perform batched conflict checks. It also adds unit tests to verify that the batched checks preserve input order and handle large batches correctly. However, the reviewer points out that Optimistic Concurrency Control (OCC) conflict checks are not required at the store layer since the leader is responsible for issuing consistent timestamps, and suggests removing these checks entirely instead of optimizing them.

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 store/lsm_store.go

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
store/lsm_store.go (1)

1431-1457: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ソート比較ロジックの重複を共通化できます。

sortedMutationConflictKeyssortedReadConflictKeys はキー抽出部分のみが異なり、sort.Slice 内の比較関数は完全に同一です。比較ロジックを共通ヘルパーへ切り出すと、将来ソート基準を変更した際の片方だけ更新漏れ(挙動乖離)を防げます。

♻️ 比較関数の共通化案
+func sortConflictCheckKeys(keys []conflictCheckKey) {
+	sort.Slice(keys, func(i, j int) bool {
+		if c := bytes.Compare(keys[i].key, keys[j].key); c != 0 {
+			return c < 0
+		}
+		return keys[i].index < keys[j].index
+	})
+}
+
 func sortedMutationConflictKeys(mutations []*KVPairMutation) []conflictCheckKey {
 	keys := make([]conflictCheckKey, len(mutations))
 	for i, mut := range mutations {
 		keys[i] = conflictCheckKey{key: mut.Key, index: i}
 	}
-	sort.Slice(keys, func(i, j int) bool {
-		if c := bytes.Compare(keys[i].key, keys[j].key); c != 0 {
-			return c < 0
-		}
-		return keys[i].index < keys[j].index
-	})
+	sortConflictCheckKeys(keys)
 	return keys
 }
 
 func sortedReadConflictKeys(readKeys [][]byte) []conflictCheckKey {
 	keys := make([]conflictCheckKey, len(readKeys))
 	for i, key := range readKeys {
 		keys[i] = conflictCheckKey{key: key, index: i}
 	}
-	sort.Slice(keys, func(i, j int) bool {
-		if c := bytes.Compare(keys[i].key, keys[j].key); c != 0 {
-			return c < 0
-		}
-		return keys[i].index < keys[j].index
-	})
+	sortConflictCheckKeys(keys)
 	return keys
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@store/lsm_store.go` around lines 1431 - 1457,
共通のキー比較ロジックをヘルパーへ切り出し、sortedMutationConflictKeys と sortedReadConflictKeys の
sort.Slice からそのヘルパーを再利用するよう更新してください。各関数のキー抽出と index の順序付けは維持し、重複した bytes.Compare
と index 比較を一箇所に集約してください。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@store/lsm_store.go`:
- Around line 1431-1457: 共通のキー比較ロジックをヘルパーへ切り出し、sortedMutationConflictKeys と
sortedReadConflictKeys の sort.Slice からそのヘルパーを再利用するよう更新してください。各関数のキー抽出と index
の順序付けは維持し、重複した bytes.Compare と index 比較を一箇所に集約してください。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b6d60aa9-4135-4ab5-8512-cc610adf4f8a

📥 Commits

Reviewing files that changed from the base of the PR and between e1890c5 and 38f8168.

📒 Files selected for processing (2)
  • store/lsm_store.go
  • store/lsm_store_txn_test.go

@bootjp bootjp force-pushed the fix/raft-apply-conflict-iterator branch from 38f8168 to c6e5ddd Compare July 12, 2026 16:11
@bootjp

bootjp commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

Addressed the high review note by documenting why store-level OCC remains required, then re-ran go test ./store ./kv and golangci-lint --config=.golangci.yaml run ./store ./kv --timeout=5m. @codex review

@bootjp bootjp force-pushed the fix/raft-apply-conflict-iterator branch from c6e5ddd to 7b3aaa1 Compare July 12, 2026 16:14
@bootjp

bootjp commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

Also addressed the sort-comparison duplication nit by adding a shared conflict-key sorter. Re-ran go test ./store ./kv and golangci-lint --config=.golangci.yaml run ./store ./kv --timeout=5m. @codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 7b3aaa17b7

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

@bootjp bootjp force-pushed the fix/raft-apply-conflict-iterator branch 9 times, most recently from 202e832 to 70f7913 Compare July 12, 2026 17:29
@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 (70f7913):

  • 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 force-pushed the fix/raft-apply-conflict-iterator branch from 70f7913 to 483103c Compare July 12, 2026 17:38
@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 (483103c):

  • 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.

@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: 483103c25a

ℹ️ 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 store/lsm_store.go
Comment on lines +1650 to +1654
ts, exists, err := s.latestCommitTSWithIter(ctx, iter, current, &seekKey, &upperBound, preferNext, linearAdvanceLimit)
if err != nil {
return err
return nil, false, err
}
if exists && ts > startTS {
s.writeConflicts.record(WriteConflictKindRead, classifyWriteConflictKey(key))
return NewWriteConflictError(key)
preferNext = iter.Valid()

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 Re-seek after a bounded seek misses its key

When latestCommitTSWithIter misses current, SeekGEWithLimit can still leave the Pebble iterator valid at a key beyond the supplied limit; Pebble documents that this limit is best-effort. Setting preferNext solely from iter.Valid() then lets the next lookup start from that later key, so if the next mutation/read key sorts before that iterator position, conflictTSFromCurrentIter returns c > 0 and reports no version without seeking. In sparse batches this can skip a real latestCommitTS > startTS and allow an OCC-conflicting write/read set to commit.

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 (a57e6dd):

  • 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.

@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: a57e6dd1fe

ℹ️ 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 store/lsm_store.go
if mut == nil || len(mut.Key) == 0 {
continue
}
return s.committedVersionAtLocked(mut.Key, commitTS)

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 identify replays from only the first key

When callers pin CommitTS (supported by OperationGroup.CommitTS), a different raft entry can share the timestamp with an earlier entry. If its first mutation key already has a version at that timestamp, this returns true before checkApplyConflicts or applyMutationsBatch run, so a new entry like [a, c] after a prior [a, b] at the same commitTS is acknowledged as already landed, c is never written, and the applied index is advanced. The replay fast path needs a stronger same-entry check than the first key alone.

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 (37eb89b):

  • kv/fsm.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

Here are some automated review suggestions for this pull request.

Reviewed commit: 37eb89bea7

ℹ️ 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 store/lsm_store.go
Comment on lines +983 to +984
if iter.SeekGEWithLimit(seekKey, upperBound) != pebble.IterValid {
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.

P1 Badge Re-seek batch gets after bounded misses

When a batch lookup misses, SeekGEWithLimit is allowed to return IterValid on a key beyond upperBound; Pebble's next SeekGEWithLimit can then no-op if that overshot key is still below the next lookup's limit. In the transaction lock batch path, a missing earlier lock can therefore leave the iterator inside a later lock's key range, causing that later existing lock to be reported missing and allowing a raw/one-phase write to proceed through an active transaction lock. Treat out-of-range hits as forcing a real re-seek/reset before the next key.

Useful? React with 👍 / 👎.

Comment on lines +392 to +393
snapIndex := snapshot.GetMetadata().GetIndex()
decision, have := decideSkipOutcome(fsm, snapIndex)

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 skip entries needed to re-observe commit timestamps

When the durable FSM index is between the snapshot index and committedTailIndex, this now takes the skip path and returns have. On open, rawNodeAppliedForOpen only rewinds for config/volatile entries; data entries at indexes snapshot.Index+1..have are not passed to kvFSM.Apply, so their observeAppliedCommitTS calls are lost after a process restart even though their MVCC versions remain in Pebble. In that crash window the shared HLC can start below persisted commit_ts values and allocate duplicate/stale timestamps; the gate needs to replay/observe through the committed target or explicitly observe the skipped max commit timestamp.

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 (71455f8):

  • kv/fsm.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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (4)
main.go (1)

352-355: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

環境変数名を構造化キーとして渡すことを推奨。

name をメッセージ文字列に結合するのではなく、構造化キーとして渡すべきです。コーディングガイドラインでは slog で安定した構造化キーを使用することが求められています。name は常にハードコード定数のためログインジェクションの実害はありませんが、構造化ログとしてのクエリ性や一貫性が向上します。

As per coding guidelines: "Use slog for logging and maintain structured keys (e.g., key, commit_ts, route_id, etc.)"

♻️ Proposed refactor for structured logging
-		slog.Warn("invalid "+name+"; using default",
-			"value", raw,
-			"default", true,
-		)
+		slog.Warn("invalid env var; using default",
+			"env", name,
+			"value", raw,
+			"default", true,
+		)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.go` around lines 352 - 355, Update the slog.Warn call to keep the
warning message static and pass name as a structured field using a stable key
such as "name"; preserve the existing raw and default fields.

Sources: Coding guidelines, Linters/SAST tools

main_background_env_test.go (1)

5-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

テーブル駆動テスト形式へのリファクタリングを推奨。

コーディングガイドラインでは *_test.go ファイルでテーブル駆動テストを推奨しています。現在の3つのサブテストは構造が重複しており、テーブル駆動にすることで簡潔になり、"true""0" などのエッジケース追加も容易になります。

As per coding guidelines: "Unit tests must be co-located with packages in *_test.go files; prefer table-driven test cases"

♻️ Proposed table-driven test refactor
 func TestEnabledEnv(t *testing.T) {
-	t.Run("unset returns default", func(t *testing.T) {
-		t.Setenv("ELASTICKV_TEST_BOOL_ENV", "")
-		if !enabledEnv("ELASTICKV_TEST_BOOL_ENV") {
-			t.Fatal("enabledEnv() = false, want true")
-		}
-	})
-
-	t.Run("false disables default true", func(t *testing.T) {
-		t.Setenv("ELASTICKV_TEST_BOOL_ENV", "false")
-		if enabledEnv("ELASTICKV_TEST_BOOL_ENV") {
-			t.Fatal("enabledEnv() = true, want false")
-		}
-	})
-
-	t.Run("invalid returns default", func(t *testing.T) {
-		t.Setenv("ELASTICKV_TEST_BOOL_ENV", "maybe")
-		if !enabledEnv("ELASTICKV_TEST_BOOL_ENV") {
-			t.Fatal("enabledEnv() = false, want true")
-		}
-	})
+	tests := []struct {
+		name  string
+		value string
+		want  bool
+	}{
+		{"empty returns default", "", true},
+		{"false disables", "false", false},
+		{"true enables", "true", true},
+		{"zero disables", "0", false},
+		{"one enables", "1", true},
+		{"invalid returns default", "maybe", true},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Setenv("ELASTICKV_TEST_BOOL_ENV", tt.value)
+			if got := enabledEnv("ELASTICKV_TEST_BOOL_ENV"); got != tt.want {
+				t.Fatalf("enabledEnv() = %v, want %v", got, tt.want)
+			}
+		})
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main_background_env_test.go` around lines 5 - 26, Refactor TestEnabledEnv
into a table-driven test using a case slice containing each environment value
and expected result, then iterate with subtests and set ELASTICKV_TEST_BOOL_ENV
for each case before calling enabledEnv. Preserve the existing unset, "false",
and invalid-value expectations while keeping the test easy to extend with
additional boolean edge cases.

Source: Coding guidelines

kv/fsm.go (2)

1515-1524: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

assertNoConflictingTxnLock の判定を共通化してください。 prepareTxnMutation からまだ呼ばれているので dead code ではありませんが、decodestartTS/PrimaryKey 比較 → NewTxnLockedError の流れは txnLockConflictError と重複しています。単一件・バッチの両方で同じヘルパーを使う形に寄せると、今後の差分が減ります。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/fsm.go` around lines 1515 - 1524, assertNoConflictingTxnLock に重複している
decodeTxnLock、startTS/PrimaryKey 比較、NewTxnLockedError の判定を削除し、単一件・バッチ双方が
txnLockConflictError を利用するよう共通化してください。prepareTxnMutation を含む既存の呼び出し経路と、競合しない場合に
nil を返す挙動は維持してください。

1432-1450: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

ScanAt フォールバックの走査範囲を絞りたい GetAtBatch が使えない場合でも、ここは minLockKey..nextScanCursor(maxLockKey) をページングしており、対象外の txn ロックもまとめて読みます。targets は exact key を持っているので、疎な mutation バッチではもっと狭い取得経路に寄せたいです。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kv/fsm.go` around lines 1432 - 1450, assertNoConflictingTxnLocksWithScan
のフォールバック走査を、scanStart/scanEnd の広い範囲ではなく targets
に含まれる正確なロックキーだけを取得する経路へ変更してください。GetAtBatch が使えない場合も対象キーを基準に取得し、取得した KV は既存の
updateTxnLockCheckResult に渡して競合判定を維持してください。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@store/lsm_store_applied_index_test.go`:
- Around line 114-115: Update the test setup after ApplyMutations to store
metaAppliedIndexBytes as a complete 8-byte little-endian value, such as
entryIdx-1, instead of []byte{0x01}; ensure the value represents an existing
older index so the stale comparison exercises the expected forward-progress
path.

---

Nitpick comments:
In `@kv/fsm.go`:
- Around line 1515-1524: assertNoConflictingTxnLock に重複している
decodeTxnLock、startTS/PrimaryKey 比較、NewTxnLockedError の判定を削除し、単一件・バッチ双方が
txnLockConflictError を利用するよう共通化してください。prepareTxnMutation を含む既存の呼び出し経路と、競合しない場合に
nil を返す挙動は維持してください。
- Around line 1432-1450: assertNoConflictingTxnLocksWithScan
のフォールバック走査を、scanStart/scanEnd の広い範囲ではなく targets
に含まれる正確なロックキーだけを取得する経路へ変更してください。GetAtBatch が使えない場合も対象キーを基準に取得し、取得した KV は既存の
updateTxnLockCheckResult に渡して競合判定を維持してください。

In `@main_background_env_test.go`:
- Around line 5-26: Refactor TestEnabledEnv into a table-driven test using a
case slice containing each environment value and expected result, then iterate
with subtests and set ELASTICKV_TEST_BOOL_ENV for each case before calling
enabledEnv. Preserve the existing unset, "false", and invalid-value expectations
while keeping the test easy to extend with additional boolean edge cases.

In `@main.go`:
- Around line 352-355: Update the slog.Warn call to keep the warning message
static and pass name as a structured field using a stable key such as "name";
preserve the existing raw and default fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 93ef96b5-403d-41fd-bc96-9ff9175e91b7

📥 Commits

Reviewing files that changed from the base of the PR and between 8ba79dd and 71455f8.

📒 Files selected for processing (13)
  • internal/raftengine/etcd/engine.go
  • internal/raftengine/etcd/engine_test.go
  • internal/raftengine/etcd/wal_store.go
  • internal/raftengine/etcd/wal_store_skip_gate_test.go
  • kv/fsm.go
  • kv/fsm_txn_test.go
  • main.go
  • main_background_env_test.go
  • store/lsm_store.go
  • store/lsm_store_applied_index_test.go
  • store/lsm_store_test.go
  • store/lsm_store_txn_test.go
  • store/mvcc_store.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • store/lsm_store_txn_test.go
  • internal/raftengine/etcd/wal_store.go
  • internal/raftengine/etcd/wal_store_skip_gate_test.go

Comment on lines +114 to +115
require.NoError(t, ps.ApplyMutations(ctx, muts, nil, commitTS, commitTS))
require.NoError(t, ps.db.Set(metaAppliedIndexBytes, []byte{0x01}, pebble.Sync))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# readAppliedIndexLocked が truncated 値を欠落扱いにするか確認
ast-grep outline store/lsm_store.go --match readAppliedIndexLocked
rg -nP -C4 'func .*readAppliedIndexLocked|len\(val\) < timestampSize' store/lsm_store.go

Repository: bootjp/elastickv

Length of output: 1548


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# テストの該当範囲と、applied index の判定ロジックを読む
sed -n '1,220p' store/lsm_store_applied_index_test.go
printf '\n---\n'
rg -n -C6 'func .*shouldWriteAppliedIndexLocked|func .*readAppliedIndexLocked|LastAppliedIndex|metaAppliedIndexBytes|timestampSize' store/lsm_store.go

Repository: bootjp/elastickv

Length of output: 21135


metaAppliedIndex には 8 バイトの値を書いてください
[]byte{0x01} は truncated 扱いで missing と同じ分岐に入るため、existing < appliedIndex の stale 比較を通せません。entryIdx-1 などの 8 バイト little-endian 値にして、古い index からの前進を直接検証してください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@store/lsm_store_applied_index_test.go` around lines 114 - 115, Update the
test setup after ApplyMutations to store metaAppliedIndexBytes as a complete
8-byte little-endian value, such as entryIdx-1, instead of []byte{0x01}; ensure
the value represents an existing older index so the stale comparison exercises
the expected forward-progress path.

Source: Coding guidelines

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