Skip to content

s3: add blob fetch rpc#1063

Open
bootjp wants to merge 6 commits into
design/s3-blob-offload-scaffoldfrom
design/s3-blob-offload-rpc
Open

s3: add blob fetch rpc#1063
bootjp wants to merge 6 commits into
design/s3-blob-offload-scaffoldfrom
design/s3-blob-offload-rpc

Conversation

@bootjp

@bootjp bootjp commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • add internal S3BlobFetch gRPC service for content-addressed chunkblob fetch/push
  • store pushed chunkblobs under !s3|chunkblob| in the local MVCC store with digest validation
  • register the service on each raft gRPC server while keeping public S3 blob offload capability disabled

Verification

  • make -C proto gen
  • go test ./adapter -run 'TestS3BlobFetch|TestS3BlobOffload' -count=1 -timeout=240s\n- go test ./adapter -run '^$' -count=1 -timeout=120s\n- go test . -run '^$' -count=1 -timeout=120s\n- go test ./... -run '^$' -count=1 -timeout=300s\n- go test -race ./adapter -run 'TestS3BlobFetch' -count=1 -timeout=240s\n- golangci-lint run ./adapter/... --timeout=5m\n- golangci-lint run --timeout=5m\n- git diff --check\n- git verify-commit HEAD\n\nAuthor: bootjp

@coderabbitai

coderabbitai Bot commented Jul 10, 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: 2c1cd7b4-3670-46b6-841e-45c1182e40e4

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

Copy link
Copy Markdown
Owner Author

Implemented the S3 blob fetch RPC slice on top of #1057.

Changes:

  • add S3BlobFetch FetchChunkBlob/PushChunkBlob proto service and generated Go stubs
  • add local MVCC-backed chunkblob push/fetch handler with sha256 validation and streaming fetch frames
  • register S3BlobFetch on each raft gRPC server and include it in leader health service names
  • keep S3BlobOffloadLocalCapability false, so this does not enable the public S3 PUT/GET offload path

Verification:

  • make -C proto gen
  • go test ./adapter -run 'TestS3BlobFetch|TestS3BlobOffload' -count=1 -timeout=240s\n- go test ./adapter -run '^$' -count=1 -timeout=120s\n- go test . -run '^$' -count=1 -timeout=120s\n- go test ./... -run '^$' -count=1 -timeout=300s\n- go test -race ./adapter -run 'TestS3BlobFetch' -count=1 -timeout=240s\n- golangci-lint run ./adapter/... --timeout=5m\n- golangci-lint run --timeout=5m\n- git diff --check\n- git verify-commit HEAD\n\n@codex review

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the S3BlobFetch gRPC service to support peer-to-peer S3 chunk blob transfers. It implements the S3BlobFetchServer with FetchChunkBlob and PushChunkBlob streaming RPCs, registers the service in main.go, and adds comprehensive unit tests. The review feedback suggests a performance optimization in s3ChunkBlobReceiveState.finish() to return the payload bytes directly instead of cloning them, avoiding redundant memory allocation and copying.

Important

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

Comment thread adapter/s3_blob_fetch.go Outdated
if !s.seenEOF {
return s.digest, nil, s3BlobFetchStatus(codes.InvalidArgument, "missing s3 chunkblob eof")
}
return s.digest, bytes.Clone(s.payload.Bytes()), nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Since s3ChunkBlobReceiveState is a local variable in recvChunkBlob and is not reused, its internal buffer will not be modified after finish() is called. Furthermore, the underlying store (mvccStore or pebbleStore) already clones or serializes the payload during PutAt. Therefore, calling bytes.Clone here is redundant and introduces an unnecessary allocation and memory copy of the entire chunk blob (which can be up to s3ChunkSize in size). We can safely return s.payload.Bytes() directly.

Suggested change
return s.digest, bytes.Clone(s.payload.Bytes()), nil
return s.digest, s.payload.Bytes(), nil

@bootjp

bootjp commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

Addressed the current-head S3BlobFetch review feedback.

Changes:

  • Avoid the redundant receive-buffer clone in PushChunkBlob after EOF; the receive state is local and not reused.

