Skip to content

Add live point-in-time logical backup producer#1128

Open
bootjp wants to merge 4 commits into
design/live-backup-control-planefrom
design/live-backup-producer
Open

Add live point-in-time logical backup producer#1128
bootjp wants to merge 4 commits into
design/live-backup-control-planefrom
design/live-backup-producer

Conversation

@bootjp

@bootjp bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • add the live point-in-time logical backup producer and CLI over the backup control-plane RPCs
  • renew the distributed pin while streaming, stop renewal before publishing MANIFEST.json, and always end the backup session
  • encode DynamoDB, S3, Redis, and SQS scopes with checksums and expected-key validation
  • add crash, renewal-race, cross-adapter, and TTL-expiry acceptance coverage
  • add the operator backup/restore runbook and promote the Phase 1 design document to Implemented

Stack

Risk

The data path is opt-in through the new backup CLI and admin RPCs. The main correctness risks are publishing a completion marker after pin loss and reading from a changing route catalog; both are covered by pinned-route and renewal-quiescence tests.

Verification

  • go test ./internal/backup ./cmd/elastickv-backup -count=1
  • go test -race ./internal/backup -run 'Test(RunLiveBackup|ProducerCrash|CrossAdapterConsistency|ExpectedKeysBaseline)' -count=1
  • go test ./adapter -run 'Test(BeginBackup|RenewBackup|StreamBackup|ListBackup|BackupProtocol|InternalForwardAdminProposal)' -count=1
  • go test ./kv -run 'Test(Backup|BeginBackup|Pin|BackupScanner|ShardStoreScanAt_RoutesFilesystem)' -count=1
  • make -C proto gen followed by a clean generated diff
  • golangci-lint run ./... --timeout=15m --allow-parallel-runners -j 2
  • git diff --check
  • no references remain to docs/design/2026_04_29_proposed_logical_backup.md

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

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

Run ID: 67f89093-9585-4f9b-9e84-20f403836f9f

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

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 signed HEAD is 2d468c5a169c93696b0037d66c644b5378685898.

Verification:

  • producer and CLI tests pass
  • focused producer acceptance tests pass under -race
  • control-plane adapter and backup scanner tests pass
  • proto breaking/generation checks pass with no generated diff
  • full lint reports 0 issues
  • git diff --check passes
  • the former proposed design path has no remaining references

Please review the latest head.

@codex review
@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

@bootjp: Reviewing the latest signed HEAD (2d468c5a169c93696b0037d66c644b5378685898) now.

✅ 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 Phase 1 of the logical backup feature, enabling point-in-time consistent logical backups from a running elastickv cluster. It introduces the elastickv-backup CLI tool, adds server-side configurations for active backup pins, implements the live backup producer lifecycle (including streaming and renewal), and updates design documents and runbooks. The review feedback highlights a potential nil pointer dereference on Windows when loading system certificates, portability issues with using os.Link for manifest publication on filesystems that do not support hard links, and noisy warning logs during graceful shutdown due to unsuppressed context cancellation 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 cmd/elastickv-backup/main.go Outdated
Comment thread internal/backup/finalize.go Outdated
Comment thread internal/backup/live_producer.go
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Updated the latest head d5a5934945c060183a6409fd0cc81c2010a7672b for all three findings:

  • fall back to a new certificate pool when SystemCertPool returns either an error or a nil pool
  • publish the final manifest with same-directory os.Rename, while preserving the pre-existing marker guard
  • keep graceful cancellation warning-free and retain warnings for genuine renewal failures

Verification:

  • go test ./internal/backup ./cmd/elastickv-backup -count=1
  • go test -race ./internal/backup -run 'Test(RunLiveBackup|ProducerCrash|CrossAdapterConsistency|ExpectedKeysBaseline)' -count=1
  • GOOS=windows GOARCH=amd64 go test -c -o /tmp/elastickv-backup-windows.test.exe ./cmd/elastickv-backup
  • golangci-lint run ./internal/backup/... ./cmd/elastickv-backup/... --timeout=5m

Caller audit for the manifest error-semantics change found one production caller (RunLiveBackup), which propagates finalization errors, leaves no manifest, and still executes EndBackup.

@codex review

@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Current head d5a5934 is fully green and all prior inline findings are resolved. @codex review

@bootjp
bootjp force-pushed the design/live-backup-control-plane branch from 830d645 to 6a85349 Compare July 19, 2026 11:09
@bootjp
bootjp force-pushed the design/live-backup-producer branch from d5a5934 to 83f3a9f 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 83f3a9f6aa23fd632aba203228284ddda27c4d53.

  • final tree is byte-identical to prior head d5a5934945c060183a6409fd0cc81c2010a7672b
  • 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 logical backup control plane #1119 head 6a85349d2deec210799349b420d35c9563666e55

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 83f3a9f6aa23fd632aba203228284ddda27c4d53 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 83f3a9f6aa23fd632aba203228284ddda27c4d53 remains mergeable with all required checks green and no current-head root findings. @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: 83f3a9f6aa

