Account for existing store usage when computing dynamic JetStream MaxStore - #8374
Conversation
…Store Signed-off-by: Marcus Ashmond <marcusbrew18@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 812b10ae4f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if err := syscall.Statfs(storeDir, &fs); err == nil { | ||
| // Estimate 75% of available storage. | ||
| ba = int64(uint64(fs.Bavail) * uint64(fs.Bsize) / 4 * 3) | ||
| ba = int64((uint64(fs.Bavail)*uint64(fs.Bsize) + uint64(dirSize(storeDir))) / 4 * 3) |
There was a problem hiding this comment.
Exclude orphaned snapshot staging from dynamic storage sizing
When dynamic JetStream storage starts after a failed snapshot/restore, .snap-staging can still exist under js.config.StoreDir; this size is added here before startup later removes that directory (jetstream.go:1230). In that scenario the computed MaxStore is inflated by 75% of transient data that is immediately deleted, so the server can accept stream reservations beyond the intended disk budget. Please skip snapStagingDir (or clean it before this calculation) when adding existing store usage.
Useful? React with 👍 / 👎.
|
@claude review |
|
Claude finished @neilalexander's task in 2m 38s —— View job Code Review
The fix correctly identifies the root cause and the mathematical approach is sound. Here are my findings, from most to least significant:
|
|
@claude check #8322 (comment) |
|
Claude finished @neilalexander's task in 1m 51s —— View job Analysis: issue #8322 comment by @MauriceVanVeen
The referenced comment is from @MauriceVanVeen (a NATS team member), posted on 2026-06-19 in the original bug report. The full text:
How this differs from the PR's approachThe team's suggestion and the PR's implementation take meaningfully different approaches:
The suggested flow was: recover streams → read their reported usage → add that back to the dynamic limit. This is a two-phase approach where The PR's approach — measure disk usage at Practical implicationsThe PR does fix the ratchet-down bug correctly (the math checks out), but the approach has the One tradeoff worth noting: MauriceVanVeen's suggestion requires that stream recovery itself doesn't fail due to The PR as written is simpler and solves the main problem, but @neilalexander / the team may want to consider whether the implementation should more closely follow the two-phase approach described in the issue comment (recover first, then adjust MaxStore using internal stream byte reporting rather than |
When JetStream runs in dynamic storage mode (no explicit max_file_store configured), the server computes MaxStore as roughly 75% of the currently free disk space on the store directory's filesystem. This calculation is redone on every server restart.
The problem: as JetStream writes data to disk over time, free disk space naturally drops, so the computed MaxStore ceiling also drops on each subsequent restart, even though nothing is actually misconfigured. In practice this means stream configs (sum of max_bytes) that were valid when first created can start failing to update or recreate after a restart, with insufficient storage resources available (err_code=10047), purely because the dynamic ceiling ratcheted downward.
This PR fixes the calculation by adding back the current on-disk size of the JetStream store directory before applying the 75% estimate, so a server that restarts with existing data ends up with a dynamic MaxStore roughly consistent with what it had before, rather than one that shrinks every time.
Resolves #8322
Related: #4140, which raised the same underlying issue with the calculation not accounting for existing usage.
Changes
Testing
Ran the new test in isolation and the broader TestJetStream suite locally to check for regressions in dynamic config behavior.
Note: while running the full test suite, TestJetStreamClusterSnapshotStreamAssetOnShutdown and TestJetStreamClusterStreamResetOnExpirationDuringPeerDownAndRestartWithLeaderChange failed once under load. Both pass reliably in isolation and on a clean checkout without this change, so this appears to be pre-existing flakiness unrelated to this fix.