Skip to content

Add live logical backup control plane#1119

Merged
bootjp merged 8 commits into
design/live-backup-pin-substratefrom
design/live-backup-control-plane
Jul 23, 2026
Merged

Add live logical backup control plane#1119
bootjp merged 8 commits into
design/live-backup-pin-substratefrom
design/live-backup-control-plane

Conversation

@bootjp

@bootjp bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • add BeginBackup, RenewBackup, EndBackup, ListAdaptersAndScopes, and StreamBackup admin RPCs
  • replicate bounded backup pin reservations and per-group retention fences through Raft
  • gate backup start on live member capabilities and snapshot headroom
  • scan and classify user-visible keys at one pinned read timestamp using the existing logical encoders
  • rotate HMAC-protected renewal tokens with a hard deadline and reapply complete pins so partial delivery cannot leave a replica unprotected

Safety

  • enforce a cluster-wide active backup cap with deterministic reservation and compensating release
  • reject expired tokens for renew, list, and stream while allowing EndBackup cleanup
  • retry transient per-group proposals and fail closed when renewal cannot finish before the prior deadline
  • scope compactor retention fences to their Raft group while preserving process-wide ordinary read pins

Validation

  • go test ./adapter -run Test(BeginBackup|RenewBackup|BackupToken|StreamBackup|BackupProtocol|GetRaftGroups|GetNodeVersion|Admin) -count=1 -timeout=240s
  • go test ./kv ./internal/backup . -count=1 -timeout=240s
  • go test -race ./adapter ./kv ./internal/backup -run Test(BeginBackup|RenewBackup|BackupToken|StreamBackup|BackupProtocol|GetRaftGroupsLeaderVersion|GetRaftGroupsSnapshotsEachGroupOnce|LeaderVersionProbeAttemptTimeout|ActiveTimestampTracker|ApplyBackup|FSMCompactorScopesBackupPinsByGroup|LiveDecoder|ScopeForKey) -count=1 -timeout=300s
  • golangci-lint run . ./adapter/... ./kv/... ./internal/backup/... --timeout=5m --allow-parallel-runners
  • buf generate
  • buf breaking --against the stacked admin API base

Author: bootjp

Summary by CodeRabbit

  • 新機能
    • Admin 経由でバックアップの開始・更新・終了、対象スコープ一覧、キー・バリューのストリーミング取得に対応しました。
    • スコープ単位の絞り込みと、バックアッププロトコルの対応可否をバージョンで判定します。
  • 改善
    • 固定した読み取り時点とルートスナップショットで安定した取得を実現しました。
    • 予約/解除の管理を強化し、キーのみの走査・再利用を最適化しました。
  • 信頼性
    • TTL/ヘッドルーム/容量/互換性の事前検証、部分失敗時の補償、エラー時の後処理を強化しました。
    • バックアップピンの同時上限を引き下げました。

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

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

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

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 872dd5ff-f5aa-4840-ac14-4868c356f083

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

ライブバックアップ用のFSMワイヤ、スナップショット走査、Admin RPC、トークン管理、起動時設定、アダプタ別デコード機能、管理提案転送、統合テストを追加しました。

Changes

ライブバックアップ基盤

