How PrintStash avoids running out of RAM on large meshes
A technical look at triangle estimates, cgroup-aware memory budgets, render concurrency, chunked rasterization, and safe preview skipping.
PrintStash avoids running out of RAM on large meshes by estimating a mesh’s triangle count before loading it, then skipping geometry and thumbnail generation for anything above VAULT_MESH_MAX_RENDER_TRIANGLES, which defaults to 2,000,000. The model is still indexed and searchable; only its preview is missing.
The behavior came out of an early failure. The first shared-volume scans reached one unusually dense mesh, memory use spiked during thumbnail generation, and the container was killed. On a smaller home server the same file could push the host into heavy swap instead. The file still needed to appear in the library, and skipping its preview let the rest of the scan finish.
Estimate before loading
A normal STL may be easy to preview. A scan or lattice can contain millions of triangles and require far more memory than its file size suggests. Parsing it to discover that it is too large defeats the purpose of a safety check.
The estimate runs before a supported mesh format reaches the full loader, so the decision costs a header read instead of a full parse. A file over the triangle limit is indexed without generating geometry or a thumbnail, although a large 3MF can retain its embedded preview.
VAULT_MESH_MAX_LOAD_MB provides a second, format-independent ceiling when a reliable triangle estimate is unavailable. Its default is 200 MB.
The budget follows the container
Host RAM is not the right number when Docker has a smaller cgroup limit. PrintStash detects the memory available to the process and derives a format-specific triangle cap from VAULT_MESH_MEMORY_BUDGET_FRACTION.
The default fraction is 0.5. The calculated limit is combined with the fixed triangle cap, so a small container skips work that a larger one may render safely. This requires no per-host profile for a normal installation.
Rendering is serialized and chunked
Bulk imports can schedule many background tasks. VAULT_MAX_RENDER_JOBS limits how many expensive mesh renders run at once and defaults to one. If concurrency is increased, the memory budget is divided across those jobs.
The software rasterizer processes faces in chunks controlled by VAULT_MESH_RENDER_FACE_CHUNK_SIZE. Temporary arrays therefore scale with the chunk instead of the total number of faces. Float32 arrays reduce the footprint further without affecting a small thumbnail in a meaningful way.
After a file finishes, the worker asks the system allocator to return freed arenas to the kernel where malloc_trim is available. This keeps a long scan from retaining every previous high-water mark.
What the user sees
When a preview is skipped, the model remains indexed and searchable. The original file is not modified, and its metadata can still be stored where extraction did not require loading the full geometry. Logs explain which limit prevented rendering.
Most installations should keep the defaults. On a memory-constrained host, lower the memory fraction or fixed caps before increasing Docker swap. On a large host, raise them only after measuring a representative import.
The current settings and their memory implications are documented in the configuration reference.