Skip to content

Add physical snapshot export substrate#1131

Merged
bootjp merged 5 commits into
mainfrom
feature/physical-snapshot-export-substrate
Jul 19, 2026
Merged

Add physical snapshot export substrate#1131
bootjp merged 5 commits into
mainfrom
feature/physical-snapshot-export-substrate

Conversation

@bootjp

@bootjp bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • add the focused physical snapshot object-offload design and link it from the storage roadmap
  • expose the newest WAL-valid persisted FSM snapshot as a single-use stream paired with Raft index, term, ConfState, byte count, and CRC32C
  • add complete-payload restore preparation that preserves the existing FSM header and leaves store-format dispatch to the receiver
  • preserve the existing logical external-restore header behavior through focused regression coverage

Dependency and scope

PR #1130 owns the Pebble SST ingest snapshot stream. This pull request treats the complete FSM payload as opaque and does not parse or duplicate that format.

Because #1130 is still in review, this pull request intentionally contains only the independently reviewable export/restore substrate. Object-store publication, runtime scheduling, retention/GC, and operator restore wiring remain later milestones in the focused design.

Safety

The exporter reads only a WAL-valid persisted Raft snapshot. Token index, metadata index, footer CRC32C, and the CRC32C recomputed during streaming must agree. The source file descriptor remains open for the whole stream, so local retention cannot replace the exported inode mid-upload.

Physical restore targets an absent data directory and copies a verified regular file through the existing atomic restore preparation. The existing logical restore API and its synthetic KV header are unchanged.

Tests

  • go test ./internal/raftengine/etcd -count=1 -timeout=20m
  • go test -race ./internal/raftengine/etcd -run 'TestPreparePhysicalSnapshotRestoreAndExportOpaquePayload|TestPersistedSnapshotExport|TestOpenPersistedSnapshotExport' -count=1 -timeout=10m
  • go test ./... -run '^$' -count=1 -timeout=20m
  • golangci-lint run ./internal/raftengine/etcd/... --timeout=10m
  • commit hook: full golangci-lint, 0 issues
  • git diff --check

Summary by CodeRabbit

  • 新機能

    • PebbleのSST取り込み方式によるスナップショットの作成・転送・復元に対応しました。
    • 復元前後の整合性検証を強化し、失敗時には既存データを保持します。
    • 物理スナップショットのエクスポートと復元に対応しました。
    • SST方式が利用できない場合は従来方式へ自動的に切り替えます。
  • ドキュメント

    • SSTスナップショット転送および外部オブジェクトストレージへのオフロード設計を追加しました。
  • バグ修正

    • 不完全な書き込みを正しく検出するよう改善しました。

@coderabbitai

coderabbitai Bot commented Jul 19, 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: 16 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6fc0ebcb-0a90-43a7-be38-b1173ca22c8b

📥 Commits

Reviewing files that changed from the base of the PR and between 9012ed1 and 95dab95.

📒 Files selected for processing (3)
  • docs/design/2026_07_19_proposed_physical_snapshot_object_offload.md
  • internal/raftengine/etcd/external_snapshot_restore.go
  • internal/raftengine/etcd/external_snapshot_restore_test.go
📝 Walkthrough

Walkthrough

Pebble の SST ingest スナップショット転送、検証付き原子的復元、物理 FSM スナップショットのエクスポート API、関連テストと設計文書を追加・更新しています。

Changes

SST ingest スナップショット転送