Layer / File(s) Summary
バックアップワイヤとFSM処理
proto/admin.proto, kv/backup_codec.go, kv/fsm_backup.go, kv/*_test.go
予約・解除サブタイプ、バックアップFSM適用、HLC観測、関連RPCメッセージとテストを追加しました。
スナップショット走査とスコープ復元
kv/backup_scan.go, internal/backup/*, kv/shard_store_test.go
ルートスナップショット共有、キー専用スキャナ、アダプタ別スコープ復元、S3/SQSキー解析を実装しました。
Adminバックアップライフサイクル
adapter/admin_backup.go, adapter/admin_grpc.go, adapter/admin_backup_test.go
Begin/Renew/End、一覧取得、ストリーミング、HMACトークン、ピア互換性ゲート、セッション更新と統合テストを追加しました。
起動設定とバックアップ配線
main.go, main_admin_test.go, kv/active_timestamp_tracker.go
ライブバックアップ設定の検証、ピン上限、read fence、ピアプローブ、提案者登録を起動処理へ接続しました。
管理提案のリーダー転送
adapter/internal.go, kv/leader_admin_proposer.go, proto/internal.proto, *_test.go
管理提案をローカル提案者またはリーダーへのInternal RPCへ転送する経路を追加しました。

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

Sequence Diagram(s)

sequenceDiagram
  participant AdminClient
  participant AdminServer
  participant LeaderAdminProposer
  participant InternalLeader
  participant RaftGroups
  participant ShardStore

  AdminClient->>AdminServer: BeginBackup
  AdminServer->>LeaderAdminProposer: reserve and pin
  LeaderAdminProposer->>InternalLeader: ForwardAdminProposal when follower
  InternalLeader->>RaftGroups: apply admin proposal
  RaftGroups-->>AdminServer: commit indexes
  AdminServer->>ShardStore: capture route snapshot and scan baseline
  AdminServer-->>AdminClient: pin token and expected keys
  AdminClient->>AdminServer: StreamBackup
  AdminServer->>ShardStore: scan at pinned readTS
  ShardStore-->>AdminServer: BackupKV records
  AdminServer-->>AdminClient: stream BackupKV
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% 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 ライブ論理バックアップの管理プレーン追加を端的に表しており、変更内容と一致しています。

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

Copy link
Copy Markdown
Owner Author

Current signed HEAD 1703991 is ready for review.

Safety evidence:

  • renewal replays the complete read_ts pin on every group so a missing replica-local fence is restored
  • the HMAC token carries the committed hard deadline and rotates after each renewal
  • renew, list, and stream reject expired tokens; EndBackup still accepts them for idempotent cleanup
  • BeginBackup reserves the cluster-wide pin slot before fanout and compensates committed groups on partial failure
  • compactor caller chain uses group-scoped backup pins plus process-wide ordinary read pins

Validation evidence is listed in the PR body; post-merge focused unit, targeted race, lint, proto generation, and proto breaking checks all pass.

@codex review
@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

@bootjp Thanks—reviewing the current signed HEAD with particular attention to the backup lifecycle safety invariants and failure/cleanup paths.

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

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request implements a live backup control plane, adding new gRPC endpoints (BeginBackup, RenewBackup, EndBackup, ListAdaptersAndScopes, and StreamBackup) to the admin service, along with key classification, backup scanners, and Raft FSM integration. The review feedback focuses on improving error handling: specifically, ensuring that errors from scanner.Close() are not silently ignored, and that context cancellation or deadline exceeded errors during backup streaming are correctly propagated as gRPC status codes instead of being masked as FailedPrecondition errors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread adapter/admin_backup.go Outdated
if scanner == nil {
return status.Errorf(codes.Unavailable, "%s", "backup scanner is nil")
}
defer func() { _ = scanner.Close() }()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The error returned by scanner.Close() is silently ignored using _. Discarding close errors on database scanners can mask resource leaks or cleanup failures. It is recommended to log this error to ensure operational visibility.

	defer func() {
		if err := scanner.Close(); err != nil {
			// Log the error to ensure resource leaks are visible
		}
	}()
References
  1. Do not silently ignore errors from Close() methods on resources like network connections. Log them to ensure resource leaks or other cleanup problems are visible.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 2b2c215. Baseline scanner close errors now flow through finishBackupScan and fail the List/Begin path; the new TestListBackupScopesReportsScannerCloseError covers the RPC-visible failure.

Comment thread adapter/admin_backup.go Outdated
if scanner == nil {
return nil, 0, errors.New("backup key scanner is nil")
}
defer func() { _ = scanner.Close() }()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The error returned by scanner.Close() is silently ignored using _. Discarding close errors on database scanners can mask resource leaks or cleanup failures. It is recommended to log this error to ensure operational visibility.

	defer func() {
		if err := scanner.Close(); err != nil {
			// Log the error to ensure resource leaks are visible
		}
	}()
References
  1. Do not silently ignore errors from Close() methods on resources like network connections. Log them to ensure resource leaks or other cleanup problems are visible.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 2b2c215. Stream scanner close errors return Internal after a successful scan; if the scan already failed, the original gRPC status is preserved and the cleanup failure is logged. The new stream test covers the close-error path.

Comment thread adapter/admin_backup.go
@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD 2b2c215 addresses all current review findings.

Evidence:

  • scanner close failures now fail otherwise-successful baseline and stream operations
  • cleanup failure after an existing stream error is logged without masking its gRPC status
  • context cancellation and deadline expiration retain Canceled/DeadlineExceeded
  • go test ./adapter -run 'Test(StreamBackup|ListBackupScopes)' -count=1 passes\n\nCI is running for this HEAD.\n\n@codex review

Comment thread adapter/admin_backup.go
Comment thread adapter/admin_backup.go Outdated
pair, ok, err := scanner.Next(stream.Context())
if err != nil {
if errors.Is(err, context.Canceled) {
return status.Error(codes.Canceled, err.Error())

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.

🚫 [golangci] reported by reviewdog 🐶
error returned from external package is unwrapped: sig: func google.golang.org/grpc/status.Error(c google.golang.org/grpc/codes.Code, msg string) error (wrapcheck)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in c9baa53. Cancellation and deadline errors now use the shared status.Errorf-based normalizer; repository golangci-lint reports 0 issues.

Comment thread adapter/admin_backup.go Outdated
return status.Error(codes.Canceled, err.Error())
}
if errors.Is(err, context.DeadlineExceeded) {
return status.Error(codes.DeadlineExceeded, err.Error())

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.

🚫 [golangci] reported by reviewdog 🐶
error returned from external package is unwrapped: sig: func google.golang.org/grpc/status.Error(c google.golang.org/grpc/codes.Code, msg string) error (wrapcheck)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in c9baa53. Cancellation and deadline errors now use the shared status.Errorf-based normalizer; repository golangci-lint reports 0 issues.

@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: 2b2c2152df

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/admin_backup.go Outdated
if err != nil {
return err
}
scanner := s.backupStore.NewBackupScanner(nil, nil, tok.readTS, s.backupConfig.scanPageSize)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep StreamBackup on the pinned route view

When a range split/move applies after BeginBackup builds expected_keys but before the producer calls StreamBackup, this creates a fresh scanner from the current route table for the old read_ts. Keys that were still owned only by the old group at the pinned timestamp can be omitted (or a later owner can be scanned instead), so the streamed backup no longer matches the baseline returned by BeginBackup and can silently miss data if the producer does not treat the count mismatch as fatal. Capture the route snapshot/version used for the baseline in the token, or otherwise block/handle route changes while the pin is active.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in c9baa53. BeginBackup captures one immutable BackupRouteSnapshot before the read fence, uses it for the expected-key baseline, and stores it under the HMAC-authenticated pin ID for List/Stream. StreamBackup constructs its scanner from that exact snapshot; a missing endpoint-local snapshot fails closed. TestBackupScannerReusesCapturedRouteSnapshot and TestStreamBackupFailsClosedWithoutPinnedRouteSnapshot cover both paths.

Comment thread main.go Outdated
}
clock := coordinate.Clock()
clock.Observe(shardStore.LastCommitTS())
readTS, err := clock.NextFenced()

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 Fence after allocating the backup read timestamp

Because readTS is allocated after the all-groups lease read above, a concurrent write can allocate a commit timestamp before this NextFenced() call but only commit/apply after the lease read has completed. That committed version is then <= read_ts and belongs in the point-in-time backup, but the prior fence did not wait for it, so BeginBackup can immediately scan and miss it. Allocate the backup timestamp first and then issue the cluster-wide read/applied barrier for that timestamp (or an equivalent commit-index fence).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in c9baa53. adminBackupReadFence now observes the local commit watermark and allocates readTS before LeaseReadAllGroupsThrough. TestAdminBackupReadFenceAllocatesTimestampBeforeBarrier records the HLC order and fails if the barrier moves ahead of timestamp issuance.

Comment thread adapter/admin_backup.go Outdated
}

func (s *AdminServer) gateBackupPeerVersions(ctx context.Context) error {
members := s.snapshotMembers(ctx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fail backup peer gating on incomplete membership

In a multi-node cluster where any Raft Configuration call times out or errors, snapshotMembers intentionally returns a partial member list instead of failing; using that tolerant view for BeginBackup means any node known only to the failed group (and not in the static seed) is never probed for backup_protocol_version. A rolling-upgrade backup can therefore proceed while an old replica is still in the Raft configuration, defeating the capability gate before new backup FSM entries are proposed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in c9baa53. The backup capability gate now uses snapshotBackupMembers, which requires every registered Raft group's Configuration call to return successfully before any peer probes or FSM proposals. The tolerant snapshotMembers path remains unchanged for GetClusterOverview. TestBeginBackupFailsClosedOnIncompleteMembership covers the failed Configuration case.

Comment thread kv/backup_scan.go Outdated
if err != nil {
return err
}
s.page = append(s.page[:0], keys...)

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 Filter key-only backup pages through captured routes

When old-owner keys remain in a shard after a split/move, the value scanner drops them via materializeRouteForKey, but the count-only scanner accepts every key returned by scanKeyRoutesWithSourceAt. In that scenario BeginBackup/ListAdaptersAndScopes can count scopes from stale off-route data that StreamBackup will not emit, causing otherwise valid backups to fail baseline-count verification or advertise scopes that cannot actually be streamed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in c9baa53. backupKeyScanner filters each raw page through the same captured-route ownership resolver as the value scanner while advancing its cursor from the raw page, including all-filtered pages. TestBackupKeyScannerFiltersStaleOffRouteKeys covers stale data followed by an owned key across page boundaries.

Comment thread adapter/admin_backup.go Outdated
Comment on lines +474 to +475
if err := stream.Send(&pb.BackupKV{Key: pair.Key, Value: pair.Value}); err != nil {
return errors.WithStack(err)

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 cancellation status from stream sends

If the client cancels or the stream deadline expires after at least one record is ready to send, the error comes from stream.Send, not scanner.Next; wrapping that raw context.Canceled/DeadlineExceeded with errors.WithStack makes status.Code see Unknown, while only scanner errors are normalized above. Handle context errors from Send the same way before wrapping so canceled backup streams retain the expected gRPC status.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in c9baa53. Errors from both scanner.Next and stream.Send pass through backupContextStreamError, preserving Canceled and DeadlineExceeded. TestStreamBackupPreservesContextStatusAndReportsCloseErrors now covers a DeadlineExceeded send failure; normal and race backup suites pass.

Comment thread internal/backup/live.go
@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD c9baa53 addresses every finding from the previous-head review.\n\nSafety evidence:\n- BeginBackup captures one immutable route view and baseline/List/Stream reuse it; missing endpoint-local state fails closed.\n- read_ts is issued before the all-groups applied barrier.\n- backup membership capability gating requires every Raft Configuration call to succeed; the overview RPC remains tolerant.\n- key-only pages apply the same captured-route ownership filter as value scans.\n- scanner and send cancellation preserve gRPC Canceled/DeadlineExceeded.\n- SQS FIFO sequence side rows use a dedicated bare-key parser and remain restorable.\n\nCaller audit:\n- adminBackupReadFence has one production caller in Admin option wiring.\n- snapshotBackupMembers is used only by the backup capability gate; snapshotMembers remains on GetClusterOverview.\n- captured-snapshot scanner APIs are confined to live-backup control and scanner tests.\n\nValidation:\n- go test ./adapter -run Backup -count=1\n- go test -race ./adapter -run Backup -count=1\n- targeted root/kv/internal-backup/adapter tests\n- targeted root/kv/internal-backup/adapter race tests\n- golangci-lint run ./... --timeout=5m (0 issues)\n- go test ./... passed every package except the pre-existing adapter multi-node/election suite, which hit its 10-minute package timeout; all backup adapter tests pass independently.\n\nCI is running for this HEAD.\n\n@codex review\n@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

@bootjp Thanks for the detailed safety and validation evidence. I’ll review the current signed HEAD, focusing on the revised route snapshot invariants, read timestamp ordering, capability-gate failure handling, cancellation status preservation, and SQS FIFO side-row restoration.

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

@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

🤖 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 `@adapter/admin_backup.go`:
- Around line 350-355: After renewBackupGroups succeeds but
requireUnexpiredBackupToken fails, release the renewed pin’s group fences and
capacity reservations before returning the token error. Update the error path in
the surrounding RPC method to perform compensating cleanup for the renewed pin,
while preserving the existing renewal error handling. Add a test covering token
expiry after renewal and verify all renewed resources are released.
🪄 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: ab9aa49b-0f99-4fe1-b4f5-b5e9a869e938

📥 Commits

Reviewing files that changed from the base of the PR and between e8c0cb2 and c9baa53.

⛔ Files ignored due to path filters (2)
  • proto/admin.pb.go is excluded by !**/*.pb.go
  • proto/admin_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (18)
  • adapter/admin_backup.go
  • adapter/admin_backup_test.go
  • adapter/admin_grpc.go
  • internal/backup/live.go
  • internal/backup/live_test.go
  • internal/backup/s3.go
  • internal/backup/sqs.go
  • kv/active_timestamp_tracker.go
  • kv/backup_codec.go
  • kv/backup_codec_test.go
  • kv/backup_scan.go
  • kv/compactor_test.go
  • kv/fsm_backup.go
  • kv/fsm_backup_test.go
  • kv/shard_store_test.go
  • main.go
  • main_admin_test.go
  • proto/admin.proto

