Skip to content

ciris-server allocator: 15 arenas at the glibc cap plus a 706 MB brk heap 91% paged out — arena cap in-process, brk composition via mallinfo2, then a measured trim trial #552

Description

@emooreatx

Continues #550 (closed by #551, which shipped the instrument) with the bridge-side composition posted on #551 and the CIRISStatus#69 A/B. Filed so the analysis lives on an issue rather than a merged PR.

What is known

Sister node (ciris-status v0.3.61, same substrate, same host): MALLOC_ARENA_MAX=2 cut committed 1458 → 611 MB (−58%), held flat 205 min, uordblks unchanged 44.3 → 44.5 MB. The entire 847 MB was allocator free-list, accumulated while processing zero traces — the churn is the loops, not data volume. keepcost 104 KB afterwards.

This node (ciris-server 0.5.198 on the canonical, 18.8 h, 2 vCPU, 2 GB limit), from smaps: committed anonymous ≈ 2.47 GB, 1.5 GB of it in host swap.

arena-class (64 MB mmap) : n=15   committed=  836 MB    ← the glibc cap: 8 × nproc(2)
large non-arena          : n=7    committed= 1371 MB    ← of which [heap] (brk) 706 MB, 91% paged out
everything else          : n=242  committed=  259 MB

So it is the same disease with a different shape: the arena cap plausibly addresses ~836 MB of 2466, and the 706 MB brk heap — reserved, touched, then cold for hours under memory pressure — is the part a cap cannot consolidate.

What 0.5.199's instrument settles first

GET /v1/node/diagnostics/memory (--diagnostics / CIRIS_DIAGNOSTICS=1, loopback-only) reports mallinfo2 from inside the process. The cold 641 MB of [heap] is one of two things and live_fraction says which:

  • fordblks — freed, not returned: churn/fragmentation, allocation-side fix (fewer, reused buffers in the loops) and/or a release policy;
  • uordblks — live but cold: something retains ~0.6 GB it stopped reading hours ago, a retention defect, and the more interesting one.

Recommended rollout to make that reading clean: at the 0.5.199 restart of the canonical's ciris-server container, set both CIRIS_DIAGNOSTICS=1 and MALLOC_ARENA_MAX=2. With one spare arena at most, mallinfo2.arena is essentially the brk heap plus 64 MB, so fordblks vs uordblks reads directly as the brk heap's composition — which the 15-arena default would blur. The cap's own effect is already known from status; what we do not know is the brk heap, and this measures it in one restart instead of two.

Candidate cures, in the order the reading will pick them

  1. Arena cap in-process, not by deploy config. mallopt(M_ARENA_MAX, 2) at the top of run / py_main on target_env = "gnu", honouring an explicit MALLOC_ARENA_MAX if the environment set one. Every glibc node gets the −58% class of win without an operator knowing the knob exists; Android (scudo), iOS, macOS and Windows are untouched because the call does not exist there. Ready to write; waiting for the canonical reading only so the change lands with a number.
  2. A trim trial, not a trim policy. If fordblks dominates: malloc_trim(0) since glibc 2.8 does NOT only cut the top chunk — it walks every arena and MADV_DONTNEEDs whole free pages inside free chunks, so keepcost (top only) understates what it would release from a fragmented brk heap. The honest test is a loopback-only POST /v1/node/diagnostics/memory/trim under the same switch, called by hand once, with mallinfo2 + RssAnon/VmSwap read before and after. Status's diag.rs deliberately does not trim; a trial door under the operator switch keeps that separation.
  3. The churn source. The loops that allocate on a cadence with no traffic: per-peer replication coordinators paging rows every anti-entropy round, config reconcile (30 s), mesh-config fold, the scorer (60 s, window 500), retention (3600 s), equivocation (900 s, up to 4096 rows), health probes. Status's finding that free lists grew with zero traces says the same is likely here; finding which loop needs an allocation profile on the canonical (or the same node under the mesh harness), after 1 and 2 have taken the easy 1.3 GB off the table.

Not for 0.5.199; the instrument is in it and the first reading decides the order.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions