Skip to content

cache/disk: persist the index's read history across restarts so a rolled L1 does not report every resident byte as unknown - #72

Merged
shreyas-blacksmith merged 5 commits into
patchsetfrom
devin/1793634200-persist-access-state
Nov 2, 2026
Merged

shreyas-blacksmith merged 5 commits into
patchsetfrom
devin/1793634200-persist-access-state

Conversation

@shreyas-blacksmith

@shreyas-blacksmith shreyas-blacksmith commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

After a restart the boot scan only has file timestamps, and on our noatime mounts those cannot tell a blob read a minute ago from one nobody ever asked for, so every pre-existing entry re-entered the index as itemStateBootScanned and the tenant census reported its bytes as unknown (36 h after the last roll, 93% of prod resident bytes were still unknown). This PR carries the in-memory read history over the restart.

Stacked on #71 (same access-state semantics); base switches to patchset when that merges.

Shutdown — main.go calls the new disk.Cache.SaveAccessState() right after servers.Wait() (gRPC/HTTP stopped, index final). It walks the LRU once under c.mu and writes <dir>/access-state.v1:

"BRAS\x01" | u32 writtenAt | u64 count | count × (fnv128a(lookupKey) | u32 lastAccess | u8 accessState)

Only itemStateNew/itemStateTouched entries are written (an untouched boot-scanned entry would come back boot-scanned anyway); 21 B/entry. Key hashing dominates, so the walk feeds batches to one encoder per CPU and a single writer; temp file + fsync + rename. Measured 0.31 s / 40 MB for 2M entries on 8 cores → ~5 s / 670 MB for the 31.7M-entry us-west members, inside the unit's TimeoutStopSec=30.

Boot — loadExistingFiles → consumeAccessState(dir) reads the file into a map[[16]byte]{lastAccess,state}, deletes it (consumed exactly once; a later crash cannot replay a stale copy), and scanDir applies it per file:

if rec, ok := bootAccess.lookup(lookupKey, addedAt /* = mtime */); ok {
    item.lastAccess, item.accessState = rec.lastAccess, rec.state
    metadata.ts = rec.lastAccess   // LRU sort order now follows real recency, not frozen atime
}

lookup refuses records for blobs whose mtime is after writtenAt (created after the file; not about this blob) and clamps lastAccess >= addedAt. Missing/garbage/truncated file → logged, removed, scan proceeds on timestamps as today. The scan's "unexpected file in cache root" check skips the two file names.

Steady state is untouched: no per-request work, no new goroutine, no new config. The new code runs only at the two ends of the process.

Tests: real diskCache restart (read blob → touched with its last read, unread → new, blob written after the save → boot-scanned; census unknown/never-read match; LRU front is the read blob), corrupt file + leftover temp file ignored and removed, lookup guards.

Link to Devin session: https://app.devin.ai/sessions/df6c6655558146b9bafe936b062e49e7
Open in Devin Desktop: https://app.devin.ai/desktop/session/df6c6655558146b9bafe936b062e49e7?variant=devin
Requested by: @shreyas-blacksmith


View with [code]smith Run Proof
Need help on this PR? Tag @codesmith-bot with what you need.

shreyas-blacksmith and others added 4 commits November 2, 2026 15:01
…tate, so boot-scanned entries stop inflating evicted_never_read after a restart

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
On shutdown, after the servers have stopped, write each known entry's
access state and last read to <dir>/access-state.v1; the next boot scan
consumes the file so pre-existing blobs come back as new/touched with
their real recency instead of boot-scanned (unknown), and the file is
deleted so a stale copy can never be replayed. Blobs created after the
file was written, and anything without a record, stay on file
timestamps. Steady state is untouched; the cost is one parallel walk of
the index at shutdown (~0.3 s / 2M entries, 21 B per entry) and one map
load at boot.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@blacksmith-staging

blacksmith-staging Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Matches Restart keeps read history in census

Before, a restart turned both blobs into unknown bytes. After, the unread blob stays never-read, the read one counts as touched, and nothing is unknown.

Merge-base

Census across restart

This PR d73ddde4

Census across restart

Also checked · 4 match

Matches Blob newer than the save stays unknown
Matches Corrupt state file ignored and removed
Matches LRU order follows restored recency
Matches Save is fast enough for shutdown

Proof on d73ddde4 (from 8ed21f27) · Did Proof get it right? Yes · No

View full Proof report

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8ed21f2. Configure here.

Comment thread cache/disk/load.go Resolved
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot changed the base branch from devin/1793631618-evicted-never-read-access-state to patchset November 2, 2026 15:59
@shreyas-blacksmith
shreyas-blacksmith merged commit cb17841 into patchset Nov 2, 2026
6 checks passed
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