Comment thread adapter/admin_backup.go
@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD 693592b addresses the current-head renewal cleanup finding.

Evidence:

  • expiry after successful renewal now compensates all group pins and the capacity reservation
  • endpoint-local route state is removed on that fail-closed path
  • the new clock-controlled regression test verifies reserve/pin/release/unreserve ordering and session removal
  • focused unit and race tests pass
  • all adapter backup tests pass
  • adapter golangci-lint reports 0 issues

CI is running for this HEAD.

@codex review
@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Current commit =="
git rev-parse HEAD
git status --short

echo "== Backup implementation outline =="
ast-grep outline adapter/admin_backup.go --items all --type function,method,struct,interface --match 'Renew|expire|cleanup|Backup|session|propose|release|unreserve' || true

echo "== Relevant implementation symbols =="
rg -n -C 5 -i 'func \(s \*AdminServer\) RenewBackup|expire|cleanup.*backup|backupSessions|unreserve|release.*backup|proposeBackupAll|proposeBackupWithRetry' adapter/admin_backup.go

echo "== Backup test outline =="
ast-grep outline adapter/admin_backup_test.go --items all --type function --match 'Renew|Deadline|expire|cleanup|Backup' || true

echo "== Relevant regression tests =="
rg -n -C 8 -i 'clock|expiry|expire|renew.*(cleanup|deadline)|unreserve|session removal|backupSessions' adapter/admin_backup_test.go

Length of output: 21431


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Renew / End paths =="
sed -n '328,395p' adapter/admin_backup.go

echo "== Fanout, compensation, and renewal retry paths =="
sed -n '714,872p' adapter/admin_backup.go

echo "== Session and token/group lookup paths =="
sed -n '910,960p' adapter/admin_backup.go
sed -n '1113,1165p' adapter/admin_backup.go

echo "== Test proposer behavior =="
sed -n '32,155p' adapter/admin_backup_test.go

echo "== Renewal-expiry regression test =="
sed -n '514,586p' adapter/admin_backup_test.go

echo "== Relevant backup configuration initialization =="
sed -n '89,153p' adapter/admin_backup.go
sed -n '227,256p' adapter/admin_backup_test.go

Length of output: 20750


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Begin and baseline lifecycle =="
sed -n '184,328p' adapter/admin_backup.go

echo "== All renewal-helper call sites =="
rg -n -C 6 '\brenewBackupGroups\s*\(' --glob '*.go'

echo "== Tests for renewal pin-fanout failure / cleanup =="
rg -n -i -C 8 'renew.*(fail|error|retry|compensat|release|unreserve)|group pins|capacity reservation' adapter/admin_backup_test.go

echo "== Backup tracker semantics for duplicate reserve/pin/release =="
ast-grep outline kv/active_timestamp_tracker.go --items all --type function,method,struct --match 'Backup|Pin|Reserve|Release|Unreserve' || true
rg -n -C 5 'Reserve|Unreserve|PinWithDeadline|ReleaseBackupPin|backup' kv/active_timestamp_tracker.go

Length of output: 6435


🏁 Script executed:

#!/bin/bash
set -u

echo "== Exact renewal helper references =="
rg -n -F -C 5 'renewBackupGroups(' --glob '*.go' || true

