Skip to content

backup: add admin version API scaffolding#1059

Merged
bootjp merged 11 commits into
design/live-backup-pin-substratefrom
design/live-backup-admin-api
Jul 19, 2026
Merged

backup: add admin version API scaffolding#1059
bootjp merged 11 commits into
design/live-backup-pin-substratefrom
design/live-backup-admin-api

Conversation

@bootjp

@bootjp bootjp commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • add the live-backup admin RPC surface for BeginBackup, RenewBackup, EndBackup, ListAdaptersAndScopes, and GetNodeVersion
  • expose the node build version through the gRPC Admin service and surface leader node versions in GetRaftGroups via an async cache
  • expose the Raft snapshot threshold through the engine interface so backup headroom checks can use the configured value

Base: #1056

Validation

  • go test ./adapter -run 'Test(GetNodeVersion|GetRaftGroups|GroupOrdering|AdminTokenAuth|GetClusterOverview)' -count=1
  • go test . -count=1
  • go test ./internal/raftengine/etcd -run 'Test(SnapshotEvery|EngineSnapshotEvery)' -count=1
  • go test ./kv -run 'Test(CoordinateDispatch|LeaderProxy|LeaseRead|ShardedCoordinator|ValidateReadOnlyShards|LeaseWarmup)' -count=1
  • go test ./internal/raftadmin -count=1
  • golangci-lint run --timeout=5m
  • go test ./... -run '^$' -count=1
  • git diff --check
  • git verify-commit HEAD

Author: 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: 892247df-de13-419f-ae4b-780d2373f688

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 admin API/version scaffolding slice on top of #1056.

Validation:

  • go test ./adapter -run 'Test(GetNodeVersion|GetRaftGroups|GroupOrdering|AdminTokenAuth|GetClusterOverview)' -count=1\n- go test . -count=1\n- go test ./internal/raftengine/etcd -run 'Test(SnapshotEvery|EngineSnapshotEvery)' -count=1\n- go test ./kv -run 'Test(CoordinateDispatch|LeaderProxy|LeaseRead|ShardedCoordinator|ValidateReadOnlyShards|LeaseWarmup)' -count=1\n- go test ./internal/raftadmin -count=1\n- golangci-lint run --timeout=5m\n- go test ./... -run '^$' -count=1\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 a new GetNodeVersion RPC to the Admin service and implements asynchronous leader version probing and caching within GetRaftGroups. It also adds a SnapshotEvery method to the AdminGroup interface and its implementations. The review feedback highlights a potential race condition in probeLeaderVersionAsync due to accessing the request context asynchronously inside a goroutine, and suggests passing a mockable clock time into leaderNodeVersion to improve testability and avoid redundant syscalls.

Important

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

Comment thread adapter/admin_grpc.go Outdated
Comment on lines +563 to +578
func (s *AdminServer) probeLeaderVersionAsync(ctx context.Context, key, address string) {
probe := s.leaderVersionProbe
timeout := s.leaderVersionProbeTimeout
go func() {
probeCtx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if md, ok := metadata.FromIncomingContext(ctx); ok {
probeCtx = metadata.NewOutgoingContext(probeCtx, md.Copy())
}
version, err := probe(probeCtx, address)
if err != nil {
version = ""
}
s.versionCache.Store(key, versionCacheEntry{version: version, fetchedAt: time.Now()})
}()
}

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.

high

In probeLeaderVersionAsync, the incoming gRPC request context ctx is passed directly into a background goroutine, and metadata.FromIncomingContext(ctx) is called asynchronously inside that goroutine.

Since the background goroutine runs asynchronously, the parent GetRaftGroups request may have already completed and returned. Once the request returns, the gRPC server cancels and cleans up the request context. Accessing ctx asynchronously after the handler has returned can lead to race conditions, undefined behavior, or accessing recycled metadata.

To ensure safety and avoid race conditions, extract the metadata synchronously from ctx before spawning the goroutine.

func (s *AdminServer) probeLeaderVersionAsync(ctx context.Context, key, address string) {
	probe := s.leaderVersionProbe
	timeout := s.leaderVersionProbeTimeout
	var md metadata.MD
	if incomingMd, ok := metadata.FromIncomingContext(ctx); ok {
		md = incomingMd.Copy()
	}
	go func() {
		probeCtx, cancel := context.WithTimeout(context.Background(), timeout)
		defer cancel()
		if md != nil {
			probeCtx = metadata.NewOutgoingContext(probeCtx, md)
		}
		version, err := probe(probeCtx, address)
		if err != nil {
			version = ""
		}
		s.versionCache.Store(key, versionCacheEntry{version: version, fetchedAt: time.Now()})
	}()
}

Comment thread adapter/admin_grpc.go Outdated
CommitIndex: st.CommitIndex,
AppliedIndex: st.AppliedIndex,
LastContactUnixMs: lastContactUnixMs,
LeaderNodeVersion: s.leaderNodeVersion(ctx, st.Leader),

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

Pass the mockable clock time now (computed on line 452 using s.now()) into leaderNodeVersion instead of calling time.Now() internally. This ensures consistency with the rest of the GetRaftGroups response (such as LastContactUnixMs) and allows deterministic testing of the cache TTL using the mock clock.

Suggested change
LeaderNodeVersion: s.leaderNodeVersion(ctx, st.Leader),
LeaderNodeVersion: s.leaderNodeVersion(ctx, st.Leader, now),

Comment thread adapter/admin_grpc.go Outdated
Comment on lines +488 to +496
func (s *AdminServer) leaderNodeVersion(ctx context.Context, leader raftengine.LeaderInfo) string {
if version, ok := s.localLeaderVersion(leader); ok {
return version
}
key := leaderVersionCacheKey(leader)
now := time.Now()
if version, ok := s.cachedLeaderVersion(key, now); ok {
return version
}

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

Update the signature of leaderNodeVersion to accept now time.Time from the caller. This avoids redundant time.Now() syscalls in the loop and allows deterministic testing of the cache TTL.

func (s *AdminServer) leaderNodeVersion(ctx context.Context, leader raftengine.LeaderInfo, now time.Time) string {
	if version, ok := s.localLeaderVersion(leader); ok {
		return version
	}
	key := leaderVersionCacheKey(leader)
	if version, ok := s.cachedLeaderVersion(key, now); ok {
		return version
	}

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread main.go Outdated
token = loaded
}
srv := adapter.NewAdminServer(self, members)
srv := adapter.NewAdminServer(self, members, adapter.WithAdminNodeVersion(buildVersion()))

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 leader-version probe into the node service

When a Raft group is led by another node, leaderNodeVersion returns an empty leader_node_version unless leaderVersionProbe is configured, but this production constructor only sets WithAdminNodeVersion and there is no non-test call wiring WithAdminLeaderVersionProbe. That means remote leaders will permanently show an unknown version instead of asynchronously fetching GetNodeVersion; please provide the probe here (or avoid advertising the field until it is wired).

Useful? React with 👍 / 👎.

Comment thread adapter/admin_grpc_test.go Outdated
srv := NewAdminServer(
NodeIdentity{NodeID: "n1", GRPCAddress: "10.0.0.11:50051"},
nil,
WithAdminLeaderVersionProbeTimeout(50*time.Millisecond),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Avoid timing out the async probe before release

This test depends on the goroutine not hitting its 50 ms timeout before the test closes releaseProbe; under a slow CI scheduler, the second GetRaftGroups call or a pause after probeStarted can take longer than that, causing the probe to cache an empty version for the 1 s TTL and waitForLeaderVersion to time out. Use a much larger timeout or release the probe before asserting cache coalescing so the test is not scheduler-sensitive.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

Addressed the current-head admin version review feedback.

Changes:

  • Wire the production leader-version probe through the shared Admin gRPC connection cache.
  • Copy inbound metadata before starting the async probe goroutine.
  • Reuse the GetRaftGroups mockable clock for leader-version cache checks.
  • Increase the async probe test timeout to avoid scheduler-sensitive failures.
  • Added a production wiring regression test that fetches a remote node version through ConfigureAdminService.

Verification:

  • go test ./adapter -run 'Test(GetNodeVersion|GetRaftGroups|GroupOrdering|AdminTokenAuth|GetClusterOverview)' -count=1\n- go test . -run 'TestConfigureAdminService|TestAdminMembersFromBootstrap|TestCanonicalSelfAddress|TestLoadAdminTokenFileRejectsOversize)' -count=1\n- go test ./internal/raftadmin -count=1\n- go test . -count=1\n- go test ./... -run '^$' -count=1\n- golangci-lint run --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: 8f78f4b228

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/admin_grpc.go Outdated
if err != nil {
version = ""
}
s.versionCache.Store(key, versionCacheEntry{version: version, fetchedAt: time.Now()})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Use AdminServer clock for version-cache stamps

When tests or callers install a custom clock with SetClock, the cache freshness checks compare entries against that injected clock, but async probe completions are stamped with wall time here. If the injected clock is behind real time, now.Sub(entry.fetchedAt) stays negative and cached empty/error versions do not expire on the configured TTL; if it is ahead, a just-fetched version can expire immediately. Stamp cache entries with the same clock used by GetRaftGroups so TTL behavior remains consistent.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

Addressed the current-head leader-version cache clock feedback.

Changes:

  • Stamp async leader-version probe completions with the AdminServer clock captured under GetRaftGroups instead of wall time.
  • Added regression coverage where the injected server clock advances past the cache TTL and forces a fresh probe.

Verification:

  • go test ./adapter -run 'TestGetRaftGroupsLeaderVersion(Async|ProbeUsesServerClockForCacheStamp|ProbePropagatesAuthMetadata)|TestGetNodeVersion|TestGetRaftGroups' -count=1 -timeout=240s\n- go test ./adapter -run '^$' -count=1 -timeout=120s\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: 6ece87b2f6

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/admin_grpc.go Outdated
if err != nil {
version = ""
}
s.versionCache.Store(key, versionCacheEntry{version: version, fetchedAt: now()})

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 Guard cache writes from stale probe completions

When a leader-version probe is still in flight after its reservation has expired (for example with a probe timeout longer than the cache TTL, or when the injected server clock advances past the TTL), a later GetRaftGroups call can replace the expired marker and start a fresh probe, but the older goroutine still unconditionally stores its result here. If that older probe then times out or returns an older value after the fresh probe has populated the cache, it overwrites the newer entry and suppresses another probe for a full TTL; use a generation/CAS-style check against the reservation before storing the completion.

Useful? React with 👍 / 👎.

Comment thread main.go
Comment thread adapter/admin_grpc.go
@bootjp

bootjp commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Addressed the current-head admin version inline findings.

Changes:

  • Guarded async leader-version probe completion with a per-reservation generation and sync.Map CAS, so stale probes cannot overwrite fresher cache entries.
  • Scoped leader-version cache/reservation keys to leader ID + address when an address is known, so a failed listener does not poison alternate per-group listener addresses for the same node ID.
  • Made adminBuildVersion an ldflags-backed variable with VCS build-info fallback, and kept configureAdminService/GetNodeVersion plus the admin HTTP cluster version on the same buildVersion() source.

Caller audit:

  • rg shows the leader-version cache helpers are only reached from AdminServer.GetRaftGroups; the external semantic remains LeaderNodeVersion == "" while probing or during an error-cache TTL, and the probed/local version once available.
  • GetNodeVersion is consumed by adminLeaderVersionProbe; buildVersion() is also used by newClusterInfoSource for GET /admin/api/v1/cluster, so both admin surfaces now share the same build identifier.
  • No other caller relies on node-ID-only leader-version cache keys.

Validation:

  • go test ./adapter -run 'TestGetRaftGroupsLeaderVersion(StaleProbeCannotOverwriteFreshCache|TriesAlternateAddressForSameLeaderID)|TestGetRaftGroupsLeaderVersion(Async|ProbeUsesServerClockForCacheStamp|ProbePropagatesAuthMetadata)|TestGetNodeVersion' -count=1 -timeout=240s\n- go test . -run 'TestConfigureAdminService(UsesStampedBuildVersion|WiresLeaderVersionProbe)' -count=1 -timeout=240s\n- go test ./adapter -run '^$' -count=1 -timeout=120s\n- go test ./... -run '^$' -count=1 -timeout=300s\n- golangci-lint run ./adapter/... . --timeout=5m\n- git diff --check\n- git verify-commit HEAD (Good signature, commit ab741c2cead5e82b2ce7176e25b64b68bcad2675)\n\n@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: ab741c2cea

ℹ️ About Codex in GitHub

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

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

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

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

@bootjp

bootjp commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Updated HEAD 7aece2712ca9 addresses the remaining active threads:

  • GetNodeVersion is stamped from buildVersion(), which uses adminBuildVersion when release builds set it and otherwise falls back to Go build info vcs.revision before returning dev. Existing TestConfigureAdminServiceUsesStampedBuildVersion covers the Admin gRPC wiring.
  • Leader-version probing now builds candidate Admin listener addresses for the same leader node ID across registered Raft groups. If the primary listener is stale or unreachable, the async probe tries the alternate listeners before caching an empty failure for that group.

Caller audit:

  • leaderNodeVersion is only called from GetRaftGroups.
  • probeLeaderVersionAsync is only called from leaderNodeVersion.
  • GetNodeVersion/buildVersion wiring is limited to configureAdminService, HTTP cluster info, and the admin listener setup.

Validation run on this head:

  • go test ./adapter -run 'TestGetNodeVersionReturnsConfiguredVersion|TestGetRaftGroupsLeaderVersion(Async|ProbeUsesServerClockForCacheStamp|StaleProbeCannotOverwriteFreshCache|TriesAlternateAddressForSameLeaderID|ProbePropagatesAuthMetadata)' -count=1 -timeout=120s
  • go test . -run 'TestConfigureAdminServiceUsesStampedBuildVersion|TestAdminGRPCGetRaftGroupsLeaderVersionFromRemote' -count=1 -timeout=120s
  • golangci-lint run . ./adapter/... --timeout=5m
  • git diff --check

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

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/admin_grpc.go Outdated
}
version := ""
for _, address := range candidates {
probed, err := probe(probeCtx, address)

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 Bound each leader-version candidate probe

When the first candidate is a stale listener that accepts no connection, the production probe path (adminLeaderVersionProbe via GRPCConnCache.ConnFor) uses a wait-for-ready gRPC call, so this call can consume the entire 500ms probeCtx before returning. The loop then breaks on the expired context and never tries the alternate listener addresses for the same leader ID, caching an empty version for the group even though a later candidate may be reachable. Please give each candidate its own bounded attempt or otherwise avoid spending the whole budget on the first address.

Useful? React with 👍 / 👎.

@bootjp

bootjp commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Addressed the current-head leader-version candidate probe finding.

Changes:

  • Bound each async leader-version candidate probe to a per-candidate timeout under the total probe budget.
  • Kept the total async probe context as the outer cap and preserved inbound metadata propagation per attempt.
  • Added a regression test where the first listener consumes its attempt budget and the alternate listener still succeeds.

Verification:

  • go test ./adapter -run 'TestGetRaftGroupsLeaderVersion' -count=1\n- go test ./adapter -run 'TestGetRaftGroups|TestGetNodeVersion|TestAdmin' -count=1\n- git diff --check\n- git verify-commit HEAD\n\n@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 5cb7ae0f00

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

@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: 259d59ba83

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

LeaderView
StatusReader
ConfigReader
SnapshotReader

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 Update remaining Engine fakes for SnapshotEvery

Embedding SnapshotReader into raftengine.Engine means every concrete value assigned to an Engine field must now define SnapshotEvery(). The existing followerProxyEngine in kv/shard_store_test.go is still assigned to ShardGroup.Engine in the proxy-scan tests but only implements the old method set through Close(), so the kv package no longer compiles until that fake is updated like the other test engines.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Already addressed by signed commit b9686b1, which adds SnapshotEvery to followerProxyEngine. The fix remains in current HEAD e8c0cb2, and both local kv tests and current-head CI compile successfully.

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Current stacked HEAD is a0e32f6b615d905bb5024a7b5e05a10b10533a1d (signed by bootjp <contact@bootjp.me>).

  • merged the latest Add live backup pin FSM substrate #1056 pin-substrate fix, including monotonic renewal deadlines and the corrected TLA+ tool checksum
  • the prior followerProxyEngine.SnapshotEvery() P1 is already fixed by b9686b15040a39ffcb6dfb2aba35595ab4447a34; go test ./kv -count=1 passes on this current HEAD
  • all review objects currently listed by reviews are stale (commit_id != a0e32f6b...)
  • no newer root inline finding exists after the addressed SnapshotEvery finding
  • current CI is running; the previous b9686b... CI was clean

@codex review
@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

@bootjp: I’ll review the current stacked HEAD (a0e32f6b615d905bb5024a7b5e05a10b10533a1d).

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: a0e32f6b61

ℹ️ About Codex in GitHub

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

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

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

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

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Updated stacked HEAD fd9ea8043dbb4caecdd88b85a65b1e1ff654f1a3 now includes the latest #1056 replicated pin-fence fixes. The merge commit is signed by bootjp <contact@bootjp.me>.

Validation:

  • go test ./kv -run Test(ActiveTimestampTracker|FSMCompactorScopesBackupPinsByGroup|ApplyBackup) -count=1
  • base commit 0584f064... also passed full go test ./kv, targeted race, lint, and caller audit

No new root inline finding is present on this HEAD; CI is running.

@codex review
@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

@bootjp: I’ll review the updated stacked HEAD and the replicated pin-fence changes.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd9ea8043d

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread adapter/admin_grpc.go Outdated
Comment on lines +551 to +553
func leaderVersionCacheKey(leader raftengine.LeaderInfo) string {
if leader.ID != "" && leader.Address != "" {
return leader.ID + "\x00" + leader.Address

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 Use per-node cache keys for leader versions

In multi-group deployments the same Leader.ID can appear with a different group listener address for each Raft group, and leaderProbeAddressesLocked already treats those addresses as alternate ways to reach the same node. Including the address in the cache key means a single GetRaftGroups poll over N groups led by the same peer starts N separate async GetNodeVersion probes and stores N cache entries, so the 10s cache does not provide the intended per-leader fanout bound on clusters with many shards. Key by Leader.ID when it is present and use the collected addresses only as probe candidates.

Useful? React with 👍 / 👎.

@bootjp bootjp Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Addressed in signed HEAD e8c0cb2. leaderVersionCacheKey now uses Leader.ID whenever present, while the request-scoped address list remains a set of alternate candidates. The regression test asserts one per-node probe tries the two candidates exactly once.

Comment thread adapter/admin_grpc.go Outdated
if leader.ID == "" {
return addresses
}
for _, id := range sortedGroupIDs(s.groups) {

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 rescanning all groups for every row

When a node hosts many Raft groups, GetRaftGroups calls leaderProbeAddressesLocked once per output row, and this loop sorts and scans the full group map again each time. That turns an admin poll from O(groups) into O(groups² log groups) Status()/sort work before the RPC can return, so large multi-shard nodes can make the overview endpoint noticeably more expensive; build the leader-ID-to-addresses map once per request instead.

Useful? React with 👍 / 👎.

@bootjp bootjp Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Addressed in signed HEAD e8c0cb2. GetRaftGroups snapshots each group status once, then builds the leader-to-address map in one pass. TestGetRaftGroupsSnapshotsEachGroupOnce verifies 64 groups cause exactly 64 Status calls.

Comment thread adapter/admin_grpc.go
Comment on lines +643 to +647
perCandidate := total / time.Duration(candidates)
if perCandidate <= 0 {
return total
}
return perCandidate

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 a sane per-address probe timeout

In multi-group clusters where the same leader has many group listener addresses, dividing the 500ms leader probe budget by the number of candidates can shrink each GetNodeVersion attempt to only a few milliseconds. A cold but healthy TCP/gRPC connection can miss that per-address slice, causing every candidate to time out and the empty result to be cached for the polling TTL even though the peer is reachable; cap the candidate list or enforce a reasonable minimum per-attempt timeout.

Useful? React with 👍 / 👎.

@bootjp bootjp Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Addressed in signed HEAD e8c0cb2. Per-candidate attempts now have a 100ms minimum unless the total probe budget is smaller; the outer total timeout remains the hard cap. Tests cover large candidate counts, a smaller total budget, and the two-candidate split.

@bootjp

bootjp commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Current signed HEAD e8c0cb2 addresses all three current-head leader-version probe findings.

  • cache entries and in-flight reservations are keyed by leader node ID, with listener addresses retained only as alternate probe candidates
  • GetRaftGroups snapshots every group once and builds the node-to-address map in one pass; the 64-group regression observes exactly 64 Status calls
  • per-candidate probe timeouts have a 100ms floor unless the total budget is smaller, while the outer probe timeout remains the hard cap

Caller audit:

  • leaderNodeVersion is called only by GetRaftGroups
  • leaderProbeAddressesByNode and leaderVersionProbeAttemptTimeout are internal to that path
  • async generation/CAS protection and auth metadata propagation remain unchanged

Validation:

  • go test ./adapter -run Test(GetRaftGroups|GetNodeVersion|Admin) -count=1 -timeout=180s
  • go test -race ./adapter -run Test(GetRaftGroupsLeaderVersion|GetRaftGroupsSnapshotsEachGroupOnce|LeaderVersionProbeAttemptTimeout) -count=1 -timeout=240s
  • golangci-lint run ./adapter/... --timeout=5m --allow-parallel-runners
  • git diff --check
  • signed commit verified for bootjp contact@bootjp.me

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: e8c0cb2d12

ℹ️ 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 471c6af into design/live-backup-pin-substrate Jul 19, 2026
14 checks passed
@bootjp
bootjp deleted the design/live-backup-admin-api branch July 19, 2026 06:07
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