Verification:

  • go test ./adapter -run 'TestS3BlobFetch|TestS3BlobOffload' -count=1 -timeout=240s\n- go test ./... -run '^$' -count=1 -timeout=300s\n- golangci-lint run ./adapter/... --timeout=5m\n- git diff --check\n- git verify-commit HEAD\n\n@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97c2749cec

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/s3_blob_fetch.go
Comment thread adapter/s3_blob_fetch.go Outdated
if !exists {
return nil, s3BlobFetchStatus(codes.NotFound, "s3 chunkblob not found")
}
payload, err := s.store.GetAt(ctx, key, readTS)

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 Read chunkblobs at a non-compacted timestamp

If the store compaction watermark has advanced past the chunkblob's write timestamp, this exact-timestamp GetAt returns ErrReadTSCompacted even though compaction keeps the newest version readable at current timestamps. In that normal post-compaction case FetchChunkBlob reports an Internal error for an existing blob; fetch should read at a current/max timestamp (or otherwise bypass the historical-read guard) after determining the key exists.

Useful? React with 👍 / 👎.

Comment thread adapter/s3_blob_fetch.go Outdated
if err := s.verifyChunkBlobDigest(digest, payload, codes.InvalidArgument); err != nil {
return err
}
if err := s.store.PutAt(stream.Context(), s3keys.ChunkBlobKey(digest), payload, s.clock.Next(), 0); 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 Allocate chunkblob commit timestamps through the fence

This is a new MVCC write timestamp, but it is allocated with HLC.Next(), whose contract in kv/hlc.go says persistence-grade allocations that become MVCC write timestamps must use NextFenced(). When this node's HLC ceiling has expired (for example after lease renewal stalls during startup rotation or a leadership transition), PushChunkBlob will still write and acknowledge a local chunkblob instead of failing closed, letting direct chunkblob persistence bypass the same stale-leader timestamp fence as the rest of the store.

Useful? React with 👍 / 👎.

Comment thread main.go Outdated
grpcSvc := adapter.NewGRPCServer(shardStore, coordinate)
pb.RegisterRawKVServer(gs, grpcSvc)
pb.RegisterTransactionalKVServer(gs, grpcSvc)
pb.RegisterS3BlobFetchServer(gs, adapter.NewS3BlobFetchServer(rt.store, coordinate.Clock(), 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 Wire the fetch server to the S3 blob metrics observer

The production registration constructs the only S3BlobFetchServer with a nil observer, so observeSHAMismatch() never reaches elastickv_s3_chunkblob_sha_mismatch_total for this RPC path. In the corrupt-local-blob or bad-push scenarios that this service explicitly detects, the request is rejected but operators get no S3 chunkblob mismatch signal even though the public S3 server is already wired with r.metricsRegistry.S3BlobOffloadObserver().

Useful? React with 👍 / 👎.

Comment thread main.go Outdated
// (nil when encryption is not wired); raftadmin.Server invokes
// it before AddVoter/AddLearner propose the conf-change.
internalraftadmin.RegisterOperationalServicesWithInterceptor(ctx, gs, rt.engine, []string{"RawKV"}, confChangeInterceptor)
internalraftadmin.RegisterOperationalServicesWithInterceptor(ctx, gs, rt.engine, []string{"RawKV", "S3BlobFetch"}, confChangeInterceptor)

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 Mark follower blob-fetch endpoints healthy

Including S3BlobFetch in this leader-only health loop makes followers report NOT_SERVING for a service that is intentionally peer-local; currentHealthStatus in internal/raftadmin/health.go maps only the Raft leader to SERVING. Any gRPC client using health checking for service S3BlobFetch would therefore avoid the follower endpoints that the leader must push chunkblobs to and that readers may fan out to on a miss.

Useful? React with 👍 / 👎.

Comment thread adapter/s3_blob_fetch.go Outdated
if err := s.verifyChunkBlobDigest(digest, payload, codes.InvalidArgument); err != nil {
return err
}
if err := s.store.PutAt(stream.Context(), s3keys.ChunkBlobKey(digest), payload, s.clock.Next(), 0); 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 Make repeated chunkblob pushes idempotent

If the leader retries after losing the response, or pushes the same content-addressed chunk for another reference, this unconditionally writes a fresh MVCC version because every call uses a new timestamp. That turns one !s3|chunkblob|<sha256> row into repeated full-value versions until compaction; after verifying the digest, duplicate pushes should detect an existing matching blob and acknowledge without appending another copy.

Useful? React with 👍 / 👎.

Comment thread adapter/s3_blob_fetch.go Outdated
if err != nil {
return err
}
if err := s.verifyChunkBlobDigest(digest, payload, codes.DataLoss); 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 Return the documented fallback code for fetch mismatches

For a peer with a corrupted local copy, this returns DataLoss, but the S3 blob-offload design documents SHA mismatch during FetchChunkBlob as an INVALID_ARGUMENT fallback trigger alongside NOT_FOUND. A client implemented against that contract would treat this peer as a hard failure instead of trying the remaining replicas, so a single bad copy can break a read even when other peers still have the chunk.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Addressed the current-head S3BlobFetch review feedback.

Changes:

  • make PushChunkBlob durability ack use the direct ApplyMutations path instead of PutAt, so the Pebble path fsyncs before Durable=true
  • allocate chunkblob commit timestamps with NextFenced and fail closed on an expired HLC ceiling
  • make duplicate and retried chunkblob pushes idempotent, including the race where another writer lands the same digest between precheck and write
  • read FetchChunkBlob payloads at the current/max timestamp so compacted historical timestamps do not break existing blobs
  • return InvalidArgument for fetch SHA mismatches and emit the S3 chunkblob mismatch observer from the gRPC path
  • register S3BlobFetch with the S3 blob observer and keep its health SERVING on followers while RawKV stays leader-gated

Caller audit:

  • NewS3BlobFetchServer is constructed only in startRaftServers and test fixtures; production now passes metricsRegistry.S3BlobOffloadObserver().
  • RegisterOperationalServicesWithInterceptor remains the existing leader-gated API; the new static-serving overload is used only for S3BlobFetch in startRaftServers and preserves RawKV leader health.
  • PushChunkBlob's semantic change is local to the S3BlobFetch server; direct ApplyMutations is the sync path for non-raft durable writes, and duplicate/write-conflict paths are re-read before acknowledging.

Verification:

  • rg caller audit for NewS3BlobFetchServer and RegisterOperationalServicesWithInterceptor
  • git diff --check
  • go test ./adapter -run 'TestS3BlobFetch' -count=1 -timeout=240s\n- go test ./internal/raftadmin -run 'TestRegisterOperationalServices' -count=1 -timeout=120s\n- go test ./... -run '^$' -count=1 -timeout=300s\n- golangci-lint run --timeout=5m\n- git verify-commit HEAD\n\n@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d207d49843

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/s3_blob_fetch.go Outdated
}

func (s *S3BlobFetchServer) nextChunkBlobCommitTS() (uint64, error) {
commitTS, err := s.clock.NextFenced()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Allocate chunkblob timestamps on the leader

Because this RPC is registered on every raft listener with that node's local clock, a leader→follower PushChunkBlob allocates from the follower's HLC here. In clusters where a follower has not yet applied the leader's renewed HLC ceiling, NextFenced can return ErrCeilingExpired even though the current leader can safely issue persistence timestamps, so chunkblob replication can fail/degrade and block offloaded PUTs when enough followers are lagging. Have the leader allocate and carry the commit timestamp for the receiver to use/observe instead of issuing it locally.

Useful? React with 👍 / 👎.

Comment thread adapter/s3_blob_fetch.go Outdated
Comment on lines +204 to +208
if err := s.store.ApplyMutations(ctx, []*store.KVPairMutation{{
Op: store.OpTypePut,
Key: key,
Value: payload,
}}, nil, 0, commitTS); 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 Wait for storage writer registration before accepting pushes

When storage envelopes are active but this process load has not yet marked its writer registration (for example just after runtime EnableStorageEnvelope/RotateDEK, or while startup registration is still catching up), the direct ApplyMutations path consults the storage registration gate and returns store.ErrWriterNotRegistered. This new runtime direct-write caller is not wrapped in retryUntilRegistered, so PushChunkBlob reports an internal write failure instead of waiting, making synchronous chunkblob replication fail transiently on nodes that are otherwise advertised as S3BlobFetch SERVING.

Useful? React with 👍 / 👎.

Comment thread adapter/s3_blob_fetch.go Outdated
Op: store.OpTypePut,
Key: key,
Value: payload,
}}, nil, 0, commitTS); 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 Allow chunkblobs to be recreated after GC

For a digest that was previously garbage-collected with a tombstone, chunkBlobAlreadyStored sees GetAt(..., MaxUint64) as not found, but this ApplyMutations call still uses startTS=0, so MVCC conflict checking rejects the newer tombstone as a write conflict and the retry path re-reads the tombstone as missing again. A later upload of the same content digest can therefore fail with an internal write error instead of recreating the local chunkblob; use a start timestamp at/after the latest version (or a direct sync put path with equivalent durability) for this content-addressed upsert.

Useful? React with 👍 / 👎.

Comment thread main.go Outdated
grpcSvc := adapter.NewGRPCServer(shardStore, coordinate)
pb.RegisterRawKVServer(gs, grpcSvc)
pb.RegisterTransactionalKVServer(gs, grpcSvc)
pb.RegisterS3BlobFetchServer(gs, adapter.NewS3BlobFetchServer(rt.store, coordinate.Clock(), s3BlobObserver))

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 Gate S3 blob pushes during startup rotation

startRaftTransport opens these gRPC listeners before waitRotateOnStartup.Wait completes, and the existing startup gate is only a unary interceptor over RawKV/TransactionalKV/RaftAdmin/EncryptionAdmin methods. Since PushChunkBlob is a new client-streaming mutator and is registered here without any stream gate, a peer can write chunkblobs while --encryption-rotate-on-startup is still blocking other mutators, bypassing the rotation safety window that keeps writes from landing under the pre-rotation state.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Addressed the current-head S3BlobFetch review feedback.

Changes:

  • require leader-supplied commit_ts on PushChunkBlob and store follower chunkblobs at that timestamp instead of allocating from the follower HLC
  • use the latest chunkblob version timestamp as the direct-write startTS, allowing recreation after a GC tombstone while rejecting stale push timestamps
  • retry direct chunkblob writes while storage writer registration is pending
  • apply the startup rotation gate to the PushChunkBlob streaming RPC

Durability note:

  • PushChunkBlob still uses the direct ApplyMutations path; for Pebble this is the pebble.Sync direct-write path, so Durable=true is sent only after the sync write returns.

Caller audit:

  • NewS3BlobFetchServer is only constructed in raft server registration and tests.
  • PushChunkBlobRequest.commit_ts is currently exercised by tests only; future leader-side callers must send the leader-issued timestamp on the first stream frame.