ℹ️ 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 cmd/elastickv-backup/main.go
Comment thread cmd/elastickv-backup/main.go
Comment thread cmd/elastickv-backup/main.go Outdated
Comment thread internal/backup/live_producer.go
Comment thread internal/backup/live_producer.go
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD 56277d2cbc957487a967e03263167fb3cb5ed38f addresses all five current-head findings.

Evidence:

  • live producer modes unsupported by the shipped native restore path fail before dial and at the library entrypoint
  • TLS server-name-only and CA plus skip-verify combinations fail closed
  • non-empty one/two-record scopes require at least one returned record
  • server baseline and stream now exclude decoder-dropped S3 generation/incomplete-upload and SQS side rows; producer counts only after decoder acceptance
  • runbook and implemented design no longer recommend unrestorable modes

Verification:

  • go test ./cmd/elastickv-backup ./internal/backup ./adapter -run 'Test(ParseFlags|LoadTransportCredentials|ScopeForKey|RunLiveBackup|ExpectedKeysBaseline|ValidateLiveBackupRestoreCompatibility|Backup|StreamBackup|ListBackupScopes)' -count=1 -timeout=300s
  • focused -race tests for the backup CLI and producer
  • golangci-lint run ./cmd/elastickv-backup ./internal/backup ./adapter --timeout=5m --allow-parallel-runners reports 0 issues
  • git diff --check

Caller audit for the ScopeForKey semantic change found the baseline scan, server stream, producer stream validator, and tests; baseline and stream now share the same retained-record classification. CI is running for this 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

case bytes.HasPrefix(key, []byte(DDBTableGenPrefix)):
return ddbScopeFromDirectSegment(key, DDBTableGenPrefix)

P2 Badge Exclude DynamoDB generation counters from scopes

When a DynamoDB table has been deleted, the live adapter leaves the !ddb|meta|gen| counter behind while the table schema is gone, and the decoder's HandleTableGen is a no-op. Classifying that counter as a scoped table makes BeginBackup/ListAdaptersAndScopes select a deleted table and lets the live manifest publish dynamodb.tables for a scope that has no _schema.json; the native restore path then emits no table even though the manifest says it was dumped. Treat this like the other generation counters that are already unscoped, or only count it when the schema record is present.

ℹ️ 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/backup/live_producer.go Outdated
Comment thread internal/backup/live_producer.go
Comment thread internal/backup/live_producer.go

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Latest signed HEAD 7d4834f1211074152a9238e98dc23d11c31ad358 addresses all current-head findings.

  • DynamoDB generation counters are now unscoped, so deleted-table counters cannot create manifest-only tables.
  • Each pin renewal is bounded by TTL/3 and the remaining expiry; deadline failure cancels the dump and blocks manifest publication.
  • Default all-scope selection includes every non-empty Begin baseline scope even when the later List response omits it.
  • S3 validation now counts only records retained after finalize: bucket metadata, active manifests, and manifest-selected chunks.

Verification:

  • go test ./internal/backup ./cmd/elastickv-backup -count=1 -timeout=300s
  • focused adapter backup tests
  • focused producer tests under -race
  • golangci-lint run ./internal/backup ./cmd/elastickv-backup ./adapter --timeout=5m --allow-parallel-runners reports 0 issues
  • git diff --check

Caller audit for ScopeForKey covers the server baseline, server stream, producer validator, and tests; all now exclude the same generation counter.

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

ℹ️ 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/backup/live.go Outdated
Comment thread internal/backup/live.go Outdated
Comment thread internal/backup/live.go Outdated
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Evidence for current HEAD ed0498de2d24eeabd7b07c25a637cfb2d8438841:

  • Finalized retained counts now cover SQS, DynamoDB, Redis, and S3.
  • Deleted SQS generation-only state, schema-less DynamoDB items, Redis orphan TTLs, and Redis stream entries without metadata cannot mask a short stream or publish a misleading manifest.
  • Redis derivable delta, claim, and score indexes are excluded consistently from Begin baseline and Stream filtering.
  • Caller audit covered all ScopeForKey, FinalizedScopeCounts, and retained-count production callers.
  • All three current review threads were replied to and resolved.

