Skip to content

engine idle floor is ~1.28GB after a reindex (RSS shows 2.4GB; ~1.1GB is reclaimable MADV_FREE) #6027

Description

@cajasmota

Summary

After a full reindex the engine settles at ~2.5 GB RSS and stays there indefinitely, with no work in flight. sched: returned idle heap to OS fires and RSS does not move.

On a 16 GB machine shared with the user's real work — the stated driver for #5954 — this permanent floor matters more than the transient peak the epic has been optimising.

Measured

Full from-scratch reindex of the corpus group (427,301 entities / 1,852,625 relationships), engine at bbf22392f. Clean run: swap flat at 582 MB throughout, available memory never below 6359 MB, so these are not pressure artifacts.

Engine RSS sampled every 2 s:

t=   0s  engine=  693MB     <- freshly restarted
t= 121s  engine= 1806MB
t= 241s  engine= 2190MB
t= 601s  engine= 2494MB
t= 720s  engine= 2495MB
...
t=1921s  engine= 2495MB     <- flat for 22 minutes

sched: returned idle heap to OS idle_for=34s freeosmemory_took=96ms fired at t=914s. RSS after it: unchanged at 2495 MB. Confirmed again 20 minutes after the run ended — 2496 MB, 0.5% CPU, in_flight=0.

For contrast, the same engine before this reindex was 365 MB, and 693 MB immediately after a restart. So the ~2.5 GB is acquired by the reindex and then never released.

Where the time and memory actually go

Same run, phase breakdown:

phase duration peak RSS
extraction (index children) 111 s 3830 MB
engine in-process (resolve refs / links / analytics) ~760 s 2495 MB
community detection (group-algo) 6 s 1697 MB
peak summed across all processes 4767 MB

Two things worth noting for anyone reasoning about this:

  • Extraction and community detection together are 117 s of a ~880 s pipeline. The dominant wall-time cost is now engine in-process work, which emits almost no log lines — the 14-minute window after group-algo contains exactly one message.
  • The 4767 MB peak is transient and serialised (~2 min, during extraction). The 2.5 GB floor is permanent. For the concurrent-worktree-agent workload, the floor is the binding constraint.

Probable cause, unverified

FreeOSMemory on darwin releases pages via MADV_FREE, which does not reduce RSS until the kernel actually reclaims under pressure. If so, the memory is available to the system and the RSS number overstates the harm — but it would still show up as pressure on a machine that is already tight, and it means the log line is claiming something the system does not reflect.

The alternative is that the engine is genuinely retaining live heap after a rebuild (index structures, the prior graph, or analytics scratch never dropped). These have very different fixes, so the first task is to distinguish them — compare runtime.MemStats live heap (next_gc / (1 + GOGC/100)) against RSS at idle, rather than trusting either number alone.

Note heap_inuse is not live heap; that mistake has been made in this epic before.

Why this is filed separately from the peak work

#5954's slices so far (#6009, #6011, #6017, #6025) all target transient peak during the pipeline, and they are measured wins. None of them touch what the engine holds at rest. This is a different axis and needs its own measurement before any fix is designed.

Refs #5954

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions