Skip to content

store: add migration version import export - #1085

Merged
bootjp merged 49 commits into
mainfrom
design/hotspot-split-m2-store-export
Aug 31, 2026
Merged

store: add migration version import export#1085
bootjp merged 49 commits into
mainfrom
design/hotspot-split-m2-store-export

Conversation

@bootjp

@bootjp bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add raw MVCC version export/import APIs for migration chunks across memory and Pebble stores.
  • Persist per-bracket import acknowledgements and target-local migration HLC floors.
  • Preserve tombstones and expire_at metadata, including sparse zero-version progress chunks.

Tests

  • go test ./store -count=1 -timeout=180s
  • GOCACHE=$(pwd)/.cache GOLANGCI_LINT_CACHE=$(pwd)/.golangci-cache golangci-lint run ./store ./kv --timeout=5m
  • go test ./store ./kv ./distribution ./adapter -run TestNonExistentForCompileOnly -count=1 -timeout=180s
  • git diff --check

Author: bootjp

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 116 files, which is 16 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 00728c0f-040d-42e0-a147-c36950b7a660

📥 Commits

Reviewing files that changed from the base of the PR and between ddbb0a5 and d2d620d.

⛔ Files ignored due to path filters (1)
  • proto/internal.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (116)
  • .github/workflows/golangci-lint.yml
  • .github/workflows/redis-proxy-docker.yml
  • adapter/distribution_server.go
  • adapter/distribution_server_test.go
  • adapter/dynamodb_cleanup_retry_test.go
  • adapter/dynamodb_item_write.go
  • adapter/dynamodb_onephase_dedup_test.go
  • adapter/dynamodb_schema.go
  • adapter/dynamodb_transact.go
  • adapter/grpc.go
  • adapter/redis_collection_ttl.go
  • adapter/redis_collection_ttl_test.go
  • adapter/redis_compat_helpers.go
  • adapter/redis_delta_compactor.go
  • adapter/redis_delta_compactor_test.go
  • adapter/redis_exec_dedup_test.go
  • adapter/redis_list_dedup_test.go
  • adapter/redis_lists.go
  • adapter/redis_lua_compat_test.go
  • adapter/redis_lua_context.go
  • adapter/redis_lua_list_holes_test.go
  • adapter/redis_retry.go
  • adapter/redis_retry_test.go
  • adapter/redis_stream_cmds.go
  • adapter/redis_ttl_inline_migrator.go
  • adapter/retryable_write_fence_test.go
  • adapter/route_write_fence.go
  • adapter/s3.go
  • adapter/s3_admin.go
  • adapter/s3_admin_test.go
  • adapter/s3_cleanup_retry_test.go
  • adapter/sqs_messages.go
  • adapter/sqs_reaper.go
  • adapter/sqs_receive_route_fence_test.go
  • adapter/sqs_redrive.go
  • distribution/catalog.go
  • distribution/catalog_test.go
  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • distribution/split_job_catalog.go
  • distribution/split_job_catalog_test.go
  • docs/design/2026_04_14_implemented_etcd_snapshot_disk_offload.md
  • docs/design/2026_04_27_implemented_keyviz_cluster_fanout.md
  • docs/design/2026_04_29_implemented_snapshot_logical_decoder.md
  • docs/design/2026_04_29_proposed_logical_backup.md
  • docs/design/2026_06_02_implemented_idempotent_snapshot_restore.md
  • docs/design/2026_06_12_proposed_scaling_roadmap.md
  • internal/backup/redis_list.go
  • internal/backup/redis_list_test.go
  • internal/raftengine/engine.go
  • internal/raftengine/etcd/dispatch_report_test.go
  • internal/raftengine/etcd/engine.go
  • internal/raftengine/etcd/engine_applied_index_test.go
  • internal/raftengine/etcd/engine_test.go
  • internal/raftengine/etcd/fsm_snapshot_file.go
  • internal/raftengine/etcd/fsm_snapshot_file_test.go
  • internal/raftengine/etcd/grpc_transport.go
  • internal/raftengine/etcd/grpc_transport_test.go
  • internal/raftengine/etcd/snapshot_spool.go
  • internal/raftengine/etcd/snapshot_spool_space_other.go
  • internal/raftengine/etcd/snapshot_spool_space_unix.go
  • internal/raftengine/etcd/snapshot_spool_test.go
  • internal/raftengine/etcd/wal_store.go
  • internal/raftengine/etcd/wal_store_skip_gate_test.go
  • internal/raftengine/statemachine.go
  • internal/s3keys/keys.go
  • internal/s3keys/keys_test.go
  • kv/coordinator.go
  • kv/fsm.go
  • kv/fsm_migration_fence_test.go
  • kv/leader_routed_store.go
  • kv/leader_routed_store_test.go
  • kv/migrator_filter.go
  • kv/migrator_lock_drain.go
  • kv/migrator_lock_drain_test.go
  • kv/route_history.go
  • kv/shard_key.go
  • kv/shard_store.go
  • kv/sharded_coordinator.go
  • kv/sharded_coordinator_partition_test.go
  • kv/sharded_coordinator_txn_test.go
  • kv/transcoder.go
  • kv/txn_keys.go
  • main.go
  • monitoring/grafana/dashboards/elastickv-redis-summary.json
  • monitoring/hotpath.go
  • monitoring/hotpath_test.go
  • proto/internal.proto
  • proxy/blocking.go
  • proxy/command.go
  • proxy/config.go
  • proxy/dualwrite.go
  • proxy/metrics.go
  • proxy/noop_backend.go
  • proxy/proxy.go
  • proxy/proxy_test.go
  • proxy/raw_redis_proxy.go
  • proxy/raw_redis_proxy_test.go
  • store/hash_helpers.go
  • store/list_helpers.go
  • store/list_helpers_test.go
  • store/lsm_migration.go
  • store/lsm_store.go
  • store/lsm_store_test.go
  • store/migration_versions.go
  • store/migration_versions_test.go
  • store/mvcc_store.go
  • store/mvcc_store_snapshot_test.go
  • store/set_helpers.go
  • store/store.go
  • store/stream_helpers.go
  • store/stream_helpers_test.go
  • store/wide_column_helpers_test.go
  • store/zset_helpers.go

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


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

Copy link
Copy Markdown
Owner Author

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

  • store/mvcc_store.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@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 adds support for range migrations by implementing ExportVersions, ImportVersions, and MigrationHLCFloor across the storage implementations, allowing raw MVCC versions (including tombstones and TTL metadata) to be exported and imported idempotently. The review feedback highlights a potential synchronization/performance improvement by using atomic operations for updating s.lastCommitTS, and points out an O(N^2) complexity issue in finishExportIfLimited when calculating exported version sizes, suggesting tracking the size incrementally instead.

Important

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

Comment thread store/lsm_migration.go Outdated
Comment thread store/migration_versions.go
@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

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

  • store/mvcc_store.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: edf74ffa0e

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread store/migration_versions.go
Comment thread store/lsm_migration.go Outdated
Comment on lines +80 to +84
iterOpts := &pebble.IterOptions{
LowerBound: encodeKey(opts.StartKey, math.MaxUint64),
}
if opts.EndKey != nil {
iterOpts.UpperBound = encodeKey(opts.EndKey, math.MaxUint64)

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 Use user-key bounds for Pebble export ranges

For Pebble, bounding the iterator with encodeKey(start/end, MaxUint64) compares against the raw encoded key (userKey || invertedTS), not just the user key, so prefix-related keys cross the range boundary. For example, exporting ["aa", "b") can still emit key "a" because "a" || ^ts sorts after "aa" || 0, while exporting [nil, "aa") can omit the same "a" row; range migration would then copy keys from outside the bracket or miss keys inside it. The export loop needs to either avoid these raw bounds or re-check userKey >= StartKey in addition to the existing end check.

Useful? React with 👍 / 👎.

Comment thread store/mvcc_store.go Outdated
Comment on lines +68 to +69
migrationAcks map[string]migrationImportAck
migrationHLCFloors map[uint64]uint64

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 Reset migration metadata on in-memory restore

These new maps live outside the snapshot tree, but restoreStreamingSnapshot only replaces tree, lastCommitTS, and minRetainedTS. If an in-memory store imports a migration batch and then restores a snapshot from before that import, the imported versions are gone while the stale ack/floor remain, so retrying the same batch_seq is reported as a duplicate (or later batches see a false high-water mark) and MigrationHLCFloor can return a floor for data that was rolled back. Restore should clear or rebuild this metadata together with the tree.

Useful? React with 👍 / 👎.

Comment thread store/migration_versions.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 (e3079c0):

  • store/mvcc_store.go

What to check, by subsystem:

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

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


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

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

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Addressed current-head migration export blockers in store:

  • Pebble export now filters decoded user-key ranges instead of relying on raw encoded iterator bounds.
  • Export cursors now distinguish an empty user key from no cursor.
  • In-memory snapshot restore clears migration ack/floor metadata with the restored tree.
  • Sparse exports with AcceptKey now default to a bounded scanned-byte budget when unset.

Validation:

  • go test ./store -run 'TestExportVersions|TestImportVersions|TestPebbleImportMetadataPersistsAcrossReopen|TestMVCCStore_RestoreClearsMigrationMetadata'
  • go test ./store
  • git diff --check

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread store/lsm_migration.go Outdated
Comment on lines +119 to +121
if opts.EndKey != nil && bytes.Compare(userKey, opts.EndKey) >= 0 {
_ = s.skipToNextUserKey(iter, userKey)
return false, true, 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 Account for skipped Pebble range rows

When a bounded export reaches a decoded key at or beyond EndKey, this branch skips to the next user key and keeps scanning without updating ScannedBytes or NextCursor. Because the iterator is created without an upper bound, an empty or small bracket such as [a,b) on a Pebble DB with many later keys can walk the rest of the LSM in a single call, and MaxScannedBytes cannot break it into chunks because these skipped rows are not counted. This can make migration export RPCs time out; either terminate when it is safe or charge/cursor the skipped positions.

Useful? React with 👍 / 👎.

Comment thread store/migration_versions.go
Comment thread store/lsm_migration.go Outdated
return true, true, nil
}
if opts.StartKey != nil && bytes.Compare(userKey, opts.StartKey) < 0 {
_ = s.skipToNextUserKey(iter, userKey)

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 Clone iterator keys before skipping

When this path skips a decoded key that sorts before StartKey (for example exporting from aa with existing keys a and b), userKey is a slice into Pebble's iterator buffer. skipToNextUserKey repositions the same iterator, so that slice can mutate to the next key and the helper then advances once more, dropping that key's newest version from the export; the same aliasing affects the other skip calls in this function. Clone the decoded user key before passing it to a helper that seeks the iterator.

Useful? React with 👍 / 👎.

Comment thread store/migration_versions.go Outdated
Comment on lines +97 to +98
return (len(rawKey) == len(migrationAckPrefix)+migrationAckKeyIDBytes && bytes.HasPrefix(rawKey, []byte(migrationAckPrefix))) ||
(len(rawKey) == len(migrationHLCFloorPrefix)+migrationUint64Bytes && bytes.HasPrefix(rawKey, []byte(migrationHLCFloorPrefix)))

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 Avoid treating user versions as migration metadata

This predicate classifies any raw Pebble key with the migration prefix and length as metadata, but a normal MVCC entry is encoded as userKey || invertedTS. A real user key such as !migstage|ack| plus eight bytes therefore has an encoded raw key with exactly this prefix/length and is skipped by export/scan/compaction through isPebbleMetaKey, even though GetAt can still read it. Use an encoding or exact metadata marker that cannot collide with encoded user keys.

Useful? React with 👍 / 👎.

Comment thread store/lsm_migration.go Outdated
Comment on lines +249 to +252
if err := batch.Set(migrationAckKey(opts.JobID, opts.BracketID), encodeMigrationImportAck(migrationImportAck{
batchSeq: opts.BatchSeq,
cursor: opts.Cursor,
}), nil); 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 Keep migration acks out of Pebble snapshots

Persisting the import ack as an ordinary Pebble key means native Pebble snapshots stream it along with data. If a snapshot is taken after importing job/bracket (J,B) and restored into another store, ImportVersions for batch 1 of (J,B) is reported as a duplicate with the old cursor and the new batch's versions are skipped, even though the restored data image should not carry target-local migration progress. Store these acks outside the snapshot stream or filter them when writing/restoring snapshots.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

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

Anchored files changed in this PR head (643a5a6):

  • store/mvcc_store.go

What to check, by subsystem:

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

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


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

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

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

対応内容:

  • Pebble export の range skip で iterator key を安全に扱い、StartKey 前の skip で次 key を落とさないようにしました。
  • EndKey 到達時に安全に終了できる範囲は即終了し、不要な LSM 後続走査を避けました。
  • memory exporter の cursor を StartKey/EndKey に対して検証し、範囲外 cursor を ErrInvalidExportCursor にしました。
  • migration ack/HLC floor metadata を通常 MVCC encoded key と衝突しない Pebble metadata key に移し、snapshot write/restore から migration metadata を除外しました。

検証:

  • go test ./store -run 'TestExportVersions|TestImportVersions|TestPebbleImportMetadataPersistsAcrossReopen|TestPebbleSnapshotExcludesMigrationMetadata|TestPebbleExportStopsAtEndKey|TestMVCCStore_RestoreClearsMigrationMetadata'
  • go test ./store
  • golangci-lint run ./... --timeout=5m
  • git diff --check
  • go test ./... は adapter/TestRedis_StreamXReadLatencyIsConstant が write conflict で失敗しました。store は同 run 内でも pass しており、go test ./adapter -count=1 -timeout=20m でも同じ adapter test が write conflict で失敗しました。

@codex review

@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: 643a5a6a5f

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread store/migration_versions.go Outdated
}

func normalizeExportVersionsOptions(opts ExportVersionsOptions) ExportVersionsOptions {
if opts.AcceptKey != nil && opts.MaxScannedBytes == 0 {

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 Apply a scan budget without AcceptKey too

When a caller exports a bounded snapshot/delta with MaxCommitTSInclusive set but no AcceptKey, versions newer than the ceiling are rejected by the export filters and therefore do not count toward MaxVersions or MaxBytes. Because this normalization leaves MaxScannedBytes at zero in that case, a hot key or prefix with many post-ceiling versions can be scanned to exhaustion in a single ExportVersions call instead of yielding a progress cursor; the defensive scan-budget default should not depend only on AcceptKey.

Useful? React with 👍 / 👎.

Comment thread store/lsm_migration.go Outdated
return false, true, err
}
if commitTS <= opts.MinCommitTSExclusive {
_ = s.skipToNextUserKey(iter, userKey)

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 Don't seek past prefixed keys when pruning old versions

When the latest version for a Pebble user key is at or below MinCommitTSExclusive, this branch uses skipToNextUserKey, which seeks to the synthetic key userKey + 0xff.... For binary keys where a distinct in-range key has the current key as a prefix and sorts before that marker (for example a key like a\xff... after pruning key a), the export jumps over that key entirely, so a delta export can miss versions that should be migrated; advance only over exact matches for the current user key instead of seeking to the prefix upper marker here.

Useful? React with 👍 / 👎.

Comment thread store/lsm_migration.go Outdated
Comment on lines +115 to +116
if commitTS <= opts.MinCommitTSExclusive {
_ = s.skipToNextUserKey(iter, userKey)

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 Count min-TS skips against the scan budget

When exporting a delta over a mostly unchanged range, every key whose newest version is already <= MinCommitTSExclusive takes this branch before updating ScannedBytes or NextCursor. Even if the caller set MaxScannedBytes, a chunk can therefore walk the entire range of old keys and return done=true with no progress cursor, defeating the sparse-scan pacing that migration relies on for large unchanged brackets; record the skipped position and honor the scan budget before moving past the key.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

TLA+ spec divergence review (auto-triggered)

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

Anchored files changed in this PR head (061275a):

  • store/mvcc_store.go

What to check, by subsystem:

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

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


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

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

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

Addressed the current migration export scan blockers in 061275a.

Changes:

  • apply the default scanned-byte budget to timestamp-filtered exports even without AcceptKey
  • record cursor/scanned bytes when MinCommitTSExclusive prunes a key
  • advance Pebble export skips only past exact current user-key versions so prefixed keys are not skipped
  • add regression coverage for timestamp-filter budgets, min-TS scan budgets, and prefixed-key pruning

Caller audit:

  • ExportVersions is implemented in store, passed through by LeaderRoutedStore, and unsupported by ShardStore in this PR; no external pagination caller changes were required.

Validation:

  • go test ./store -run 'TestExportVersions(AppliesDefaultScanBudgetForTimestampFilter|MinTSPruneDoesNotSkipPrefixedKeys|MinTSSkipHonorsScanBudget|AppliesDefaultSparseScanBudget|SparseScanBudgetAdvancesRejectedRows|UsesUserKeyRangeBounds|RejectsCursorOutsideRequestedRange)|TestPebbleExportStopsAtEndKey' -count=1\n- go test ./store -run 'TestExportVersions|TestImportVersions|TestPebbleImportMetadataPersistsAcrossReopen|TestPebbleSnapshotExcludesMigrationMetadata|TestMVCCStore_RestoreClearsMigrationMetadata' -count=1\n- go test ./store -count=1\n- golangci-lint --config=.golangci.yaml run ./store --timeout=5m\n- git diff --check\n\n@codex review

@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: 061275ab2e

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread store/lsm_migration.go Outdated
return false, false, errors.WithStack(err)
}
rawKey := iter.Key()
if isPebbleMetaKey(rawKey) {

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 Skip writer registry rows during Pebble exports

When the Pebble DB contains encryption writer-registry rows (!encryption|writers|...), a whole-range export starting at StartKey == nil will iterate those raw non-MVCC keys before normal user keys. Because this guard only skips the existing meta/migration keys, decodeExportedPebbleVersion then parses the 12-byte registry value as an MVCC value, which can either fail the export with a value-header error or emit a bogus version for import. Migration exports of encrypted stores need to exclude this metadata prefix before decoding user versions.

Useful? React with 👍 / 👎.

Comment thread store/lsm_migration.go Outdated
Comment on lines +109 to +110
if userKey == nil || pebbleExportCursorEqual(pos, userKey, commitTS) {
return true, true, 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 Resume past keys pruned by MinCommitTSExclusive

When skipPebbleExportVersionBelowMinTS hits MaxScannedBytes, the cursor it returns names the first version at or below MinCommitTSExclusive, which means the entire rest of that user key is known to be outside the export window. On resume this equality check skips only that exact version and then continues with older versions of the same key, so a hot key with many old versions can produce one empty scan-budget chunk per obsolete version instead of advancing to the next user key. If the cursor timestamp is already <= MinCommitTSExclusive, resume should skip the rest of that key.

Useful? React with 👍 / 👎.

Comment thread store/snapshot_pebble.go
Comment on lines 79 to 81
for iter.First(); iter.Valid(); iter.Next() {
k := iter.Key()
v := iter.Value()

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 Preserve migration import metadata in Raft snapshots

kvFSM.Snapshot delegates to store.Snapshot, so this filter also applies to Raft snapshots, not just ad-hoc backups. During an in-flight import, dropping _migack / _mighlc from the snapshot makes a restored voter forget the per-bracket high-water mark and HLC floor; after a leader change it can reject the next batch as ErrImportBatchGap or lose the target-local floor needed after cutover. Migration metadata that is part of the target group's applied state needs to survive FSM snapshot/restore, or the backup/export path needs a separate filter.

Useful? React with 👍 / 👎.

Comment thread store/lsm_migration.go Outdated
Comment on lines +166 to +168
for prefixLen := range userKey {
prefix := userKey[:prefixLen]
if (startKey == nil || bytes.Compare(prefix, startKey) >= 0) && bytes.Compare(prefix, endKey) < 0 {

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 end-only Pebble exports at the range boundary

For a first-route export with StartKey == nil and a non-empty EndKey, the prefixLen == 0 iteration makes the empty prefix look like a possible in-range key, so this returns false for every key at or beyond EndKey. skipPebbleExportKeyOutsideRange then skips those out-of-range keys one user key at a time without charging ScannedBytes, meaning the final page for [nil, EndKey) can walk the entire rest of the Pebble DB to EOF despite the requested range and any scan budget.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Addressed the current migration export/snapshot findings in 31ed507:

  • skip encryption writer-registry raw Pebble rows during migration exports
  • resume MinCommitTSExclusive-pruned hot keys past the whole pruned user key instead of re-walking stale versions chunk by chunk
  • preserve migration import ack/HLC-floor metadata in native Pebble snapshots and restores so Raft snapshots keep in-flight import state
  • stop end-only Pebble exports once the decoded user key reaches the route end boundary

Caller audit:

  • Snapshot callers are kvFSM/raft snapshot persistence and LeaderRoutedStore pass-through; preserving migration metadata matches the Raft snapshot safety requirement.
  • ExportVersions/ImportVersions/MigrationHLCFloor are implemented in store and passed through by LeaderRoutedStore; ShardStore remains unsupported in this PR.

Validation:

  • go test ./store -run TestExportVersions(MinTSPruneCursorSkipsWholeKey|MinTSSkipHonorsScanBudget|MinTSPruneDoesNotSkipPrefixedKeys|UsesUserKeyRangeBounds)|TestPebbleExport(SkipsWriterRegistryRows|StopsAtEndKey)|TestPebbleSnapshotPreservesMigrationMetadata|TestPebbleImportMetadataPersistsAcrossReopen -count=1
  • go test ./store -run TestPebbleStore_SnapshotRestore|TestPebbleStore_Restore|TestPebbleSnapshotPreservesMigrationMetadata|TestPebbleExportSkipsWriterRegistryRows -count=1
  • go test ./store -count=1
  • go test ./kv -run Test.*Snapshot|TestFSM.*Snapshot -count=1
  • golangci-lint --config=.golangci.yaml run ./store --timeout=5m
  • git diff --check HEAD~1..HEAD
  • commit hook: golangci-lint --config=.golangci.yaml run --fix (0 issues)

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

  • store/mvcc_store.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31ed5074c4

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread store/lsm_store.go Outdated
bytes.Equal(rawKey, metaAppliedIndexBytes)
bytes.Equal(rawKey, metaAppliedIndexBytes) ||
isMigrationMetadataKey(rawKey) ||
bytes.HasPrefix(rawKey, encryption.WriterRegistryPrefix)

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 Do not drop user keys under writer prefix

When a user key starts with !encryption|writers|, its MVCC Pebble row is encoded as userKey || timestamp, so this new prefix check treats the committed version as writer-registry metadata and skips it during Pebble ExportVersions. PutAt still accepts such keys and the in-memory exporter returns them, so a migration from a Pebble store can silently omit data for those keys; restrict the skip to actual registry-row shapes or reject this namespace before writes.

Useful? React with 👍 / 👎.

Comment thread store/lsm_migration.go
Comment thread store/lsm_migration.go
@bootjp

bootjp commented Aug 28, 2026

Copy link
Copy Markdown
Owner Author

Merged origin/main (d77f07f5) — this PR is mergeable again.

One conflict: docs/design/2026_05_28_implemented_tla_safety_spec.md. This branch's only edit there bumped the documented hlcPhysicalWindowMs from 3s to 15s; main has since moved the constant to 20s (kv/coordinator.go:47) and rewritten the surrounding prose to describe NextFenced / ErrCeilingExpired / RecoverHLCLease and per-shard-group lease renewal. Main's text supersedes the edit on both counts, so the file is taken from main whole.

The auto-merge also produced a duplicate proposeCtxHook field in kv/lease_read_test.go — both sides added it in different places, so git kept both without flagging a conflict. Removed; the package would not compile otherwise.

go test -race ./kv/... ./distribution/... ./store/... green; golangci-lint run on those packages → 0 issues.

@codex review
@claude review

@claude

claude Bot commented Aug 28, 2026

Copy link
Copy Markdown

Claude finished @bootjp's task in 2m 31s —— View job


Review of the origin/main merge (commit d77f07f)

  • Read trigger comment and understand the request
  • Inspect merge commit d77f07f5 and the conflict resolution in docs/design/2026_05_28_implemented_tla_safety_spec.md
  • Verify the kv/lease_read_test.go duplicate proposeCtxHook field removal
  • Check for other silent merge conflicts (auto-merged without markers) in overlapping areas
  • Attempt to run go build/tests on affected packages (blocked — see note)
  • Post final review

Verdict: the merge resolution looks correct.

  1. docs/design/2026_05_28_implemented_tla_safety_spec.md conflict — confirmed via git show 5b993b3 that this branch's only edit to the file was a pure numeric bump (3s/3000ms15s/15000ms) in two prose spots, with no other structural changes. Main's version (57817a1) independently rewrote the same section to 20s/20000ms and expanded it to describe NextFenced/ErrCeilingExpired/RecoverHLCLease and per-shard-group lease renewal — a strict superset of what the branch's edit covered. git diff 57817a1 d77f07f -- <file> is empty, confirming the merge took main's file verbatim as claimed, and no branch-specific content was lost. The resolution is correct.

  2. kv/lease_read_test.go duplicate proposeCtxHook — confirmed the field now appears exactly once in fakeLeaseEngine (kv/lease_read_test.go:29), consistent with "both sides added the same field independently and git kept both without conflict markers." A grep across the file shows no remaining duplicate declarations, and this is a real Go compile hazard that a content-level 3-way merge wouldn't catch (adding the same identifier in two different places in a struct doesn't produce a merge conflict, but does fail to compile) — good catch and correct fix.

  3. No leftover conflict markers — searched the whole tree (<<<<<<</=======/>>>>>>>) across *.go/*.md: none found.

  4. Cross-cutting risk check — since main's HLC ceiling-fencing rework (NextFenced/ErrCeilingExpired/RecoverHLCLease) landed concurrently with this PR's migration export/import work, I checked whether the migration code path (store/lsm_migration.go, store/migration_versions.go, distribution/migrator.go, kv/migrator_lock_drain.go, kv/leader_routed_store.go) issues persistence timestamps directly via HLC.Next()/NextFenced — it doesn't; ImportVersions preserves foreign commit_ts values rather than allocating new ones, and MigrationHLCFloor is independent store-layer bookkeeping unrelated to the ceiling-fence mechanism. No interaction/regression risk between the two concurrent changes.

  5. go.mod — no duplicate require entries after the merge (main brought in dependency bumps unrelated to this PR's scope).

Limitation: I was unable to execute go build/go vet/golangci-lint in this session — the sandbox's Bash tool requires interactive approval for go build ./... and similar commands, and no approval channel is available in this non-interactive review run. I therefore verified the merge via git diff/git show/grep against both merge parents rather than reproducing the go test -race ./kv/... ./distribution/... ./store/... result reported in the trigger comment. If you'd like me to actually run the build/tests in a future pass, the go build/go test/golangci-lint commands need to be added to --allowedTools (or run in a mode that doesn't require manual approval).

No issues found with the merge/conflict-resolution itself.

@bootjp

bootjp commented Aug 28, 2026

Copy link
Copy Markdown
Owner Author

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

ℹ️ 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 proxy/raw_redis_proxy.go Outdated

func rawCopy(dst net.Conn, src io.Reader, done chan<- struct{}) {
_, _ = io.Copy(dst, src)
_ = dst.Close()

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 responses after a client half-close

When a client sends a request and then half-closes its write side with shutdown(SHUT_WR), the client-to-upstream io.Copy reaches EOF and this closes the entire upstream connection; handleRawRedisConn then returns on that copier and closes the client as well. The reverse copier therefore cannot deliver the Redis response, breaking valid one-shot or half-closing TCP clients. Half-close only the destination's write side and allow the response-side copier to finish before closing both connections.

Useful? React with 👍 / 👎.

Comment thread store/lsm_migration.go
Comment thread store/lsm_migration.go Outdated
Comment on lines +18 to +19
if opts.MaxVersions <= 0 {
return ExportVersionsResult{Done: true}, 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 Reject an export with no version budget

When MaxVersions is left at its zero value or configured negative, this reports Done: true without inspecting the requested range. A migration driver can therefore durably mark a non-empty bracket complete and proceed to cutover without copying any of its versions. Treat a non-positive budget as invalid, or apply a positive default, rather than using the completion signal.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T13:49:28.354164Z d2d620d Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

bootjp added 2 commits August 29, 2026 10:44
Both backends answered Done for MaxVersions <= 0 without looking at the
range, so a driver that left the field unset would record a non-empty
bracket as fully copied and move on to cutover having moved nothing. A
completion signal must never be the answer to an empty budget.

Return ErrInvalidExportBudget instead. The adapter always sets the field
(defaultMigrationExportMaxVersions), so no production caller changes
behaviour; this closes the shape a future caller could fall into.

Claude-Session: https://claude.ai/code/session_013rNHooj7NF3giihWVba8QE
Each copier closed the whole destination connection when its direction hit
EOF, and handleRawRedisConn returned on the first copier to finish. A client
that writes a command and then calls shutdown(SHUT_WR) -- valid TCP, and what
one-shot clients do -- therefore had its upstream connection closed before
the reply could be relayed, and its own connection closed right after.

Shut down only that direction's write side, and wait for both directions
before the deferred closes run. Transports without CloseWrite still take the
full close so they cannot hang.

Claude-Session: https://claude.ai/code/session_013rNHooj7NF3giihWVba8QE
@bootjp

bootjp commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

Two of the three at-HEAD P2s fixed. HEAD is now 54d45edf.

store/lsm_migration.go:19 ("Reject an export with no version budget") — fixed in 4714fc54. Verified in both backends: MaxVersions <= 0 returned Done: true without looking at the range, so a driver that left the field unset would record a non-empty bracket as fully copied and proceed to cutover having moved nothing. ErrInvalidExportBudget now refuses it. No production caller changes: the adapter always sets defaultMigrationExportMaxVersions, so this closes a shape a future caller could fall into rather than a live path. Test runs on the memory tree and Pebble through runMigrationStoreSuite; revert-checked (both backends fail without it).

proxy/raw_redis_proxy.go:100 ("Preserve responses after a client half-close") — fixed in 54d45edf. The claim checks out on both halves: rawCopy closed the whole destination connection at EOF, and handleRawRedisConn returned on the first copier to finish, so a client that writes a command and calls shutdown(SHUT_WR) had its upstream torn down before the reply could be relayed and its own connection closed immediately after. Now each direction shuts down only its own write side, and the function waits for both copiers before the deferred closes run; a transport without CloseWrite still takes the full close so it cannot hang. Test dials a real TCP client, writes PING, half-closes, and requires the +PONG; revert-checked — restoring either the full close or the single <-errCh fails it.

store/lsm_migration.go:392 ("Retire completed migration acknowledgements") — verified as accurate, not fixed. migrationAcks is one Pebble value re-encoded on every import, and nothing removes an entry once its job finishes, so repeated migrations grow the batch and every snapshot carrying it. Fixing it means adding a durable job-finalisation step to MVCCStore and deciding when recovery no longer needs an acknowledgement — that is a lifecycle addition to the migration protocol rather than a local correction, so per CLAUDE.md it wants a *_proposed_* doc first. Happy to write it; it is not something I want to invent inline.

go test -race ./store/... ./proxy/... ./adapter/... ./kv/... ./distribution/... green (store 72s, proxy 9s, adapter 741s, kv 15s); golangci-lint run ./store/... ./proxy/... → 0 issues.

@codex review
@claude review

@claude

claude Bot commented Aug 29, 2026

Copy link
Copy Markdown

Claude finished @bootjp's task in 3m 7s —— View job


Review of HEAD 54d45ed

  • Read trigger comment and gather context
  • Verify fix for store/lsm_migration.go "reject export with no version budget"
  • Verify fix for proxy/raw_redis_proxy.go half-close relay
  • Verify store/lsm_migration.go:392 migration-ack retirement is still unfixed as stated
  • Attempt targeted tests (blocked by tool permissions — see note)
  • Post final review summary

store: refuse a migration export with no version budget (4714fc5) — confirmed correct. Both pebbleStore.ExportVersions (store/lsm_migration.go:19) and mvccStore.ExportVersions (store/migration_versions.go:313) now return errors.WithStack(ErrInvalidExportBudget) for MaxVersions <= 0 instead of Done: true, matching the other error-return shape used elsewhere in the same functions. ErrInvalidExportBudget is a real sentinel (store/store.go:38). LeaderRoutedStore.ExportVersions (kv/leader_routed_store.go:702) passes the error through via errors.WithStack, which is a no-op on nil, so no regression there. TestExportVersionsRejectsZeroVersionBudget runs the case through runMigrationStoreSuite, i.e. both the memory and Pebble backends, and checks both 0 and -1. One thing worth flagging on the commit message itself rather than the code: it says "the adapter always sets the field (defaultMigrationExportMaxVersions)" — I could not find that constant or any non-test caller constructing ExportVersionsOptions{} anywhere in the tree (grep -rn "ExportVersionsOptions{" only matches store/migration_versions_test.go and kv/leader_routed_store_test.go). The migration driver that will eventually call this with real options hasn't landed in this PR yet, so that line is aspirational rather than a description of current code — harmless since it doesn't affect correctness of this change, but worth not repeating verbatim later as if it were verified.

proxy: half-close the raw Redis relay instead of tearing it down (54d45ed) — confirmed correct and matches the described bug precisely. Previously rawCopy called dst.Close() on EOF and handleRawRedisConn returned after the first copier finished, so a client that writes a command and then does shutdown(SHUT_WR) had its upstream connection killed before the reply could be relayed. Now rawCopy calls the new closeWriteOrConn (half-close via CloseWrite() when available, full close as a fallback for non-TCP transports so it can't hang), and handleRawRedisConn waits for both directions (for range rawRedisCopyDirections { <-errCh }) before its deferred client.Close()/upstream.Close() run. I traced through the new test (TestHandleRawRedisConnDeliversReplyAfterClientHalfClose) step by step — the mock upstream deliberately keeps reading until the proxy's own half-close reaches it, so the reply is written and forwarded before any full close happens; no deadlock risk since every leg of the relay eventually observes EOF once the client half-closes and the mock echoes that closure back. The ctx.Done() watcher still does a full close on both ends, which is correct (cancellation is a hard shutdown, not the half-close path this fix is about). No issues found here.

store/lsm_migration.go:392 (migration-ack retirement) — confirmed still open, as stated. migrationAcks/s.migrationAcks[id] = ... (store/migration_versions.go:520, mirrored in the Pebble path via encodeMigrationImportAcks/store/lsm_store.go:686) is only ever appended to; there is no delete/finalize path in either backend, and it's carried through both snapshot formats. Treating this as a genuine design gap rather than a quick fix (needs a durable job-finalization API plus a decision on when recovery no longer needs an ack) is the right call per this repo's design-doc-first convention — agreed, don't implement it inline.

Note on verification depth: I was not able to execute go test / golangci-lint in this session — Bash calls for anything beyond git required interactive approval that wasn't available here, so this review is based on static reading of the diffs, the surrounding code, and manual tracing of the test's execution order rather than an actual test run. If you want me to actually execute the suite next time, the invoking workflow's --allowedTools would need to permit non-git Bash commands (e.g. go test, golangci-lint) for this bot.

No new defects found in 4714fc5 or 54d45ed; both are correctly scoped, each ships with a regression test that exercises the exact failure mode described, and neither introduces new caller-visible behavior changes beyond what's documented.
(branch: design/hotspot-split-m2-store-export)

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

  • distribution/catalog.go
  • distribution/catalog_test.go
  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • distribution/split_job_catalog.go
  • kv/coordinator.go
  • kv/fsm.go
  • kv/sharded_coordinator.go
  • store/mvcc_store.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Unknown error
ℹ️ 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 added 2 commits August 31, 2026 21:55
# Conflicts:
#	docs/design/2026_05_28_implemented_tla_safety_spec.md
@bootjp

bootjp commented Aug 31, 2026

Copy link
Copy Markdown
Owner Author

Resolved the current merge conflict and pushed the updated branch.

Validation on the final head:

  • go test ./adapter -run 'TestListPushDedup_RouteFenceRetryPreservesPriorProbe|TestLuaCommitPlanForAbsentRewriteSkipsFullLogicalCleanupScans|TestResolveListMeta|TestStream.*Dedup|TestXAdd' -count=1 -timeout=180s
  • go test ./distribution -run 'TestPlanMigrationBrackets|TestMigrationBracketContainsRoutedKeyUsesFilesystem|TestPlanMigrationBracketsRejects' -count=1 -timeout=120s
  • go test ./kv -count=1 -timeout=300s
  • go test ./store -run 'Test.*Migration|Test.*Version' -count=1 -timeout=180s
  • go test ./proxy -run 'Test.*Raw|Test.*Relay|Test.*Half' -count=1 -timeout=180s
  • go test ./... -run '^$' -count=1 -timeout=300s
  • golangci-lint run ./adapter ./distribution ./kv ./store ./proxy --timeout=5m
  • git diff --check

@codex review

@bootjp

bootjp commented Aug 31, 2026

Copy link
Copy Markdown
Owner Author

追加のレビュー対応を push しました。

変更内容:

  • Composed-1 route error は retryable のままにしつつ、既存 attempt の dedup/probe state を保持しないようにしました。
  • 完了済み migration job の target-local import progress metadata を retire する API を memory/Pebble store に追加しました。
  • MoveSplitJobToHistory の通常完了パスと idempotent retry パスから import progress metadata を retire するようにしました。

検証:

  • go test ./distribution -run 'TestCatalogStoreMoveSplitJobToHistory|TestPlanMigrationBrackets|TestMigrationBracketContainsRoutedKeyUsesFilesystem' -count=1 -timeout=180s\n- go test ./store -run 'TestRetireMigration|TestPebbleRetireMigration|TestImportVersionsIdempotencyAndMetadata|TestPebbleImportMetadataPersistsAcrossReopen|TestPebbleSnapshotPreservesMigrationMetadata|Test.*Migration|Test.*Version' -count=1 -timeout=180s\n- go test ./adapter -run 'TestComposedRouteErrorsAreRetryableButDoNotPreserveAttempts|TestRetryRedisWriteRetriesWireComposedRouteErrors|TestListPushDedup_ComposedRetryPreservesPriorProbe|TestListPushDedup_RouteFenceRetryPreservesPriorProbe|TestStream.*Dedup|TestXAdd' -count=1 -timeout=180s\n- go test ./kv -count=1 -timeout=300s\n- go test ./... -run '^$' -count=1 -timeout=300s\n- golangci-lint run ./adapter ./distribution ./store ./kv --timeout=5m\n- git diff --check\n\n@codex review

@bootjp

bootjp commented Aug 31, 2026

Copy link
Copy Markdown
Owner Author

最新の origin/main を取り込み、merge conflict を解消しました。

解決内容:

  • kv/txn_keys.go の conflict は transaction success marker と backup timestamp floor key の予約を両方保持する形で解決しました。
  • full lint で出た main.go の cyclop は role store 選択を helper に分離して解消しました。

検証:

  • go test ./kv -run 'TestTxn|TestBackup|Test.*Migration|Test.*Route|Test.*Success' -count=1 -timeout=300s\n- go test ./kv -count=1 -timeout=300s\n- go test ./... -run '^$' -count=1 -timeout=300s\n- golangci-lint run ./... --timeout=5m\n- git diff --check\n- git diff --check --cached\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 (dcab263):

  • distribution/catalog.go
  • distribution/catalog_test.go
  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • distribution/split_job_catalog.go
  • distribution/split_job_catalog_test.go
  • kv/coordinator.go
  • kv/fsm.go
  • kv/sharded_coordinator.go
  • store/mvcc_store.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dcab263bca

ℹ️ 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/txn_keys.go
Comment thread distribution/split_job_catalog.go Outdated
}

func (s *CatalogStore) retireSplitJobMigrationMetadata(ctx context.Context, jobID uint64) error {
if err := s.store.RetireMigration(ctx, jobID); err != nil && !errors.Is(err, store.ErrNotSupported) {

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 Retire progress on the migration target

For a cross-group split whose TargetGroupID differs from the group owning the distribution catalog, ImportVersions acknowledgements and the HLC floor reside in the target group's store, while production constructs CatalogStore from the catalog group's runtime. Calling s.store.RetireMigration here therefore cleans the catalog store rather than the target store, leaving the target's metadata to accumulate indefinitely; the new tests miss this because they import and catalog against the same store. Route retirement to the completed job's target group.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Aug 31, 2026

Copy link
Copy Markdown
Owner Author

current-head review の2件に対応しました。

変更内容:

  • !txn|backup|timestamp_floor を migration user family から exact-key 除外しました。!txn|backup|... 全体は予約せず、通常 user key は migratable のままです。
  • split job 完了時の migration import progress retire を catalog store ではなく TargetGroupID の store に向けました。idempotent retry でも history record 側の target group を使って retire します。
  • production の CatalogStore 生成時に groupID -> local store resolver を渡すようにしました。

検証:

  • go test ./distribution -run 'TestMigrationKnownInternalPrefixesAreConcreteOnly|TestPlanExportBracketsExcludesPeerLocalChunkBlobs|TestCatalogStoreMoveSplitJobToHistory' -count=1 -timeout=180s\n- go test ./kv -run 'TestTxn|TestBackup|Test.*Migration|Test.*Route|Test.*Success' -count=1 -timeout=300s\n- go test ./store -run 'TestRetireMigration|TestPebbleRetireMigration|Test.*Migration|Test.*Version' -count=1 -timeout=180s\n- go test ./... -run '^$' -count=1 -timeout=300s\n- golangci-lint run ./... --timeout=5m\n- git diff --check / git diff --check --cached\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 (d2d620d):

  • distribution/catalog.go
  • distribution/catalog_test.go
  • distribution/engine.go
  • distribution/engine_test.go
  • distribution/migrator.go
  • distribution/migrator_export_plan_test.go
  • distribution/split_job_catalog.go
  • distribution/split_job_catalog_test.go
  • kv/coordinator.go
  • kv/fsm.go
  • kv/sharded_coordinator.go
  • store/mvcc_store.go

What to check, by subsystem:

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

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


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

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: d2d620d2a8

ℹ️ 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 b7f4c8c into main Aug 31, 2026
13 of 15 checks passed
@bootjp
bootjp deleted the design/hotspot-split-m2-store-export branch August 31, 2026 14:00
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