store: add read-set validation to ApplyMutations for SSI#499
store: add read-set validation to ApplyMutations for SSI#499
Conversation
Add readKeys parameter to ApplyMutations so that the store validates both write-write and read-write conflicts under the apply lock. This upgrades transaction isolation from Snapshot Isolation (SI) to Serializable Snapshot Isolation (SSI), preventing write skew anomalies where concurrent transactions read overlapping keys but write disjoint ones. The read set collected in the Redis adapter txnContext is threaded through the coordinator, FSM, and proto layers into the store, where each read key is checked for commits after startTS.
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
[Test Coverage] レビュー結果総評このPRはSI(Snapshot Isolation)からSSI(Serializable Snapshot Isolation)へのアップグレードという重要な変更にもかかわらず、新しいロジックに対する専用テストがゼロです。既存テストへの変更はすべて 不足しているテスト1.
|
| 対象 | 状況 |
|---|---|
pebbleStore.checkReadConflicts |
テストなし ❌ |
mvccStore read-set チェック |
テストなし ❌ |
| Write Skew 防止シナリオ | テストなし ❌ |
| 並行 read-write 競合 | テストなし ❌ |
adapter readKeys 伝播 |
テストなし ❌ |
| 既存テストの破損 | なし ✅ |
[Concurrency] レビュー結果🔴 HIGH: マルチシャードトランザクションで
|
[Consistency] レビュー結果問題1(Critical): クロスシャードトランザクションで
|
[Performance] レビュー結果1. リードセットの二重検証(高影響)
読み取りセットが大きい場合(SCAN / KEYS 等)は顕著なオーバーヘッドになる。どちらか一方に統一すべき。 2.
|
[Data Loss] レビュー結果結論: 重大なデータロストリスクは検出されず。ただし1件の隔離レベル低下による潜在リスクあり。問題なし: コアの書き込みアトミシティ
s.applyMu.Lock()
defer s.applyMu.Unlock()
checkConflicts(...) // 既存: write-write 競合チェック
checkReadConflicts(...) // 新規: read-write 競合チェック ← ロック内で正しく実行
applyMutationsBatch(...) // バッチ書き込み
b.Commit(pebble.Sync) // fsync 付きコミットチェックと書き込みがアトミックに行われるため、TOCTOU(Time-of-Check-Time-of-Use)競合による誤コミットは発生しません。WAL/Pebble の耐久性( 懸念: マルチシャードトランザクションで read-set 検証がアトミックにならない
if len(gids) == 1 {
return c.dispatchSingleShardTxn(..., readKeys) // ✅ readKeys が渡される
}
prepared, err := c.prewriteTxn(...) // ❌ readKeys が渡されない(2PC パス)シングルシャードの場合は
提案: 軽微:
|
| 観点 | 判定 |
|---|---|
| Write/Delete の意図しないデータ消失 | 問題なし |
| WAL/Raft ログの永続化 | 問題なし (pebble.Sync 維持) |
| エラーハンドリング欠如によるデータ消失 | 問題なし |
| トランザクションのコミット/ロールバック | 問題なし |
| TTL/Expire による意図しない削除 | 問題なし |
| スナップショット/バックアップ関連 | 問題なし |
| マルチシャード SSI の write skew | 要確認 (隔離レベル低下リスク) |
[Performance] レビュー結果問題 1 —
|
[Test Coverage] レビュー結果対象PR: #499 🔴 重大: 新規コアロジックに専用テストがない本PRが追加した主要な新機能は以下の3点です。
いずれも専用テストが存在しません。 テストファイルの変更はすべて、新しい 不足しているテストケース (具体的な提案)1.
|
| 観点 | 状態 |
|---|---|
| 変更ロジックに対応するテスト | ❌ なし (checkReadConflicts, mvccStore readKeys) |
| エッジケース (nil, 空, 存在しないキー) | ❌ なし |
| 正常系テスト | ❌ なし |
| エラーパス (ErrWriteConflict from readKeys) | ❌ なし |
| 並行アクセステスト | ❌ なし |
| Write Skew 防止のE2Eテスト | ❌ なし |
| 既存テストの破損 | ✅ 問題なし |
SSI実装の正しさが自動テストで一切検証されていない状態です。上記テストケースの追加を強く推奨します。
[Consistency] レビュー結果問題1(重大): マルチシャードトランザクションで readKeys が無視される対象:
if len(gids) == 1 {
return c.dispatchSingleShardTxn(..., elems, readKeys) // readKeys が渡される ✓
}
// 2PC パス: readKeys が渡されない ✗
prepared, err := c.prewriteTxn(startTS, commitTS, primaryKey, grouped, gids)影響: SSI(Serializable Snapshot Isolation)への昇格はシングルシャードトランザクションにのみ適用される。複数シャードにまたがるトランザクションは依然として SI(Snapshot Isolation)のままであり、write skew 異常が発生しうる。PR説明文「This upgrades transaction isolation from SI to SSI」および 問題2(軽微):
|
| 深刻度 | 問題 | 場所 |
|---|---|---|
| 重大 | マルチシャード 2PC で read-set バリデーションがスキップされる(write skew が残存) | kv/sharded_coordinator.go |
| 軽微 | checkReadConflicts が ctx を無視し context.Background() を使用 |
store/lsm_store.go |
[Concurrency] レビュー結果[HIGH] マルチシャードトランザクションで
|
| 重要度 | 箇所 | 内容 |
|---|---|---|
| HIGH | kv/sharded_coordinator.go:215 |
マルチシャード 2PC で readKeys が未伝播 → SSI 未達成 |
| MEDIUM | store/lsm_store.go:864 |
checkReadConflicts がコンテキストを捨てている |
| LOW | adapter/redis.go:1926 |
readKeys の順序が非決定的 |
単一シャードパス(onePhaseTxnRequest 経由)での実装は、applyMu による check→commit 原子性が正しく確保されており問題ありません。
[Data Loss] レビュー結果対象PR: #499 store: add read-set validation to ApplyMutations for SSI結論: 重大なデータロストリスクなしただし以下の注意事項があります。 確認項目✅ Write前の競合チェック順序(
|
- Thread readKeys through multi-shard PREPARE phase so cross-shard transactions also validate read-write conflicts (Concurrency/Consistency review) - Remove redundant validateReadSet call in Redis adapter; the store-layer check under applyMu is sufficient and avoids double LatestCommitTS I/O (Performance review) - Extract checkConflictsLocked in mvcc_store to reduce ApplyMutations cyclomatic complexity below the cyclop threshold (Lint review) - Add dedicated read-write conflict tests: conflict detected, no conflict when read key is unchanged, nonexistent read key, and nil readKeys backward compatibility (Test Coverage review)
[Test Coverage] レビュー結果PR #499 は SSI (Serializable Snapshot Isolation) の read-write 競合検出を 不足しているテスト1.
|
| 観点 | 評価 |
|---|---|
| pebbleStore の新ロジックテスト | 基本ケースは網羅 |
| mvccStore の新ロジックテスト | 不足 |
| write-skew 並行テスト | 不足 |
| FSM 統合テスト | 不足 |
| ShardedCoordinator テスト | 不足 |
| 空 readKeys / nil 互換性 | 不足 |
| 既存テスト破損 | なし |
最優先は mvccStore の読み取り競合テスト と write-skew 並行シナリオテスト の追加です。
[Performance] レビュー結果PR #499: store: add read-set validation to ApplyMutations for SSI1. 不要な スライスコピー ( ~1923行)readKeys := make([][]byte, 0, len(t.readKeys))
for _, k := range t.readKeys {
readKeys = append(readKeys, k)
}この for ループは 2. apply ロック保持中の sequential per-key I/O ( ~483行)func (s *pebbleStore) checkReadConflicts(_ context.Context, readKeys [][]byte, startTS uint64) error {
for _, key := range readKeys {
ts, exists, err := s.latestCommitTS(context.Background(), key) // Pebble点読み
...
}
}
write set のキーと read set のキーをまとめてバッチで引けるかを検討する余地がある(Pebble の 3. 全シャードへの readKeys 送信 ( ~277行,
|
[Data Loss] レビュー結果対象PR: store: add read-set validation to ApplyMutations for SSI (#499) 全体評価直接的なデータロストパスは検出されませんでした。 ただし、以下の点を記録します。 確認項目ごとの評価1. Write/Delete操作でデータが意図せず失われるパスがないか ✅
ステップ2・3でエラーが返れば、
2. エラーハンドリングの欠如によりデータが消失する可能性がないか
|
| 観点 | 評価 |
|---|---|
| 書き込みのアトミック性 | ✅ 問題なし(applyMu ロック下で競合チェック→書き込みが直列化) |
| WAL/Raftへの永続化 | ✅ 問題なし(pebble.Sync コミット、ReadKeys はRaftログに含まれる) |
| エラー時のロールバック | ✅ 問題なし(エラー時はバッチが Commit されない) |
| validateReadSet の削除 | ✅ 問題なし(同等の検証がストアロック下に移動) |
| checkReadConflicts の ctx 無視 | |
| マルチシャード cross-shard 読み取り競合 |
直接的なデータロストに該当する問題は検出されませんでした。
[Concurrency] レビュー結果以下 3 点の問題を検出しました。 [Bug]
|
| 深刻度 | 問題 | 場所 |
|---|---|---|
| 中 | でコンテキストが無視される(リーダー切り替え時に不必要なブロック) | |
| 中 | マルチシャードトランザクションで SSI が不完全(TOCTOU、write skew が依然発生しうる) | , |
| 低 | 2PC prepare の read set 検証タイミングが遅い競合を補足できない(上記の派生) |
単一シャードトランザクションのロジック(mvcc_store.go の checkConflictsLocked、lsm_store.go の ApplyMutations での applyMu によるチェック→コミットの原子化)は正しく実装されています。
[Consistency] レビュー結果1. 重大: 多シャードトランザクションにおける read-write 競合の検出漏れ場所:
req := &pb.Request{
Phase: pb.Phase_PREPARE,
Mutations: append([]*pb.Mutation{prepareMeta}, grouped[gid]...),
ReadKeys: readKeys, // 全 readKeys を全ての write shard へ
}これにより、書き込みセットに含まれないシャードに属する読み取りキーが一切チェックされない問題が発生します。 具体例(write skew が発生するシナリオ)
単一シャード ( 修正案
2. 軽微:
|
| 観点 | 判定 |
|---|---|
| Raft を経由すべき操作がローカルで直接実行されていないか | ✅ ApplyMutations は FSM の Apply() 内から呼ばれており、Raft ログコミット後に実行されている |
applyMu による check-apply のアトミック性 |
✅ lsm_store.go:906 の s.applyMu.Lock() が checkConflicts / checkReadConflicts / applyMutationsBatch を直列化している |
| readKeys のキーエンコーディング一貫性 | ✅ adapter は storage-level encoded keys (redisStrKey, listMetaKey 等) を trackReadKey に渡しており、mutation キーと同一エンコーディング |
| TTL とデータ本体の整合性 | ✅ redisTTLKey(userKey) も readKeys に追加されており、TTL の読み取りも競合検出対象になっている |
validateReadSet 削除の妥当性 |
✅ 旧来の client 側での TOCTOU ありの事前チェックを廃止し、Raft 直列化の下でのアトミックな検証に移行した設計は正しい |
| proto / FSM 間の readKeys 伝搬 | ✅ pb.Request.read_keys (field 5) → FSM → ApplyMutations まで一貫して伝達されている |
- Fix checkReadConflicts to propagate ctx instead of context.Background() (Concurrency review) - Route readKeys by shard ID in prewriteTxn so each shard only validates read keys that belong to it, and read keys on non-write shards are correctly checked by their owning shard (Consistency review) - Add mvccStore read-write conflict tests mirroring the pebbleStore coverage: conflict detected, no conflict when unchanged, nonexistent key, nil readKeys (Test Coverage review)
[Performance] レビュー結果問題1:
|
[Test Coverage] レビュー結果PR #499: 概要ストア層・MVCC層の単体テストは適切に追加されており、主要なハッピーパスとバックワード互換性は網羅されています。 不足テスト1. のユニットテストなし【高】対象ファイル: 新規追加された関数に専用テストがありません。特に以下の挙動が未検証です。 ルーティング不能な read key がサイレントに検証スキップされることで、SSI の保護漏れが生じる可能性があります。 提案テストケース: 2. マルチシャード SSI の統合テストなし【高】対象ファイル: ( パス) パスは単体テスト相当のカバーがありますが、(マルチシャード 2PC)パスで read keys が各シャードに正しく分配されることを確認するテストがありません。 提案テストケース: 3. コーディネーター層で readKeys が Request に含まれることを検証するテストなし【中】対象ファイル: 既存の が に更新されただけで、実際に を渡したときにプロトバッファ に正しく含まれるかを確認するテストがありません。 提案テストケース: 4. 複数 read key のうち一部のみが競合するケースのテストなし【低】対象ファイル: , 現在のテストは read key が 1 つのケースのみです。複数の read key を渡したとき、最初のコンフリクトで正しく早期リターンするかを検証するテストがありません。 提案テストケース: 既存テストへの影響の全呼び出し箇所(19ファイル)が 渡しに正しく更新されており、既存テストの破壊はありません。 総評ストア層の SSI 基本動作テストは充実しています。上記のうち 1, 2 は分散KVSとして特に重要な未検証領域のため、マージ前に対処することを推奨します。 |
[Data Loss] レビュー結果対象PR: store: add read-set validation to ApplyMutations for SSI (#499)問題 1:
|
[Concurrency] レビュー結果問題なし (単一シャード構成)単一シャードの
問題あり:
|
[Test Coverage] レビュー結果PR #499: 概要ストア層・MVCC層の単体テストは適切に追加されており、主要なハッピーパスとバックワード互換性は網羅されています。 不足テスト1.
|
Add validateReadOnlyShards to send read-set validation requests to shards that have read keys but no mutations. Previously, a read key on shard A would go unchecked if the transaction only wrote to shard B, allowing write skew across shards. Also fix checkReadConflicts to propagate the caller context (already addressed in prior commit for the function signature, this ensures consistent usage). Add partial-conflict test case where only some read keys conflict.
[Data Loss] レビュー結果総評重大なデータロストリスク: なし write-write衝突チェックと同様に、新しい read-write 衝突チェックも 配下でアトミックに実行されており、競合チェック → batch commit のシリアライズが正しく保たれています。以下に各観点の確認結果を記載します。 確認項目1. Write/Delete 操作のデータ消失パス問題なし。 2. WAL/Raft ログへの永続化問題なし。 3. エラーハンドリングの欠如によるデータ消失問題なし。 4. トランザクションのコミット/ロールバック処理問題なし。 5. TTL/Expire 処理での意図しないデータ削除問題なし。 6. スナップショット/バックアップ関連のデータ欠損問題なし。 注意事項 (データロストではなく SSI 正確性のギャップ)
if gid == 0 {
continue // ルーティング不明のキーを無声でスキップ
}multi-shard の prewrite パスにおいて、 |
[Consistency] レビュー結果✅ 正しい実装applyMu による原子的な check-then-commit
プロトコル層の伝播
|
[Performance] レビュー結果1.
|
[Test Coverage] レビュー結果概要PR #499 は カバーされている項目 (良好)
不足しているテストケース1.
|
[Test Coverage] レビュー結果PR #499: store: add read-set validation to ApplyMutations for SSIカバーされているテスト ✅
テスト不足 ❌1.
|
[Concurrency] レビュー結果問題 1 —
|
[Consistency] レビュー結果概要SI → SSI へのアップグレードの基本設計は正しく、シングルシャードパスの一貫性は向上している。ただし、マルチシャードトランザクションの読み取り専用シャードに対するread-write conflict検出がRaftを経由しないという重要な問題がある。 問題1 — 重要:
|
[Test Coverage] レビュー結果追加されたテスト(良い点)
不足しているテスト1.
|
| 対象 | 重要度 | 状況 |
|---|---|---|
validateReadOnlyShards ユニットテスト |
Critical | 不足 |
groupReadKeysByShardID ユニットテスト |
Critical | 不足 |
readKeys の proto 伝播テスト |
High | 不足 |
| マルチシャードSSI 結合テスト | High | 不足 |
| Redis adapter ライトスキュー E2E テスト | Moderate | 不足 |
validateReadSet のデッドコードテスト整理 |
Low | 要整理 |
store 層の単体テストはよくカバーされていますが、coordinator 層(特にマルチシャード SSI)のテストが全く存在しない点が主な懸念です。
[Concurrency] レビュー結果PR #499: store: add read-set validation to ApplyMutations for SSI問題1:
|
[Performance] レビュー結果[P1]
|
[Data Loss] レビュー結果概要データロストに関して致命的な問題は検出されませんでした。ただし、1点リスク軽微な懸念を指摘します。 OK:
|
[Test Coverage] レビュー結果総評SI → SSI へのアップグレードという重要な変更に対して、ストア層のユニットテストは充実しているが、以下の層でカバレッジに顕著なギャップがある。 不足しているテスト1.
|
| 箇所 | 深刻度 | 理由 |
|---|---|---|
validateReadOnlyShards / groupReadKeysByShardID |
高 | 多シャード環境でのSSIの要となるロジックが完全未テスト |
onePhaseTxnRequest + ReadKeys のシリアライズ |
中 | ReadKeysがプロトに正しく含まれるか未検証 |
| Redis adapter の統合パス | 中 | 削除した validateReadSet の代替パスの統合テストなし |
| SSI 並行アクセス | 低 | 新しいロックパスの並行安全性が未検証 |
[Concurrency] レビュー結果問題1 (Critical):
|
[Data Loss] レビュー結果観点: Write/Delete操作でのデータ消失データロストに関わる問題は検出されなかった。 確認事項と判定1.
|
[Performance] レビュー結果問題あり1.
|
[Consistency] レビュー結果総評: 問題なし(一貫性は強化されている)1. validateReadSet の削除と ApplyMutations への移動
旧実装の問題点(解消済み): 新実装(正しい): アトミックに検証とコミットが行われるため、race window が排除されています。 2. SSI の達成
3. 2PC パスの確認
4. readKeys のトラッキングPR #483 と合わせて 問題なし。一貫性の観点では改善です。 |
Address critical review: validateReadOnlyShards was reading the local store directly, bypassing Raft linearization. A concurrent write that was committed in Raft but not yet applied to the FSM would be invisible. Fix: issue a linearizable read barrier (LinearizableRead) on each read-only shard before checking LatestCommitTS, ensuring the FSM has applied all committed entries. Also: - Propagate ctx through dispatchTxn/prewriteTxn/validateReadOnlyShards instead of using context.Background() (Medium review) - Document the remaining TOCTOU limitation for read-only shards in multi-shard transactions (the check is outside applyMu) - Add test: onePhaseTxnRequest includes ReadKeys in proto request - Add tests: readKey overlapping writeKey (conflict and no-conflict)
| // would require a dedicated "read-validate" FSM request phase. For | ||
| // single-shard transactions and write-shard read keys, validation is fully | ||
| // atomic under applyMu. | ||
| func (c *ShardedCoordinator) validateReadOnlyShards(ctx context.Context, groupedReadKeys map[uint64][][]byte, writeGIDs []uint64, startTS uint64) error { |
There was a problem hiding this comment.
🚫 [golangci] reported by reviewdog 🐶
calculated cyclomatic complexity for function validateReadOnlyShards is 11, max is 10 (cyclop)
[Data Loss] レビュー結果対象PR: #499 store: add read-set validation to ApplyMutations for SSI確認観点と結果1. Write/Delete操作でデータが意図せず失われるパスがないか問題なし。
競合チェックはバッチ適用前に行われるため、チェックがエラーを返した場合、pebble Batch への書き込みは行われずデータは失われない。 2. WAL/Raftログへの書き込みが適切に永続化されているか問題なし。 ReadKeys は既存の Raft コミットパス( 3. エラーハンドリングの欠如によりデータが消失する可能性がないか問題なし。 全エラーパスで
4. トランザクションのコミット/ロールバック処理でデータが失われないか問題なし。 旧実装では 5. マルチシャードトランザクションにおける TOCTOU ウィンドウについてデータロストではなく、分離レベルの問題。
ただし、これは「本来 abort すべきトランザクションが commit される」可能性(isolation の低下)であり、既存データが消失するパスではない。 6. TTL/Expire処理、スナップショット/バックアップ変更なし。 本PRはTTL・Expire・スナップショット・バックアップのロジックに変更を加えていない。 総評データロストに関する問題は検出されなかった。 競合検出を applyMu ロック下に移動したことで、既存の SI 実装よりもアトミック性が向上している。マルチシャード読み取り専用シャードの TOCTOU ウィンドウは isolation レベルの問題であり、データ消失には繋がらない。 |
[Performance] レビュー結果1.
|
| # | 場所 | 内容 | 重要度 |
|---|---|---|---|
| 1 | store/lsm_store.go |
applyMu 保持中の O(N_read) ディスク I/O 追加 |
高 |
| 2 | kv/sharded_coordinator.go |
読みオンリーシャードの逐次 LatestCommitTS |
中 |
| 3 | kv/sharded_coordinator.go |
groupReadKeysByShardID マップ容量未指定 |
低 |
問題 1 は、読みセットが大きいトランザクションで書き込みスループットが顕著に低下する可能性があります。書き込みスループット重視の場合はロック外への移動を、実装コストを抑えたい場合は読みセットサイズに上限(例: 最大 N 件)を設けることでリスクを限定できます。
[Test Coverage] レビュー結果概要新規ロジック ( 問題あり1.
|
[Data Loss] レビュー結果総評コアとなる書き込みパスでのデータロストは確認されませんでした。ただし、マルチシャードトランザクションにおけるリードオンリーシャードのTOCTOU窓に、隔離レベルの抜け穴(Write Skew)があり、ユーザーデータが論理的に不整合な状態でコミットされるリスクがあります。 問題なし (安全性を確認した箇所)1. // ApplyMutations 内
if err := s.checkConflicts(ctx, mutations, startTS); err != nil {
return err
}
if err := s.checkReadConflicts(ctx, readKeys, startTS); err != nil {
return err // ← applyMutationsBatch より前に返す
}
if err := s.applyMutationsBatch(b, mutations, commitTS); err != nil {競合を検出した場合は 2. Write-Write / Read-Write チェックがどちらも同一ミューテックス下で行われており、アトミック性が保たれている。 3. 旧来の外部バリデーション( 4. コンフリクト時の abort 処理 ( if err := c.validateReadOnlyShards(ctx, groupedReadKeys, gids, startTS); err != nil {
c.abortPreparedTxn(startTS, primaryKey, prepared, abortTSFrom(startTS, commitTS))
return nil, err
}バリデーション失敗時に 要注意: Write Skew の抜け穴 (データ整合性リスク)
コード自身がコメントで認めている通り: マルチシャードトランザクションにおいて、リードオンリーシャード(そのトランザクションで書き込みが発生しないシャード)上の Read-Write 競合は FSM の 例:
影響範囲: 純粋なデータロスト(書いたデータが消える)ではなく、本来 abort されるべきトランザクションが commit されることで、アプリケーション上の論理的データ不整合が発生しうる。 コードの NOTE コメントには「single-shard および write-shard の read keys は applyMu 下で完全アトミックに検証される」と書かれており、問題は multi-shard 且つ read-only shard にあるケースに限定される。PR 説明にも「Full SSI for read-only shards in multi-shard transactions would require a dedicated 'read-validate' FSM request phase.」と記載があり、既知の制限として文書化されている点は評価できる。 推奨: この制限をユーザー向けドキュメントや CHANGELOG にも明記し、将来的に dedicated 小計
|
[Test Coverage] レビュー結果PR #499 は store 層の SSI テストは充実していますが、上位レイヤーに複数の未テスト箇所があります。 高優先度1.
|
| 層 | カバレッジ |
|---|---|
store/lsm_store (pebble) |
充実(7テスト追加) |
store/mvcc_store |
充実(4テスト追加) |
kv/coordinator (単一) |
部分的(ReadKeys伝播のみ) |
kv/sharded_coordinator |
未テスト |
kv/fsm |
未テスト |
adapter/redis (エンドツーエンド) |
旧メカニズムのテストのみ |
[Consistency] レビュー結果概要SSI (Serializable Snapshot Isolation) 実装の方針自体は正しく、単一シャードのトランザクションでは write-write・read-write 両コンフリクトが [重要] マルチシャードのread-onlyシャードにTOCTOUウィンドウが存在する該当箇所: // NOTE: This check is performed outside the FSM's applyMu lock, so there
// is a small TOCTOU window between the linearizable read barrier and the
// LatestCommitTS check.PRのコード自体がコメントで認めているとおり、マルチシャードトランザクションで 書き込みミューテーションを持たないシャード(read-onlyシャード) のread-writeコンフリクト検証は、FSMの 具体的な問題シナリオ:
シングルシャードトランザクションおよびwrite-shardに属するread-keyは [中程度] Phantom readが検出されない該当箇所: if exists && ts > startTS {
return NewWriteConflictError(key)
}Txn開始時に存在しなかったキーが、startTS以降に別のトランザクションによってINSERTされた場合、 実際には
ただし range scan(例: [確認済み・問題なし] Raftを経由すべき操作の直接実行
[確認済み・問題なし] ロック下での線形化可能性
[確認済み・問題なし] TTL・エンコーディングの一貫性
まとめ
|
[Concurrency] レビュー結果1. 問題なし:
|
[Concurrency] レビュー結果対象: #499 (store: add read-set validation to ApplyMutations for SSI) 問題 1:
|
| トランザクション種別 | 保証 |
|---|---|
| シングルシャード | 完全な SSI (applyMu 下でアトミック) |
| マルチシャード — 書き込みシャードの read key | 完全な SSI (PREPARE リクエスト内で FSM がアトミックに検証) |
| マルチシャード — 読み取り専用シャードの read key | ベストエフォートのみ (TOCTOU 窓あり) |
コメントで「dedicated read-validate FSM request phase が必要」と明記されているので、設計上の制限として認識されていることは理解できる。ただし PR 説明 / CHANGELOG でこの保証の非対称性を明示することを強く推奨する。「SSI」として外部向けに宣伝すると、マルチシャード環境で write skew が発生した際に原因調査が困難になる。
問題 2: groupReadKeysByShardID が gid == 0 の read key をサイレントにドロップ — 低
場所: kv/sharded_coordinator.go — groupReadKeysByShardID
gid := c.engineGroupIDForKey(key)
if gid == 0 {
continue // ← ルートが見つからない read key を無言でスキップ
}engineGroupIDForKey はルートが存在しない場合に 0 を返す。read key にルートがなければ PREPARE にも validateReadOnlyShards にも渡らず、コンフリクト検証が完全にスキップされる。
ルートが存在しない key をそもそも「読める」か?という問いはあるが、ルーティングテーブル更新中の一時的な不整合や nil/空 key の誤混入時に、無言スキップではなくエラーを返す方が安全。少なくとも slog.Warn でログを出すことを検討してほしい。
問題 3: prewriteTxn 内 txnGroupForID 失敗時の abort 漏れ — 既存バグ (参考情報)
場所: kv/sharded_coordinator.go — prewriteTxn ループ先頭
for _, gid := range gids {
g, err := c.txnGroupForID(gid)
if err != nil {
return nil, err // ← prepared 済みシャードが abort されない
}このコードパスは本 PR では変更されていないが、本 PR によって validateReadOnlyShards 失敗時の abort が正しく実装されたことで、この既存パスとの非対称性が際立つ。別 issue として起票を推奨する。
問題なし (確認済み)
- 単一シャード SSI:
checkConflictsLockedがs.mtx.Lock()下で write-write・read-write コンフリクトをアトミックに検証 → 完全な原子性を保証 - フォロワー→リーダー proxy:
coordinator.goのredirectパスでreqs.ReadKeysがonePhaseTxnRequestに正しく渡されている - コンテキスト伝播:
ctxがprewriteTxn→validateReadOnlyShards→linearizableReadEngineCtxまで正しくスレッドされており、クライアントのキャンセル/タイムアウトがabortPreparedTxnを適切にトリガーする - abort シーケンス:
validateReadOnlyShards失敗時にabortPreparedTxnが全 PREPARE 済みシャードに対して正しく呼ばれる - ロック順序:
mvccStore.ApplyMutationsはs.mtx.Lock()1 本のみ取得し、内部で追加のロックを取得しないためデッドロックリスクなし - ゴルーチンリーク:
validateReadOnlyShardsは同期的で goroutine を生成しない - Raft リーダー切り替え: FSM の
Applyは全ノードで決定論的に実行されるため、read key コンフリクト検証が FSM 内で行われることは Raft の整合性と矛盾しない - read key の defensive copy:
commit()内でt.readKeysマップから新スライスにコピーしてからOperationGroup.ReadKeysに渡しており、並行アクセスに対して安全
[Consistency] レビュー結果対象PR: #499 (store: add read-set validation to ApplyMutations for SSI) および #483 (adapter: decompose ZSet storage into per-member wide-column keys) PR #499 — SSIのための読み取りセット検証
|
| # | PR | 分類 | 内容 |
|---|---|---|---|
| 1 | #499 | マルチシャードSSIのTOCTOUウィンドウ(コードに文書化済み) | |
| 2 | #483 | 🔴 重要 | wide-columnケースでのTTL削除非対称性 — 孤立TTLキーのリスク |
| 3 | #483 | buildZSetDiffElems 削除が origMembers のみ対象(孤立キー対策として deleteZSetWideColumnElems の使用を検討) |
[Performance] レビュー結果[中]
|
[Data Loss] レビュー結果対象PR: store: add read-set validation to ApplyMutations for SSI 総評: データロストの問題なしデータロスト観点での危険なパスは検出されませんでした。以下、各チェックポイントの結果です。 Write/Delete操作でのデータ消失パス問題なし。
競合が検出された場合はステップ2または1で
|
[Performance] レビュー結果[中]
|
[Concurrency] レビュー結果対象PR: #499 store: add read-set validation to ApplyMutations for SSI 問題点[高]
|
| 観点 | 判定 | 根拠 |
|---|---|---|
applyMu 下での read-write 競合チェック |
✓ | checkConflicts(write-write) → checkReadConflicts(read-write) の順で同一 applyMu.Lock() 内に実行。アトミック性確保。 |
latestCommitTS の再帰的なロック取得 |
✓ | checkReadConflicts は内部版 s.latestCommitTS(dbMu 再取得なし)を使用。ApplyMutations が既に dbMu.RLock() を保持しているため正しい。 |
| Raft リプレイの決定性 | ✓ | ReadKeys は pb.Request (Raft ログエントリ) に永続化されるため、リーダー切り替え後のリプレイで同一の競合チェックが再実行される。 |
handleOnePhaseTxnRequest への r.ReadKeys 伝搬 |
✓ | ワンフェーズコミットで r.ReadKeys が正しく ApplyMutations に渡される。 |
handlePrepareRequest での r.ReadKeys 適用 |
✓ | 2PC PREPARE フェーズで read 競合を検出する設計として妥当。startTS, startTS の使用も PREPARE 時点では commitTS 未確定のため正しい。 |
旧 validateReadSet の削除 |
✓ | ApplyMutations 内でロック下に検証されるため、アダプタ層での事前チェックは TOCTOU を内包しており削除が正しい。 |
| コンテキスト・タイムアウト伝搬 | ✓ | txnContext.commit() が redisDispatchTimeout 付きの新規 ctx を生成。validateReadOnlyShards も ctx を受け取りリードバリアに渡している。 |
| goroutine リーク・チャネルブロック | ✓ | 新規 goroutine の起動はなし。 |
[Consistency] レビュー結果概要SSI(Serializable Snapshot Isolation)実装のために読み取りセット(read-set)検証を 問題点[HIGH] 複数シャードにまたがる読み取り専用シャードの TOCTOU 窓(SSI 不完全)対象: 書き込みがない読み取り専用シャードの競合チェックは、各書き込みシャードの 現状の動作を許容する場合でも、「単一シャードトランザクションのみ完全な SSI が保証される」という制約を [MEDIUM]
|
[Test Coverage] レビュー結果対象PR: #499 不足しているテスト🔴 HIGH:
|
| レイヤー | 状況 |
|---|---|
store/lsm_store (PebbleStore) |
✅ 6ケース追加で十分 |
store/mvcc_store |
✅ 4ケース追加で十分 |
kv/coordinator |
✅ ReadKeys伝播テストあり (TestCoordinateDispatchTxn_ReadKeysInRequest) |
kv/sharded_coordinator |
❌ validateReadOnlyShards, groupReadKeysByShardID, end-to-end ReadKeys 未テスト |
kv/fsm |
❌ ReadKeysを使ったFSMレベルテスト未テスト |
adapter/redis |
|
adapter/s3 |
✅ 既存テスト更新のみ |
最も重要なのは validateReadOnlyShards() のテスト不足です。マルチシャード環境でのSSI保証の中核部分が一切検証されていません。
Add readKeys parameter to ApplyMutations so that the store validates both write-write and read-write conflicts under the apply lock. This upgrades transaction isolation from Snapshot Isolation (SI) to Serializable Snapshot Isolation (SSI), preventing write skew anomalies where concurrent transactions read overlapping keys but write disjoint ones.
The read set collected in the Redis adapter txnContext is threaded through the coordinator, FSM, and proto layers into the store, where each read key is checked for commits after startTS.