Skip to content

fix(orchestrator): stop nbd read flood for permanently-missing build objects (rebased #3295) - #3715

Open
AdaAibaby wants to merge 1 commit into
e2b-dev:mainfrom
AdaAibaby:fix/nbd-negative-cache-missing-build
Open

AdaAibaby wants to merge 1 commit into
e2b-dev:mainfrom
AdaAibaby:fix/nbd-negative-cache-missing-build

Conversation

@AdaAibaby

@AdaAibaby AdaAibaby commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stops the NBD read flood (high-frequency ERROR logs + repeated object-store 404s) that occurs when a build object is permanently missing from the store and the Linux NBD kernel driver retries the failing block read in a tight loop.

Rebased replacement for the earlier attempt (#3295), which was closed as no-longer-applying after the diff cache / NBD dispatch refactors. This PR is written against current main; the root causes it fixes are still present there.

Fixes #3294.

Root cause (unchanged, still on main)

DiffStore.GetOrCreate de-duplicates concurrent calls with singleflight but does not cache failed results: when create returns storage.ErrObjectNotExist the error is discarded, so the next kernel retry calls create again → storage 404 → EIO → kernel retry → … And dispatch.go logs every read failure at ERROR, so the expected-404 case floods dashboards.

Changes

  • build/cache.go — negative cache. On create returning storage.ErrObjectNotExist, install a short-lived (60s) entry in a dedicated ttlcache; subsequent GetOrCreate for that key short-circuits without touching storage. The TTL lets a re-uploaded build recover; Add clears the entry immediately. Guards against a concurrent Add: a double-check inside the singleflight closure, and a positive-cache recheck before installing the negative entry, so a diff Add published mid-probe is never shadowed.
  • nbd/dispatch.go — log-level split. ErrObjectNotExist (permanent, expected when a build is gone) is logged at WARN with a clearer message; transient backend errors stay at ERROR.

Test

Added 4 tests in cache_test.go: negative cache short-circuits (create called once), non-ErrObjectNotExist errors are not negatively cached (retried), Add clears the negative entry, and the Add-vs-probe race never shadows an available diff. go build, go vet, gofmt, and the full pkg/sandbox/build suite pass.

/cc @ryan-sana PTAL when free, thanks.

…objects

When a build object is permanently absent from the store, the Linux NBD kernel
driver retries the failing block read in a tight loop. Each retry reaches
DiffStore.GetOrCreate, which does not cache negative results, so every retry
triggers a fresh object-store 404 lookup - producing a high-frequency ERROR log
flood and burning object-store quota.

- Negative cache in GetOrCreate: store a short-lived (60s) negative entry when
  create returns storage.ErrObjectNotExist. Subsequent calls for the same key
  short-circuit immediately without hitting storage. The TTL allows recovery if
  the build is re-uploaded; Add clears the entry so a fresh upload is visible at
  once. A double-check inside the singleflight closure, plus a positive-cache
  recheck before installing the negative entry, prevent a race with a concurrent
  Add shadowing an available diff.

- Log-level split in the nbd dispatcher: ErrObjectNotExist (permanent, expected
  when a build is gone) is logged at WARN with a clearer message; transient
  backend errors stay at ERROR, keeping dashboards and alerting focused on
  actionable signals.

Rebased onto current main (the diff cache and NBD dispatch were refactored since
the original attempt). Fixes e2b-dev#3294.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

orchestrator: nbd read flood when build object missing from store (no negative caching + missing build_id in error log)

2 participants