Validation:

  • go test ./internal/backup ./cmd/elastickv-backup -count=1 -timeout=300s
  • focused go test -race ./internal/backup
  • golangci-lint run ./internal/backup ./cmd/elastickv-backup --timeout=5m --allow-parallel-runners (0 issues)
  • signed author and committer: bootjp <contact@bootjp.me>

Requesting a fresh latest-head review.

@codex review

@bootjp
bootjp force-pushed the design/live-backup-producer branch from ed0498d to 53e6402 Compare July 19, 2026 13:44
@bootjp

bootjp commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Rebase evidence for current HEAD 53e64025e8efaa147b7759cfb2d2ad434dfb9a64:

  • Rebased onto Add live logical backup control plane #1119 HEAD f30bd74b5e64064d6a846ac640cf35d639a9d03b.
  • git range-diff reports all four producer commits unchanged (=).
  • All four PR-visible commits retain author and committer bootjp <contact@bootjp.me> and GitHub signature verification is verified: true.
  • The retained-count findings remain fixed in rebased commit 53e64025.

Post-rebase validation:

  • go test ./internal/backup ./cmd/elastickv-backup ./kv . -count=1 -timeout=600s
  • go test ./adapter -run 'Backup|ForwardLeaseRead|ForwardAdminProposal' -count=1 -timeout=300s
  • focused producer race tests
  • golangci-lint run ./internal/backup ./cmd/elastickv-backup ./adapter ./kv . --timeout=5m --allow-parallel-runners (0 issues)
  • git diff --check

The broad unfiltered adapter suite reached its 600-second timeout in Raft integration cleanup; the focused backup/forwarding adapter suite passed and GitHub CI is now running on this head.

Requesting a fresh latest-head review after the dependency rebase.

@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: 53e64025e8

ℹ️ 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/backup/s3.go
Comment on lines +560 to +561
if object.manifest == nil || (bucket.activeGen != 0 && object.generation != bucket.activeGen) {
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Align S3 retained counts with expected counts

When a bucket has blob rows that are intentionally excluded from the native dump — for example chunks from an in-flight multipart upload, stale upload attempt, or old bucket generation — this retained-count path drops them, but the BeginBackup/ListAdaptersAndScopes baseline still counts every !s3|blob|... row for the bucket via ScopeForKey. That makes validateExpectedLiveCounts see a shortfall and abort otherwise valid live backups once enough excluded chunks exist, even though the default producer mode is documented to omit incomplete uploads/orphans. Please make the expected-count side use the same retained S3 denominator or otherwise exclude those blob rows before comparison.

Useful? React with 👍 / 👎.

if err := d.writeEffectiveDDBItems(itemsDir, st.name, hashKey, rangeKey, items); err != nil {
return err
}
st.retained = 1 + retainedDDBItemRecords(st.itemsByGen, emitOrder)

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 Align DynamoDB retained counts with expected counts

When an active table still has leftover item rows from a non-active/non-migration generation, the baseline scan counts those !ddb|item|... records for the table via ScopeForKey, but this retained count only includes generations selected for output. If the stale generation has more than the shortfall tolerance, validateExpectedLiveCounts aborts an otherwise valid backup even though the encoder intentionally drops stale-generation rows to avoid resurrecting deleted data. Please make the baseline use the same generation-filtered denominator or keep these counts comparable.

Useful? React with 👍 / 👎.

Comment thread internal/backup/sqs.go
if st.genSeen {
count++
}
visible, _ := filterStaleGenMessages(st.messages, st.activeGen)

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 Align SQS retained counts with expected counts

When a queue has residual old-generation messages after a PurgeQueue/DeleteQueue while the reaper is still cleaning up, the BeginBackup/ListAdaptersAndScopes baseline counts every !sqs|msg|data|... row for that queue, but this retained count filters those stale messages out. Once the stale rows exceed the tolerated shortfall, live backup fails even though flushQueue deliberately drops those messages to preserve the current user-visible queue. Please apply the same generation filtering to the expected-count baseline or otherwise compare like with like.

Useful? React with 👍 / 👎.

Comment on lines +728 to +731
if st.sawWide {
count += uint64(len(st.members))
} else {
count += boolRecordCount(st.legacySeen)

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 Align Redis ZSET retained counts with expected counts

When Redis has many zsets in the dual legacy+wide-column state left by migration, ScopeForKey still counts each !redis|zset|... legacy blob in the BeginBackup/ListAdaptersAndScopes baseline, but this retained-count path drops that record as soon as any wide member exists because the live read path prefers the wide rows. That leaves actual counts lower than the baseline and can make validateExpectedLiveCounts abort otherwise valid live backups of migrated Redis data; please exclude stale legacy zset blobs from the baseline or count them consistently.

Useful? React with 👍 / 👎.

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