next.js
4173ca26 - [turbopack] Don't evict when there is little memory to save (#95213)

Commit
42 days ago
[turbopack] Don't evict when there is little memory to save (#95213) ### What? Adds a new `'auto'` mode to the experimental `turbopackMemoryEviction` config option and makes it the default. In `'auto'` mode, Turbopack only evicts in-memory cache after a snapshot once enough memory has been allocated since the last eviction to make the work worthwhile. The option now accepts three values: - `false`: never evict. - `'auto'` (new default): evict after a snapshot only once a memory threshold has been crossed. - `'full'`: evict all evictable data after every snapshot (the previous behavior). ### Why? When the persistent (FileSystem) cache is enabled, Turbopack snapshots its in-memory state to disk and can then evict those in-memory copies to reclaim memory, reloading them from disk on demand. Previously, with eviction enabled (`'full'`), we evicted after *every* snapshot. This is too aggressive: tasks get restored from disk and then immediately evicted again, cycle after cycle, wasting work for little memory benefit. `'auto'` mirrors the existing persistence-threshold model (we already skip a snapshot when too little compilation time has accumulated to justify its cost). Here the proxy is memory instead of time: it isn't worth paying the restore-then-re-evict churn to reclaim a small amount of memory. This was motivated by an example in v0.app where the client would poll the server every 5 seconds leading to a pathological behavior * client poll -> * next.js ensurePage -> * turbopack writeEndpointToDisk * recompute and restore settings for that endpoint (3-10ms of io work) * realize there is nothing to do * respond to client * 2 seconds later..... persist and evict everything saving 0.5M of ram (100ms of work! writes out 2 SSTs and then compacts them!) * 3 seconds after that restart the loop This is silly, #95137 will prevent the persistence loop from occurring , this PR will also just skip the 'restore and recompute' work using a similar strategy. ### How? We can't measure exactly how much memory a sweep would reclaim, so we use `TurboMalloc::memory_usage()` (process-global net live bytes) as a proxy. In `'auto'` mode an eviction sweep runs only once the net bytes allocated since the last eviction exceed a threshold (default 128 MiB, overridable via `TURBO_ENGINE_EVICT_MIN_BYTES`). The first eviction after startup always runs. The threshold scales down under OS memory pressure (`TurboMalloc::memory_pressure()`) so we evict more eagerly when memory is tight. **Backend (`turbo-tasks-backend`):** - `BackendOptions.evict_after_snapshot: bool` → `eviction_mode: EvictionMode` (`Off` / `Full` / `Auto`). - New `EvictionControl` type owns the policy: the mode plus the threshold bookkeeping. The background snapshot loop calls `should_evict(snapshot_had_new_data)` once per cycle and `record_eviction()` after a sweep, so the loop no longer branches on the mode. `'full'` and `false` behave exactly as before. <!-- NEXT_JS_LLM_PR -->
Author
Parents
Loading