echo "== Active timestamp tracker outline =="
ast-grep outline kv/active_timestamp_tracker.go --items all --type function,method,struct --match 'Backup|Pin|Reserve|Release|Unreserve' || true

echo "== Active timestamp tracker backup methods =="
rg -n -i -C 6 'backup|reserve|unreserve|pinwithdeadline|releasebackuppin' kv/active_timestamp_tracker.go || true

echo "== Existing renewal failure test contexts =="
rg -n -i -C 12 'TestRenew.*(Retry|Fail|Release|Cleanup)|renew backup pin|group pins|capacity reservation' adapter/admin_backup_test.go || true

Length of output: 853


@bootjp I found one remaining renewal cleanup gap.

adapter/admin_backup.go, Line 350: if renewBackupGroups successfully commits the renewed capacity Reserve but exhausts retries while applying the renewed Pin entries, it returns an error. RenewBackup immediately maps that to Unavailable on Line 351 without calling compensateBackupRelease.

That leaves the control-group reservation at its new deadline, plus any groups whose pin proposal did commit during the partially failed fanout. Since the client receives an error—and its existing token may expire—the capacity slot and retention fences can persist until expiry rather than being fail-closed immediately.

Please compensate this error path as well (release all token groups, then unreserve the control group, and remove the endpoint-local session), and add a regression test that injects a pin-fanout failure after Reserve succeeds and asserts Release → Unreserve.

✅ 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-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

elastickv/kv/backup_scan.go

Lines 372 to 374 in 693592b

