Skip to content

perf(api): stream mapping eviction to fix OOM from whole-index loading - #828

Closed
mohammed90 wants to merge 2 commits into
darkweak:masterfrom
mohammed90:perf/stream-mapping-eviction
Closed

mohammed90 wants to merge 2 commits into
darkweak:masterfrom
mohammed90:perf/stream-mapping-eviction

Conversation

@mohammed90

Copy link
Copy Markdown
Contributor

Problem

Production Caddy instances OOM after deploying the latest build. Heap profiling
shows EvictMapping → MapKeys holding 325 MB + 43 MB live at snapshot
time — the eviction job loads the entire Redis mapping index into memory every
interval (default: 1 minute).

Changes

⚠️ Dependency note

The previous pin (core v0.0.20-0.20260314133624-4df176921261) pointed at the
perf/core/reduce-overhead branch, which returns pooled lz4/bufio readers to
their pools before http.Response.Body is consumed — the same use-after-put
race class fixed in storages v0.0.19. The bump moves off that branch; do not
re-pin to it.

Testing

  • go test ./pkg/api/... ./pkg/middleware/... — green.
  • Storer-level batching behavior covered by tests in the storages PR.

Expected impact

  • Eviction memory: O(index size) → O(batch) per run.
  • Steady-state allocation rate drops sharply (64 KB lz4 blocks vs 4 MB).
  • Redis mapping index becomes self-pruning via TTLs.

Rollout notes

  • Recommend setting GOMEMLIMIT (~80% of container limit) and reviewing
    mapping_eviction_interval and max_cacheable_body_bytes.
  • Surrogate-key storage growth is a known remaining issue, addressed separately.

EvictMapping fetched the entire mapping index via MapKeys before
processing it. With the go-redis storage this materialized hundreds of
MB per run (325 MB live in a production heap profile) on every eviction
interval, which is the primary driver of the OOM crashes.

Process mappings one entry at a time through the storer's
MappingWalker when available, falling back to MapKeys for storers that
don't implement it. Peak memory for an eviction run drops from
O(index size) to O(batch).

Temporarily replace darkweak/storages/core with the fork commit that
provides MappingWalker, bounded mapping key TTLs and 64 KB lz4 blocks,
so canary builds can validate the fix. The replace also moves off the
perf/core/reduce-overhead pseudo-version, which pools lz4/bufio readers
that escape through http.Response.Body and can serve truncated
responses under concurrency. Swap the replace for a tagged upstream
release once darkweak/storages merges the changes.
@netlify

netlify Bot commented Jul 7, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for teal-sprinkles-4c7f14 canceled.

Name Link
🔨 Latest commit bac29ea
🔍 Latest deploy log https://app.netlify.com/projects/teal-sprinkles-4c7f14/deploys/6a4d8a17ccf1fa0008e2c5ab

Production goroutine profiles showed one eviction goroutine per
configured cache handler for the same storer: eviction registration
runs in the handler constructor, which executes once per handler and
again on config reloads. All of them walked the same mapping index
concurrently, multiplying its memory cost.

Register at most one eviction goroutine per storer identity for the
process lifetime. Extend the eviction lock expiry on every ownership
check and keep extending it while a walk runs, so walks longer than
the lock TTL don't let other replicas join mid-walk. Previously the
in-process holder comparison also short-circuited all local goroutines
to "owned", making the lock a no-op inside one process.

Drop mapping values larger than 1 MB during eviction without decoding
them: unmarshalling a pathological value materializes it in memory,
and the response keys it references expire through their own TTLs.
@netlify

netlify Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for teal-sprinkles-4c7f14 canceled.

Name Link
🔨 Latest commit fd79f2a
🔍 Latest deploy log https://app.netlify.com/projects/teal-sprinkles-4c7f14/deploys/6a93613ed0425300082df4cc

@mohammed90

Copy link
Copy Markdown
Contributor Author

included in #851

@mohammed90 mohammed90 closed this Sep 13, 2026
@mohammed90
mohammed90 deleted the perf/stream-mapping-eviction branch September 20, 2026 04:47
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