Layer / File(s) Summary
設計契約とロードマップ参照
docs/design/*
SST ingest 転送の形式、検証、フォールバック、復元手順と、物理スナップショットオフロード案を文書化し、ロードマップの参照を更新しています。
SST ingest スナップショット生成
store/lsm_store.go, store/snapshot_pebble_sst.go, store/snapshot_pebble.go, store/snapshot_pebble_sst_test.go
チェックポイントから SST を生成し、ハッシュ・長さ付きマニフェストとして出力します。設定無効時や準備失敗時は従来形式へフォールバックします。
検証付き原子的復元
store/lsm_store.go, store/snapshot_pebble_sst.go, store/snapshot_pebble_sst_test.go
マニフェスト、SST 本文、メタデータを検証して一時 DB に ingest し、成功時のみディレクトリを置換します。破損時の宛先維持、ロールバック、成果物掃除もテストしています。
物理復元と永続スナップショットエクスポート
internal/raftengine/etcd/*, kv/snapshot_sst_ingest_test.go
物理 FSM payload 用の復元 APIと peer 正規化を追加し、永続スナップショットを CRC 検証付きで単回ストリーミングできるようにしています。gRPC および FSM の往復テストを追加しています。

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

Sequence Diagram(s)

sequenceDiagram
  participant Raft
  participant pebbleStore
  participant GRPCTransport
  participant destinationStore
  Raft->>pebbleStore: Snapshot()
  pebbleStore->>GRPCTransport: EKVSSTI1 payloadを送信
  GRPCTransport->>destinationStore: receiveSnapshotStream()
  destinationStore->>destinationStore: manifest検証・SST ingest・atomic swap
Loading

Possibly related PRs

  • bootjp/elastickv#530: スナップショットのマジックヘッダーによる復元経路が関連しています。
  • bootjp/elastickv#1038: 外部スナップショット復元 API の領域が関連しています。
  • bootjp/elastickv#1112: スケーリングロードマップ文書の更新が関連しています。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 物理スナップショットの export/restore 基盤追加という主要変更を端的に表しており、内容と整合しています。

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 19, 2026

Copy link
Copy Markdown
Owner Author

Current head: 94693adf28ec5bdfd045e91c1216a526bf9ccca1

Evidence:

  • author and committer: bootjp <contact@bootjp.me>
  • GitHub signature verification: valid
  • newest persisted snapshot export pins one descriptor and verifies metadata index, token index, footer CRC32C, exact length, and streamed CRC32C
  • complete physical payload restore preserves the existing FSM header and delegates inner format handling to the receiver
  • existing external restore caller and synthetic-header semantics remain unchanged

Validation:

  • go test ./internal/raftengine/etcd -count=1 -timeout=20m
  • focused race tests for physical restore/export and corruption paths
  • all-package compile
  • target lint and full commit-hook lint: 0 issues
  • git diff --check

Please review the persisted snapshot consistency boundary, single-use stream lifecycle, CRC failure behavior, complete-payload restore semantics, and the explicit dependency boundary with #1130.

@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 introduces the initial substrate for the physical snapshot object offload design (milestone M4). It adds a detailed design document, implements PreparePhysicalSnapshotRestore to restore fresh etcd-raft data directories from opaque FSM payloads, and provides OpenPersistedSnapshotExport to safely stream locally persisted snapshots. Feedback is provided regarding a potential data race in Metadata() if proto.Clone fails, where the original ConfState pointer could be leaked instead of being set to nil.

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 internal/raftengine/etcd/persisted_snapshot_export.go
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Updated head: 35479d5e8d9becc4b2190bb6025bfe65bcdbc0e9

Resolved the metadata ownership finding by clearing ConfState if cloning cannot yield an owned copy. The export handle never exposes its internal pointer.

Validation:

  • focused persisted snapshot export/restore tests: pass
  • focused race tests: pass
  • full golangci-lint commit hook: 0 issues
  • git diff --check: pass
  • author/committer: bootjp <contact@bootjp.me>
  • GitHub commit verification: verified

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 35479d5e8d

ℹ️ 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 internal/raftengine/etcd/external_snapshot_restore.go
Comment thread internal/raftengine/etcd/external_snapshot_restore.go Outdated
@bootjp
bootjp force-pushed the feature/physical-snapshot-export-substrate branch from 35479d5 to 9012ed1 Compare July 19, 2026 13:20
@bootjp
bootjp changed the base branch from main to feature/pebble-sst-ingest-snapshot July 19, 2026 13:20
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Updated stacked head: 9012ed1ed45e9693b32f99724cb58a21eb1d7df9
Base: #1130 head 214a8ccf027a9190d7a5ec467f27a6ae3658c097

Resolved both current-head restore membership findings:

  • validate suffrage and require at least one voter
  • normalize a cloned peer list by NodeID before all restore writes
  • seed restore ConfState voters/learners to match persisted peer suffrage
  • preserve cold-bootstrap voter-only behavior

Caller audit:

  • PrepareExternalSnapshotRestore: operator CLI supplies a sorted voter-only list
  • PreparePhysicalSnapshotRestore: object-offload restore path plus focused tests
  • seedExternalSnapshotRestoreDir: only reached through the normalized shared path

Validation:

  • go test ./store ./kv ./internal/raftengine/etcd -count=1 -timeout=20m
  • focused restore/export race tests
  • target golangci-lint: 0 issues
  • stack git diff --check: pass
  • old Add Pebble SST ingest snapshot transfer #1130 proposed/partial path references: none
  • author/committer: bootjp <contact@bootjp.me>
  • GitHub signature verification: verified

@codex review

Base automatically changed from feature/pebble-sst-ingest-snapshot to main July 19, 2026 13:22

@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 (2)
kv/snapshot_sst_ingest_test.go (1)

14-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

TTL 付きキーの SST ingest スナップショット round-trip カバレッジを追加してください。

本テストは kv/ パッケージの Snapshot/Restore(replication 経路)に新たに触れていますが、PutAt の TTL 引数は常に 0 で、TTL 付きエントリが SST ingest スナップショット経由で正しく復元されるかは検証されていません。TTL 付きキーのケースを1つ追加することを推奨します。

As per coding guidelines, "Add test coverage for TTL/HLC behaviors, OCC logic, and replication logic whenever touching clocks, optimistic concurrency control, or replication mechanisms" for {store,kv}/**/*_test.go.