Verification:

  • make -C proto gen
  • go test ./adapter -run 'TestS3BlobFetch' -count=1 -timeout=240s
  • go test . -run 'TestStartupPublicKVGate|TestStartupGatedCoordinator' -count=1 -timeout=120s
  • go test ./... -run '^$' -count=1 -timeout=300s
  • golangci-lint run --timeout=5m
  • git diff --check
  • git verify-commit HEAD

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0de5f9f36b

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/s3_blob_fetch.go Outdated
Comment on lines +123 to +124
if stored {
return 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 Write the leader timestamp before idempotent ack

When a digest already has a visible payload, this fast path returns Durable=true without validating or appending the supplied commit_ts. If an object reuses an old chunk at timestamp 30 while the follower only has that chunk at timestamp 10, then a GC tombstone at timestamp 20 applied after this ack becomes the latest version and FetchChunkBlob at MaxUint64 returns NotFound, even though the push for timestamp 30 was acknowledged. Please only skip the write when the existing latest version is already at/after the pushed timestamp, or append the duplicate payload at the leader-supplied timestamp.

Useful? React with 👍 / 👎.

Comment thread main.go
Comment thread adapter/s3_blob_fetch.go Outdated
return err
}
if err := s.applyChunkBlobUntilRegistered(stream.Context(), key, payload, startTS, commitTS); err != nil {
if stored, retryErr := s.storedAfterWriteConflict(stream.Context(), err, key, digest, payload); stored || retryErr != 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 Retry when a newer tombstone wins the OCC window

This conflict path only treats ErrWriteConflict as success when the payload is currently visible. If chunkBlobWriteStartTS reads version 10, a GC tombstone at 20 lands before ApplyMutations, and the pushed commit_ts is 30, the write is still valid but this branch falls through as an internal error instead of recomputing the latest timestamp and recreating the chunk. That makes recreation after GC fail under normal races unless the caller happens to retry the whole RPC.

Useful? React with 👍 / 👎.

Comment thread adapter/s3_blob_fetch.go Outdated
@bootjp

bootjp commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Addressed the current-head S3BlobFetch review feedback.

Changes:

  • rewrite already-visible chunkblobs at the leader-supplied commit timestamp unless the latest local version is already at or above that timestamp
  • retry direct chunkblob writes after a concurrent tombstone when the pushed commit timestamp still wins the OCC window
  • recheck the startup rotation gate during PushChunkBlob receive and immediately before each direct write
  • observe successful pushed commit timestamps into the local HLC on the receiving node

Caller audit:

  • NewS3BlobFetchServer is still constructed only in raft server registration and tests; production registration now passes the HLC and startup gate hook.
  • PushChunkBlob is still the only runtime caller of the direct chunkblob write path; generated proto client/server stubs and tests are the only other references.
  • startupRotationGatedMethod already includes S3BlobFetch/PushChunkBlob for stream-open gating; this change adds handler-level checks for long-lived streams.

Verification:

  • rg caller audit for NewS3BlobFetchServer, PushChunkBlob, chunkBlobWriteStartTS, retryAfterChunkBlobWriteConflict, WithS3BlobFetchClock, WithS3BlobFetchPushBlocked, startupRotationGatedMethod, and S3BlobFetch_PushChunkBlob
  • go test ./adapter -run 'TestS3BlobFetch' -count=1 -timeout=240s
  • go test . -run 'TestStartupPublicKVGate|TestStartupGatedCoordinator' -count=1 -timeout=120s
  • go test ./... -run '^$' -count=1 -timeout=300s
  • golangci-lint run --timeout=5m
  • git diff --check
  • git log -1 --show-signature

@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: 5288c41ec0

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/s3_blob_fetch.go
Comment thread adapter/s3_blob_fetch.go Outdated
@bootjp

bootjp commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Current HEAD 5288c41ec008 addresses the remaining active threads:

  • PushChunkBlob now persists via direct ApplyMutations before returning Durable: true; the store contract keeps direct ApplyMutations on pebble.Sync, including under ELASTICKV_FSM_SYNC_MODE=nosync.
  • The startup rotation gate is checked at stream open, before every received frame, before store, and inside the retry loop.
  • The follower-local HLC observes the leader-supplied commit timestamp after successful or idempotent chunkblob writes.

Validation run on this head:

  • go test ./adapter -run 'TestS3BlobFetch' -count=1 -timeout=240s
  • go test ./store -run 'TestDirectApplyMutations_NoSyncConfigured_StillWritesDurably|TestApplyMutationsRaft_NoSync' -count=1 -timeout=120s

@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: 5288c41ec0

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/s3_blob_fetch.go
Comment thread adapter/s3_blob_fetch.go Outdated
Comment on lines +290 to +294
if err := s.store.ApplyMutations(ctx, []*store.KVPairMutation{{
Op: store.OpTypePut,
Key: key,
Value: payload,
}}, nil, startTS, commitTS); 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 chunkblob writes out of Raft snapshots

When PushChunkBlob is exercised, this direct write goes into the same MVCCStore that backs the Raft FSM; the Pebble snapshot path streams every key from that store, so local-only !s3|chunkblob|... payloads will be captured in Raft snapshots and transferred/restored as FSM state. That defeats the offload path's snapshot-size goal and makes snapshots depend on whichever peer-local blobs happened to exist on the snapshotting node; store these blobs in snapshot-excluded local storage or filter this prefix from FSM snapshots.

Useful? React with 👍 / 👎.

Comment thread adapter/s3_blob_fetch.go Outdated
@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 (b06af8a):

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

@bootjp

bootjp commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Addressed current S3BlobFetch findings:

  • S3 chunkblob push now uses a store path that preserves the local LastCommitTS/safe-snapshot watermark while still writing the chunk version at the leader commit timestamp.
  • Writer-registration retry rechecks the push gate before each retry, so a rotation gate closing during backoff stops the next attempt.

Caller audit:

  • ApplyMutationsPreservingLastCommitTS is called only by the S3 chunkblob push path and test wrappers.
  • Existing ApplyMutations callers continue to advance LastCommitTS through the normal path.

Verification:

  • go test ./adapter -run '^TestS3BlobFetch' -count=1 -timeout=240s
  • go test ./store -count=1 -timeout=180s
  • golangci-lint --config=.golangci.yaml run ./adapter ./store --timeout=5m
  • git diff --check
  • git log -1 --show-signature

Note: broader go test ./adapter ./store still reaches the existing Test_grpc_transaction timeout on this branch; the isolated test times out as well and is unrelated to the S3/store watermark change.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: b06af8ae55

ℹ️ 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".

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