if write > 0 && bytes.Equal(out[write-1].key, item.key) {
out[write-1] = item
continue

P1 Badge Prefer owned duplicate keys before trimming

When a page contains the same raw key from the current owner and from a later-scanned stale/off-route group, such as after a range split where the old group still has a copy, this dedupe overwrites the owned item with the later duplicate before routeForRoutedKey filters ownership. The stale item is then rejected and the live key is omitted from both expected counts and StreamBackup. Filter or rank by captured route ownership before collapsing duplicates.

ℹ️ 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 main.go Outdated
Comment on lines +2044 to +2046
clock := coordinate.Clock()
clock.Observe(shardStore.LastCommitTS())
readTS, err := clock.NextFenced()

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 Fence all shard groups before minting read_ts

When BeginBackup runs on a node whose local shard store has not yet applied a write that already committed on another raft-group leader, this mints readTS before the LeaseReadAllGroupsThrough call below. That lease read can then pull in the completed write with a commit timestamp greater than readTS, so the subsequent pinned scan omits data that was already committed before the backup began. The fence should first bring all groups up to date, then observe the resulting max LastCommitTS/allocate a timestamp above it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in bd8d262. The all-groups lease/read barrier now completes before LastCommitTS is observed and read_ts is minted. TestAdminBackupReadFenceAllocatesTimestampAfterBarrier applies a commit during the barrier and proves the returned timestamp is greater.

Comment thread adapter/admin_backup.go Outdated
Comment on lines +255 to +257
routes := s.backupStore.CaptureBackupRouteSnapshot(nil, nil)

readTS, err := s.backupReadFence(ctx)

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 Capture route snapshot after the read fence

When a route split or rebalance commits between this route snapshot and backupReadFence, the returned readTS can include writes routed with the new catalog while later scans are forced to the older routes captured here. A key committed on the new group before readTS is then never scanned even though it belongs in the backup. Capture the route view after the fence, or bind it to a catalog version that is also fenced by readTS.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in bd8d262. prepareBackup now captures the immutable route snapshot only after the read fence succeeds and validates its timestamp. TestBeginBackupCapturesRouteSnapshotAfterReadFence pins the ordering.

Comment thread adapter/admin_backup.go
Comment thread main.go Outdated
for _, rt := range runtimes {
srv.RegisterGroup(rt.spec.id, rt.engine)
if group := shardGroups[rt.spec.id]; group != nil {
srv.RegisterBackupProposer(rt.spec.id, group.Proposer())

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 Route backup proposals to group leaders

In multi-group deployments where leadership is spread across nodes, registering group.Proposer() wires backup pin proposals to the local raft engine for each group. The etcd engine's ProposeAdmin is still a local proposal path and returns not-leader on followers, so BeginBackup fails unless the chosen admin endpoint is leader for every raft group. Use a leader-proxying proposer for these backup FSM entries instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in bd8d262. Backup ProposeAdmin calls now use a leader-routed proposer. Followers forward the raw admin payload over Internal.ForwardAdminProposal; the verified leader applies it through its local wrap-aware ShardGroup.Proposer. Local and follower forwarding tests cover both paths.

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD bd8d262 addresses every current-head finding.

Evidence:

  • captured-route dedupe now preserves the captured owner when a later stale/off-route group returns the same raw key; key-only and value scanners are both covered
  • the all-group read fence now completes before observing LastCommitTS and minting read_ts; route capture follows the successful fence
  • live members without probe addresses fail BeginBackup before any Raft proposal
  • backup admin proposals are leader-routed over Internal gRPC and applied through the leader wrap-aware proposer
  • failed renewal fanout releases all group pins, unreserves the control slot, and removes the endpoint session

Caller audit:

  • adminBackupReadFence has one production wiring site
  • mergeAndTrimRoutedScanKeys has one scanner merge caller
  • RegisterBackupProposer is consumed only through backup ProposeAdmin paths
  • RenewBackup is the only RPC caller whose failure cleanup semantics changed; BeginBackup and its renewal loop retain their existing compensation

Validation:

  • go test ./... -count=1 -timeout=600s
  • go test -race ./adapter ./kv ./internal/backup . -count=1 -timeout=600s
  • golangci-lint --config=.golangci.yaml run --fix
  • golangci-lint run . ./adapter/... ./kv/... ./internal/backup/... --timeout=5m --allow-parallel-runners
  • make -C proto gen (includes buf breaking and generation)
  • git diff --check

@codex review

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

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

@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 (1)
adapter/internal_admin_proposal_test.go (1)

63-82: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

テーブル駆動テストへのリファクタリング

As per coding guidelines, unit tests should be co-located with packages in *_test.go files; prefer table-driven test cases. 類似したテストケースをテーブル駆動形式にまとめることを推奨します。

♻️ 提案するリファクタリング
 func TestInternalForwardAdminProposalFailsClosed(t *testing.T) {
 	t.Parallel()
-	t.Run("follower", func(t *testing.T) {
-		internal := NewInternalWithEngine(nil, internalAdminLeaderView{state: raftengine.StateFollower}, nil, nil)
-		_, err := internal.ForwardAdminProposal(context.Background(), &pb.ForwardAdminProposalRequest{})
-		require.ErrorIs(t, err, ErrNotLeader)
-	})
-	t.Run("apply response", func(t *testing.T) {
-		proposer := &internalAdminProposer{response: stderrors.New("apply failed")}
-		internal := NewInternalWithEngine(
-			nil,
-			internalAdminLeaderView{state: raftengine.StateLeader},
-			nil,
-			nil,
-			WithInternalAdminProposer(proposer),
-		)
-		_, err := internal.ForwardAdminProposal(context.Background(), &pb.ForwardAdminProposalRequest{})
-		require.ErrorContains(t, err, "apply failed")
-	})
+	tests := []struct {
+		name       string
+		leaderView internalAdminLeaderView
+		proposer   raftengine.Proposer
+		wantErr    string
+		errTarget  error
+	}{
+		{
+			name:       "follower",
+			leaderView: internalAdminLeaderView{state: raftengine.StateFollower},
+			proposer:   nil,
+			errTarget:  ErrNotLeader,
+		},
+		{
+			name:       "apply response",
+			leaderView: internalAdminLeaderView{state: raftengine.StateLeader},
+			proposer:   &internalAdminProposer{response: stderrors.New("apply failed")},
+			wantErr:    "apply failed",
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			internal := NewInternalWithEngine(
+				nil,
+				tt.leaderView,
+				nil,
+				nil,
+				WithInternalAdminProposer(tt.proposer),
+			)
+			_, err := internal.ForwardAdminProposal(context.Background(), &pb.ForwardAdminProposalRequest{})
+			if tt.errTarget != nil {
+				require.ErrorIs(t, err, tt.errTarget)
+			}
+			if tt.wantErr != "" {
+				require.ErrorContains(t, err, tt.wantErr)
+			}
+		})
+	}
 }
🤖 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 `@adapter/internal_admin_proposal_test.go` around lines 63 - 82, Refactor
TestInternalForwardAdminProposalFailsClosed into a table-driven test covering
the follower and apply-response failure scenarios. Define each case with its
setup and expected error assertion, then iterate with t.Run while preserving the
existing ErrNotLeader and “apply failed” checks.

Source: Coding guidelines

🤖 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 `@kv/backup_scan.go`:
- Around line 393-401: Update preferredRoutedScanKey so that when current and
candidate have the same ownership state, it returns current to preserve the
stable ordering from the preceding sort.SliceStable. Keep returning current when
only currentOwned is true, and return candidate only when candidate is owned
while current is not.

---

Nitpick comments:
In `@adapter/internal_admin_proposal_test.go`:
- Around line 63-82: Refactor TestInternalForwardAdminProposalFailsClosed into a
table-driven test covering the follower and apply-response failure scenarios.
Define each case with its setup and expected error assertion, then iterate with
t.Run while preserving the existing ErrNotLeader and “apply failed” checks.
🪄 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: c7c9afca-f492-4391-b91d-516cb75aa5c6

📥 Commits

Reviewing files that changed from the base of the PR and between c9baa53 and bd8d262.

⛔ Files ignored due to path filters (2)
  • proto/internal.pb.go is excluded by !**/*.pb.go
  • proto/internal_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (11)
  • adapter/admin_backup.go
  • adapter/admin_backup_test.go
  • adapter/internal.go
  • adapter/internal_admin_proposal_test.go
  • kv/backup_scan.go
  • kv/leader_admin_proposer.go
  • kv/leader_admin_proposer_test.go
  • kv/shard_store_test.go
  • main.go
  • main_admin_test.go
  • proto/internal.proto
🚧 Files skipped from review as they are similar to previous changes (2)
  • main.go
  • adapter/admin_backup.go

Comment thread kv/backup_scan.go Outdated
@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD f5d9e2f addresses the latest review.

  • duplicate keys now retain stable route order when ownership states tie and select the candidate only when it is the sole captured owner
  • ambiguous enumerated full-range routes have a deterministic first-route contract
  • the Internal admin proposal failure cases are table-driven as requested

Validation:

  • go test ./kv -run TestBackup(ScannerMaterializesFromEnumeratedRoute|ScannersPreferCapturedOwnerForDuplicateKey) -count=1
  • go test ./adapter -run TestInternalForwardAdminProposal -count=1
  • go test -race ./adapter ./kv -run Test(InternalForwardAdminProposal|BackupScannerMaterializesFromEnumeratedRoute|BackupScannersPreferCapturedOwnerForDuplicateKey) -count=1
  • golangci-lint run ./adapter ./kv --timeout=5m --allow-parallel-runners
  • pre-commit full golangci-lint --fix: 0 issues

@codex review

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Signed HEAD f5d9e2f is unchanged. All required CI checks are green, merge state is clean, and the current-head CodeRabbit review has no new findings. Please complete the latest-head review. @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: f5d9e2f42e

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread kv/fsm_backup.go
Comment thread main.go Outdated
return 0, errors.Wrap(err, "backup: fence raft groups")
}
clock.Observe(shardStore.LastCommitTS())
readTS, err := clock.NextFenced()

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 Fence backup reads through the active timestamp allocator

When --tsoEnabled is in use, writes consume timestamps from the coordinator's BatchAllocator, but the backup fence bypasses that allocator and calls the underlying HLC directly. If the batch allocator already has unused timestamps reserved below the newly returned readTS, later writes can still commit below the backup timestamp after the pin is installed, breaking the snapshot guarantee; the fence needs to coordinate with or invalidate the same allocator that production writes use.

Useful? React with 👍 / 👎.

Comment thread adapter/internal.go
Comment thread adapter/internal.go
Comment thread kv/backup_scan.go Outdated
}
s.page = s.page[:0]
for _, item := range keys {
if _, ok := routeForRoutedKey(item, s.routes); ok {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep partition-routed SQS keys in backup scans

When backing up an SQS HT-FIFO queue whose partition resolver routes partitioned message keys to a non-default group, this filter drops those records: routeForRoutedKey only checks distribution routes using routeKey, while production ShardRouter.ResolveGroup consults the partition resolver before routeKey. The scanner does visit every group, but records from partition-only groups fail this ownership test and are neither counted nor streamed, so live backups of partitioned queues are incomplete.

Useful? React with 👍 / 👎.

Comment thread main.go Outdated
}
return func(ctx context.Context) (uint64, error) {
clock := coordinate.Clock()
if err := kv.LeaseReadAllGroupsThrough(coordinate, ctx); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Forward the read fence instead of requiring local leaders

In a multi-group deployment where this admin endpoint is follower for any Raft group, BeginBackup fails here before the leader-forwarding proposers are used: LeaseReadAllGroupsThrough on ShardedCoordinator calls each local engine's LinearizableRead, and the etcd engine rejects reads when State != Leader. After leader balancing, no node may lead every group, making the live backup RPC unusable unless callers happen to choose an all-leader endpoint.

Useful? React with 👍 / 👎.

Base automatically changed from design/live-backup-admin-api to design/live-backup-pin-substrate July 19, 2026 06:07
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Updated the signed HEAD to 830d645475ba651c32e43f5a65dfb1226909cf3f from the refreshed pin-substrate base.

Conflict-resolution evidence:

  • retained the captured route snapshot for backup scans
  • adopted filesystem forward-scan route normalization without consulting the live route catalog during pinned materialization
  • regenerated protobuf outputs from the merged source definitions
  • focused kv, adapter, and root-package tests pass
  • proto breaking/generation checks and full lint pass
  • current-head CI is green

Please review the latest head.

@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: 830d645475

ℹ️ 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 main.go Outdated
coordinate.Clock(),
relay,
internalTimestampOptions(coordinate)...,
append(internalTimestampOptions(coordinate), adapter.WithInternalAdminProposer(proposerForGroup(rt, shardGroups)))...,

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 Gate internal admin proposal forwarding

When the node gRPC listener is reachable by normal clients, this wires ForwardAdminProposal on /Internal unconditionally, even when the Admin service is disabled or protected by a bearer token; the existing admin interceptor only checks /Admin methods. That lets any caller submit arbitrary ProposeAdmin payloads (for example long-lived backup pins with low read_ts, or malformed control entries) to the Raft leader. Please gate this option on the same admin authentication/enablement path or add equivalent auth/validation to the internal RPC.

Useful? React with 👍 / 👎.

Comment thread adapter/admin_backup.go
Comment thread adapter/admin_backup.go Outdated
if readTS == 0 || readTS == ^uint64(0) {
return preparedBackup{}, status.Errorf(codes.FailedPrecondition, "%s", "backup read fence returned an invalid timestamp")
}
routes := s.backupStore.CaptureBackupRouteSnapshot(nil, nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Capture routes at the backup timestamp

When a route split or move applies after backupReadFence returns read_ts but before this call, the token stores the post-change route set while scans still read MVCC data at the older timestamp. Keys that were owned by the old group at read_ts can then be dispatched/materialized from the new captured owner, or have the old copy filtered out, so the live backup can omit data. Capture a route view that is valid at read_ts (or freeze route changes through the pin) instead of reading the live catalog here.

Useful? React with 👍 / 👎.

Comment thread adapter/admin_backup.go
Comment thread adapter/admin_backup.go Outdated
) (map[uint64]uint64, error) {
reserveEntry := kv.EncodeBackupReserveEntry(kv.BackupReserveEntry{PinID: pinID, ReadTS: readTS, Deadline: deadline})
if _, _, err := proposeBackupAll(ctx, []backupGroup{controlGroup}, reserveEntry); err != nil {
if errors.Is(err, kv.ErrTooManyActiveBackups) {

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 capacity errors across forwarded reserves

When BeginBackup is handled by a follower for the control group, the reserve proposal goes through ForwardAdminProposal; the leader returns ErrTooManyActiveBackups as a gRPC error, so this local errors.Is check no longer matches and the client gets Unavailable instead of ResourceExhausted. That makes capacity exhaustion look retryable and bypasses the API contract tested for local proposals; map the internal RPC error/status back to the sentinel or ResourceExhausted before this check.

Useful? React with 👍 / 👎.

Comment thread kv/fsm_backup.go
Comment thread adapter/admin_backup.go Outdated
if scanner == nil {
return status.Errorf(codes.Unavailable, "%s", "backup scanner is nil")
}
scanErr := streamBackupRecords(stream, scanner, selected)

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 streaming once the backup pin expires

For streams that last past tok.deadline when the client fails to renew, this starts the paging loop after only the upfront expiry check and never revalidates the session deadline. Once the tracker sweeper reaps the pin, FSM compaction can advance below tok.readTS while the RPC keeps reading pages, so a slow dump can silently become incomplete instead of failing when the pin is no longer valid. Recheck the deadline around each page/send or bind the stream lifetime to a live renewed pin.

Useful? React with 👍 / 👎.

@bootjp
bootjp force-pushed the design/live-backup-pin-substrate branch from 13d22a8 to 26d45ba Compare July 19, 2026 11:08
@bootjp
bootjp force-pushed the design/live-backup-control-plane branch from 830d645 to 6a85349 Compare July 19, 2026 11:09
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

History-only author-compliance repair completed at 6a85349d2deec210799349b420d35c9563666e55.

  • final tree is byte-identical to prior head 830d645475ba651c32e43f5a65dfb1226909cf3f
  • the PR now exposes one linear commit authored and committed by bootjp <contact@bootjp.me>
  • GitHub reports the commit signature as verified/valid
  • parent is sanitized Add live backup pin FSM substrate #1056 head 26d45baebb34ae6afbfaf39394e62deafcddc481

No runtime or source-tree semantics changed. CI is running on the reconstructed head.

@codex review

@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Current reconstructed head 6a85349d2deec210799349b420d35c9563666e55 is fully green. PR-visible authorship and signature verification pass, the final tree remains identical to the pre-rewrite head, and there are no current-head root findings. Please complete the latest-head review. @codex review

@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Latest signed head 6a85349d2deec210799349b420d35c9563666e55 remains mergeable with all required checks green and no current-head root findings. @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 (85e98d7):

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

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD 85e98d736480612bc71dba11ae4ca84685f5e676 addresses all four current-head findings.

Safety evidence:

  • backup routes are captured from the runtime catalog-owner CatalogStore, after every data-group pin/floor commits
  • replicated backup pins persist a monotone per-group timestamp floor; delayed raw, one-phase, and PREPARE writes at or below the cut fail closed
  • pre-pin COMMIT/ABORT remains resolvable, so the scanner either includes committed data or fails BeginBackup on a pending primary
  • applying the floor advances HLC state and invalidates each process-local cached TSO window once per cut
  • reserved TSO group 0 is excluded from backup fan-out and token encoding
  • forwarded lease-read RPCs require and carry the configured admin bearer token

Caller/semantic audit:

  • requestCommitTS has one production consumer, kvFSM.applyRequestErr; every raw and transaction phase passes through the floor check
  • raw/NONE/PREPARE/COMMIT/ABORT constructors and handlers were audited; only COMMIT/ABORT deliberately bypass the stale-write rejection
  • coordinator and Internal timestamp callers route through the same coordinator allocator, so TSO-window invalidation covers both paths
  • runtime catalog mutations route through the coordinator/FSM; direct CatalogStore.Save is bootstrap-only

Validation:

  • focused unit/integration tests across ./kv ./adapter .: pass
  • the same focused set under -race: pass
  • go test ./adapter -run Backup -count=1: pass
  • related main admin/backup tests: pass
  • go test ./... -run ^$: pass
  • golangci-lint run ./kv ./adapter . --timeout=5m --allow-parallel-runners: 0 issues
  • full adapter package run reached the existing 10-minute multi-node/election timeout; all changed backup/auth/timestamp paths pass independently

@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: 85e98d7364

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/admin_backup.go Outdated
Comment thread main.go Outdated
Comment on lines +2493 to +2498
if err := kv.LeaseReadAllGroupsThrough(coordinate, ctx); err != nil {
return 0, errors.Wrap(err, "backup: fence raft groups")
}
lastCommitTS := shardStore.LastCommitTS()
clock.Observe(lastCommitTS)
readTS, err := allocateBackupReadTimestamp(ctx, coordinate, lastCommitTS)

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 Allocate backup read_ts after remote leader timestamps

When BeginBackup is served by a node whose local replica is behind another shard leader, this only observes the local stores' LastCommitTS() after LeaseReadAllGroups. The lease read fences leader applied indexes, but it does not fetch the leaders' highest commit timestamps, and non-TSO forwarded writes are stamped on those leaders; the backup can therefore choose read_ts below a write that completed before the fence and then omit that committed version from the scan.

Useful? React with 👍 / 👎.

Comment thread main.go
Comment thread kv/fsm_backup.go
Comment thread internal/backup/live.go Outdated
Comment on lines +36 to +40
case hasAnyBackupPrefix(key, DDBTableMetaPrefix, DDBTableGenPrefix, DDBItemPrefix, DDBGSIPrefix):
return scopeForDDBKey(key)
case hasAnyBackupPrefix(key,
S3BucketMetaPrefix, S3BucketGenPrefix, S3ObjectManifestPrefix,
S3UploadMetaPrefix, S3UploadPartPrefix, S3BlobPrefix, S3GCUploadPrefix, S3RoutePrefix,

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 Drop generation counters from live scopes

When a DynamoDB table or S3 bucket has been deleted before read_ts, its generation counter can intentionally remain even though the resource is no longer user-visible. Including the generation prefixes in scope discovery makes ListAdaptersAndScopes and expected_keys report phantom resources, and StreamBackup can emit only operational generation records that the decoder later drops.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

Addressed the latest live-backup P1 findings in 2f927a6.

Changes:

  • fail closed when backup peer probing cannot resolve a member address
  • require admin authentication on internal admin proposal and lease-read forwarding
  • allocate backup read_ts after fencing all data-group leaders and carrying leader last_commit_ts through ForwardLeaseRead
  • wait for local backup pin apply before catalog capture, then read catalog routes from the fenced catalog owner
  • persist backup timestamp floors through FSM state/snapshots and reject stamped writes at or below the floor
  • validate backup cuts against prepared lock-only transactions before counting/scanning
  • exclude the reserved TSO group from backup group snapshots
  • keep partition-routed SQS keys in backup scans using the same partition resolver as runtime dispatch, including fail-closed unresolved partition keys
  • reserve the whole txn internal namespace for raw user writes so the backup floor key cannot be written directly

Verification:

  • go test ./adapter ./kv ./internal/backup ./cmd/server -run 'TestInternalForwardLeaseReadUsesLeaderBarrier|TestAdminTokenAuthProtectsInternalAdminMethods|TestBeginBackupWaitsForLocalPinApplyBeforeCatalogCapture|TestValidateBackupSnapshotAtRejectsPreparedInsert|TestScopeForKey|TestLiveDecoderAcceptsSQSQueueSequenceSideRecord|TestAdminBackupReadFenceUsesCoordinatorTimestampAllocator|TestShardedCoordinatorLeaseReadAllGroups|TestBackupScannerKeepsPartitionResolvedOwnerForSQSKey|TestBackupScannerFailsClosedOnUnresolvedPartitionedSQSKey|TestBackupScannersPreferCapturedOwnerForDuplicateKey' -count=1
  • go test ./... -run '^$'
  • make -C proto breaking
  • golangci-lint run ./adapter ./kv ./internal/backup ./cmd/server --timeout=5m
  • go test ./adapter ./kv ./internal/backup ./cmd/server -count=1

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

  • distribution/engine.go
  • kv/coordinator.go
  • kv/fsm.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@bootjp

bootjp commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD dd9f499 addresses the remaining live-backup safety windows.

Changes:

  • StreamBackup rechecks token expiry inside the record loop and before each send, so a stream fails closed once the pin is no longer live.
  • FSM snapshot generation now rejects snapshots while a group-scoped live backup pin is active, avoiding snapshots that would drop in-memory retention state.
  • streamBackupRecords complexity was reduced by extracting scope selection.

Evidence:

  • go test ./kv -run 'TestActiveTimestampTrackerOldestBackupForGroupIgnoresReadsAndOtherGroups|TestFSMSnapshotRejectsActiveBackupPin|TestBackupTimestampFloorSurvivesSnapshotRestore|TestApplyBackupRejectsStaleStampedWrites|TestApplyBackupFencesPreallocatedWrites|TestBackupTimestampFloorKeyRejectsRawMutation|TestApplyBackupAllowsResolutionOfPrePinTransaction' -count=1\n- go test ./adapter -run 'Test(StreamBackup|ListBackupScopes|RenewBackupReleasesResourcesWhenTokenExpiresDuringRenewal|RenewBackupRetriesAndRejectsTamperedToken|SnapshotBackupGroupsExcludesReservedTSOGroup|BeginBackupWaitsForLocalPinApplyBeforeCatalogCapture|BeginBackupLifecycleAndBaselineAtPinnedTimestamp)' -count=1\n- golangci-lint run ./adapter ./kv ./internal/backup ./cmd/server --timeout=5m\n- go test ./... -run '^$'\n- go test ./adapter ./kv -count=1\n\nNotes for prior latest-head comments that are already covered in this HEAD:\n- Internal ForwardAdminProposal and ForwardLeaseRead are in the AdminTokenAuth protected-method set and production registration uses those interceptors.\n- snapshotBackupGroups skips raft group 0.\n- snapshotBackupMembers now fails closed when a live member has no probe address after seed merge.\n- the backup timestamp floor rejects already-stamped raw/prepare/one-phase writes at or below read_ts, while ValidateBackupSnapshotAt fails on unresolved prepared transactions.\n- route catalog capture waits for local pin apply and reads the catalog snapshot at read_ts from the catalog owner path.\n\n@codex review

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

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

Anchored files changed in this PR head (dd9f499):

  • distribution/engine.go
  • kv/coordinator.go
  • kv/fsm.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd9f499f93

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/admin_backup.go Outdated
Comment on lines +535 to +536
scanErr := streamBackupRecords(stream, scanner, selected, func() error {
return s.requireUnexpiredBackupToken(tok)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor renewed backup deadlines while streaming

For a long-running backup where the producer renews every ttl/3, this closure keeps checking the immutable token decoded when StreamBackup started. RenewBackup extends backupSessions and returns a replacement token, but an already-open server stream cannot swap its request token, so the stream will fail at the original deadline even though the replicated pin is still live. Recheck the stored session/active pin deadline for this pin instead of the stale token deadline so renewed multi-hour dumps can continue safely.

Useful? React with 👍 / 👎.

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

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

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

Anchored files changed in this PR head (6608a1c):

  • distribution/engine.go
  • kv/coordinator.go
  • kv/fsm.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@bootjp

bootjp commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD 6608a1c addresses the latest live-backup renewal and forwarding findings.

Changes:

  • RenewBackup waits for renewed group pins to apply locally before publishing the renewed session deadline or token.
  • StreamBackup checks the endpoint session deadline during streaming, so an already-open stream honors successful renewals instead of the original request token deadline.
  • Forwarded admin proposal capacity failures now preserve ResourceExhausted across the internal RPC path.

Validation:

  • go test ./adapter -run 'Test(StreamBackup(HonorsRenewedSessionDeadline|StopsWhenTokenExpiresMidStream)|RenewBackup(WaitsForLocalPinApplyBeforePublishing|RetriesAndRejectsTamperedToken|ReleasesResources)|BeginBackupMaps(ForwardedCapacityReservationStatusToResourceExhausted|CapacityReservationToResourceExhausted)|InternalForwardAdminProposal)' -count=1\n- go test ./adapter -run 'Test.*(BeginBackup|RenewBackup|StreamBackup|ListBackupScopes|BackupProtocolVersion|SnapshotBackupGroups|InternalForwardAdminProposal)' -count=1\n- go test ./kv -count=1\n- go test ./... -run '^$'\n- golangci-lint run ./... --timeout=5m\n\nAll current non-outdated review threads have been resolved. CI is running for this head.\n\n@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

for _, route := range routes {

P2 Badge Include partition-only groups in backup scans

When an SQS FIFO partition map routes a queue partition to a configured Raft group that does not own any byte-range catalog route, this loop never visits that group's store because the captured snapshot only contributes distribution routes; the partition resolver is only consulted after a key has already been found. validateShardRanges only checks that referenced ranges point at known groups, so such partition-only groups are allowed, and live backups will under-count and omit their partitioned SQS records even though BeginBackup pinned all groups. Please carry the pinned group list into the backup scan source, or otherwise add partition-routed groups to the snapshot before scanning.

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/admin_backup.go Outdated
if err != nil {
return nil, status.Errorf(codes.Internal, "encode renewed backup token: %v", err)
}
s.extendBackupSession(tok)

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 Fail renewal when the backup session was ended

When EndBackup runs concurrently after this RPC's snapshot check, it can delete the session while renewBackupGroups is still waiting on Raft; extendBackupSession then silently no-ops for the missing session, but RenewBackup still returns a fresh token after re-pinning. In that interleaving the caller either gets a token for pins that EndBackup subsequently released, or the renew resurrects raft pins with no endpoint session so later stream/list calls fail and the pins linger until TTL. Please make the session extension conditional/failing with compensation, or serialize RenewBackup and EndBackup for the same pin.

Useful? React with 👍 / 👎.

Comment thread adapter/admin_backup.go
@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

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

Anchored files changed in this PR head (b3b8ab9):

  • distribution/engine.go
  • kv/coordinator.go
  • kv/fsm.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@bootjp

bootjp commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

Updated signed head b3b8ab9f7fb2d4c161d4226bcafe75e45c977b1d addresses the current live-backup review findings.

Changes:

  • RenewBackup now treats endpoint session extension as part of success. If the session was ended while group renewals were in flight, it compensates the renewed pins/reservation and returns FailedPrecondition instead of handing out a token with no live session.
  • Scoped StreamBackup now uses a key filter before value materialization when the store supports filtered backup scanning, so unselected scopes are skipped as keys rather than loading/proxying their values first.
  • Added regression coverage for concurrent session removal during renew and for scoped streams materializing only selected-scope values.

Validation:

  • go test ./adapter -run 'Test(StreamBackupUsesPinTimestampAndScopeFilter|RenewBackupFailsWhenSessionEndsDuringRenew|StreamBackupHonorsRenewedSessionDeadline|StreamBackupFailsClosedWithoutPinnedRouteSnapshot)' -count=1\n- go test ./adapter -run 'Test.*Backup' -count=1\n- go test ./kv -run 'TestBackupScanner|TestBackupKeyScanner' -count=1\n- go test ./kv -count=1\n- golangci-lint run ./adapter ./kv --timeout=5m\n- go test ./... -run '^$'\n- git diff --check\n\nCaller audit:\n- extendBackupSession is only used by finishRenewBackup; failed extension now triggers compensation.\n- Filtered backup scanning is only used by scoped StreamBackup; unscoped streams keep the existing scanner path.\n\n@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

for _, route := range routes {

P1 Badge Include partition-only groups in backup scans

When --sqsFifoPartitionMap routes FIFO partitions to a Raft group that has no byte-range route in the distribution catalog, this loop never scans that group at all; routeForRoutedKey only gets a chance to accept partitioned SQS keys after their group has been enumerated here. Since BeginBackup pins every registered group but the captured route snapshot can omit partition-only groups, expected counts and streams silently miss those SQS records.

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread kv/backup_scan.go Outdated
Comment thread kv/backup_scan.go
Comment thread adapter/admin_backup.go Outdated
Comment thread kv/fsm_backup.go Outdated
@bootjp

bootjp commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

Pushed d91744b.

Fixed current review findings:

  • Backup lock validation now evaluates commit/rollback records at the backup read timestamp, so post-cut decisions stay locked.
  • Backup route snapshots can scan pinned groups for partition-resolver-owned keys that have no byte-range route.
  • Filtered backup stream predicates run after route ownership is established.
  • ListAdaptersAndScopes aborts if the backup session expires while scanning.
  • Capacity reservation no longer installs the durable timestamp fence before the pin is accepted.

Caller audit:

  • scanBackupScopeCounts has two callers: BeginBackup baseline uses no live-session check before registration; ListAdaptersAndScopes passes requireLiveBackupSession.
  • BackupRouteSnapshotWithScanGroups is only used by admin backup after pinning, plus tests.
  • ValidateBackupSnapshotAt, NewFilteredBackupScannerAtSnapshot, and backupSubtypeReserve callers are covered in kv/adapter backup tests.

Validation:

  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./kv ./adapter -run 'Test(ValidateBackupSnapshotAtRejectsPreparedInsert|ValidateBackupSnapshotAtRejectsPostCutTxnDecision|BackupScannersIncludePartitionOnlyGroups|FilteredBackupScannerAppliesFilterAfterOwnership|BackupScannerKeepsPartitionResolvedOwnerForSQSKey|BackupScannerFailsClosedOnUnresolvedPartitionedSQSKey|ApplyBackupReserveDoesNotInstallTimestampFloor|ApplyBackupFencesPreallocatedWrites|ListBackupScopesFailsWhenSessionEndsDuringScan|ListBackupScopesReportsScannerCloseError|StreamBackupUsesPinTimestampAndScopeFilter|RenewBackupFailsWhenSessionEndsDuringRenew)' -count=1
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./kv -count=1
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./adapter -run 'Test.*Backup' -count=1
  • GOCACHE=$(pwd)/.cache GOTMPDIR=$(pwd)/.cache/tmp go test ./... -run '^$'
  • GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./adapter ./kv --timeout=5m
  • git diff --check

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

  • distribution/engine.go
  • kv/coordinator.go
  • kv/fsm.go
  • kv/sharded_coordinator.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: d91744b04b

ℹ️ 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 1eddbb1 into design/live-backup-pin-substrate Jul 23, 2026
8 checks passed
@bootjp
bootjp deleted the design/live-backup-control-plane branch July 23, 2026 15:14
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