♻️ 追加例
 	require.NoError(t, srcStore.PutAt(context.Background(), []byte("key"), []byte("value"), 41, 0))
+	require.NoError(t, srcStore.PutAt(context.Background(), []byte("ttl-key"), []byte("ttl-value"), 41, 100))

そして復元後に該当キーの TTL/期限が維持されていることを検証するアサーションを追加してください。

🤖 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/snapshot_sst_ingest_test.go` around lines 14 - 40, Test
TestKVFSMSnapshotRoundTripsSSTIngestPayload with an additional key written via
PutAt using a non-zero TTL, then include it in the SST ingest snapshot and
restore flow. After Restore, assert both the TTL/expiration metadata and value
for that key are preserved, while retaining the existing non-TTL round-trip
coverage.

Source: Coding guidelines

internal/raftengine/etcd/external_snapshot_restore.go (1)

347-392: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

voters/learners の分割ロジックを共通化してください
confStateForSnapshotRestorePeers の分割条件は wal_store.gosplitPeersBySuffrage と同じです。ここを共通ヘルパーに寄せると、将来の suffrage 変更時の片修正を防げます。

🤖 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 `@internal/raftengine/etcd/external_snapshot_restore.go` around lines 347 -
392, confStateForSnapshotRestorePeers の voters/learners 分割を独自実装せず、wal_store.go の
splitPeersBySuffrage を共通ヘルパーとして再利用してください。splitPeersBySuffrage の戻り値から
raftpb.ConfState を構築し、Suffrage の判定ロジックを一箇所に集約してください。
🤖 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 `@docs/design/2026_07_19_proposed_physical_snapshot_object_offload.md`:
- Around line 13-14: Adjust the line wrapping in the design document so “PR
`#1130`” remains on the same line, preventing “#1130” from appearing at the start
of a line and being parsed as an ATX heading.

---

Nitpick comments:
In `@internal/raftengine/etcd/external_snapshot_restore.go`:
- Around line 347-392: confStateForSnapshotRestorePeers の voters/learners
分割を独自実装せず、wal_store.go の splitPeersBySuffrage
を共通ヘルパーとして再利用してください。splitPeersBySuffrage の戻り値から raftpb.ConfState を構築し、Suffrage
の判定ロジックを一箇所に集約してください。

In `@kv/snapshot_sst_ingest_test.go`:
- Around line 14-40: Test TestKVFSMSnapshotRoundTripsSSTIngestPayload with an
additional key written via PutAt using a non-zero TTL, then include it in the
SST ingest snapshot and restore flow. After Restore, assert both the
TTL/expiration metadata and value for that key are preserved, while retaining
the existing non-TTL round-trip coverage.
🪄 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: 36b263ce-b2a6-4be9-9adf-0195c7a6881f

📥 Commits

Reviewing files that changed from the base of the PR and between 915bc77 and 9012ed1.

📒 Files selected for processing (13)
  • docs/design/2026_06_12_proposed_scaling_roadmap.md
  • docs/design/2026_07_19_implemented_pebble_sst_ingest_snapshot_transfer.md
  • docs/design/2026_07_19_proposed_physical_snapshot_object_offload.md
  • internal/raftengine/etcd/external_snapshot_restore.go
  • internal/raftengine/etcd/external_snapshot_restore_test.go
  • internal/raftengine/etcd/grpc_transport_sst_snapshot_test.go
  • internal/raftengine/etcd/persisted_snapshot_export.go
  • internal/raftengine/etcd/persisted_snapshot_export_test.go
  • kv/snapshot_sst_ingest_test.go
  • store/lsm_store.go
  • store/snapshot_pebble.go
  • store/snapshot_pebble_sst.go
  • store/snapshot_pebble_sst_test.go

Comment thread docs/design/2026_07_19_proposed_physical_snapshot_object_offload.md Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: 9012ed1ed4

ℹ️ 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 commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Updated head: e08e3b7dafdcfdde6ce8adae27e40d89b4b5aab7

Addressed the current CodeRabbit root and relevant review-body item:

  • kept PR #1130 off an ATX-heading position
  • reused the existing suffrage partition helper
  • sorted learner IDs for deterministic restore ConfState output

The TTL suggestion refers to #1130 base files and is outside this stacked PR diff; #1130 already reached its latest-head stop condition.

Validation:

  • focused physical/external restore tests: pass
  • target golangci-lint: 0 issues
  • git diff --check: pass
  • author/committer: bootjp <contact@bootjp.me>
  • GitHub signature verification: verified

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e08e3b7daf

ℹ️ 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 internal/raftengine/etcd/external_snapshot_restore.go
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Updated head: 95dab952494c1c3010a01f03ee713dc727e4ff0c

Resolved the current duplicate restore peer identity finding:

  • validate normalized peer IDs before creating restore state
  • reject distinct NodeIDs sharing an explicit ID
  • reject blank IDs that normalize to the same address

Caller audit:

  • PrepareExternalSnapshotRestore: external snapshot operator CLI already derives and rejects duplicate NodeIDs
  • PreparePhysicalSnapshotRestore: object-offload restore API now receives the shared normalized-ID guard
  • seedExternalSnapshotRestoreDir: remains reachable only after shared validation and normalization

Validation:

  • focused external/physical restore tests: pass
  • target golangci-lint: 0 issues
  • full pre-commit golangci-lint: 0 issues
  • git diff --check: pass
  • author/committer: bootjp <contact@bootjp.me>
  • GitHub signature verification: valid

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 95dab95249

ℹ️ 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 merged commit 764db2d into main Jul 19, 2026
11 checks passed
@bootjp
bootjp deleted the feature/physical-snapshot-export-substrate branch July 19, 2026 14:16
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