Skip to content

A reservation ledger, for the case where the floors fit and the caches do not #49

Description

@marcobambini

Narrowed from #31, which closed when gate 1 ran. This is the residue that
survived it, and the residue is smaller and better defined than the original
question.

What #31 settled, and is not up for re-litigation

  • Two K3 opens on a 64 GB machine cannot be fixed by any budget policy.
    docs/LEARNED.md §64: K3's floor is 29.19 GB, almost entirely the resident
    trunk, so two floors are 58.38 GB against a 51.54 GB ceiling. A ledger would
    drive the second process to its floor and the pair would still oversubscribe.
    Gate 1 confirmed the failure mode empirically — neither process reached
    decode, and the machine needed a power cycle.
  • Bounding the ceiling by an instantaneous sample stays refused, for the
    reason in src/memory.c.
  • Explicit budgets stay authoritative. Warn, never clamp.
  • Capacity is solved. waste_usable_ram() is min(physical, cgroup limit).

Where the case actually exists

A ledger only has something to divide when the floors fit and the caches do
not
. That is not a corner — it is what a larger machine running K3 looks
like, and it is the opposite of where #31 was written:

host ceiling 3/4 one open picks two opens two at the floor
64 GB 51.54 GB 46.39 GB 92.78 GB — 135% 58.38 GB — 85%, over the ceiling for one
128 GB 96.00 GB 80.77 GB (floor + 3x) 161.54 GB — 126% 58.38 GB — 46%, comfortable

On 64 GB there is nothing to engineer: the floors alone bust the budget. On
128 GB the second process has 37 GB of headroom above its floor that a ledger
could hand it deliberately instead of leaving it to be discovered by paging.

This is the row #31's table called "K3 + Kimi-Linear, different containers"
generalised. The predicate is not container identity and never was; it is
resident bytes against a ceiling somebody else has already spent, which is
arithmetic waste_open is one input short of doing.

What is already argued about the shape

From #31's thread, so it is not re-derived:

  • kill(pid, 0) is the wrong liveness primitive. Pid reuse leaves a dead
    reservation unprunable, and every future budget on that machine is
    permanently smaller — a symptom nobody attributes to a stale file. Closing it
    with process start times costs three platform paths.
  • flock per reservation is the right one, and is already in the tree. One
    file per reservation held under LOCK_EX for the life of the process; the
    kernel releases it on any exit. A reader takes LOCK_SH | LOCK_NB and a file
    it can lock is dead. The refcounted, atfork-safe, CLOEXEC implementation
    is at src/waste.c:117, landed as exclusive_open in Add opt-in exclusive model-container ownership #29 — same technique,
    different key.
  • Placement has no answer that is both safe and complete. World-writable
    means any local user can publish a 10 TB reservation and starve the machine;
    per-user is safe and blind exactly where the harm is real, on a shared box.
    This is a choice, not a cost, and it comes before a file format.
  • Namespaces are worse. Two containers on one host each see one process,
    believe they are alone, and share one kernel's RAM. Silent under-counting in
    a safety mechanism is worse than no mechanism, because the printed budget
    line then looks like it was checked against something.
  • waste.h says no global state. A cross-process ledger is the first thing
    this project would write outside a container directory. That is a decision
    about what the library is.
  • The "capacity, not pressure" defence is weaker than Two auto-budget opens on one machine each size against the whole machine #31 stated. The sum
    of live reservations is not fixed for the life of the process. What survives
    is that its volatility is declared and attributable: a smaller budget is
    always matched by a real, still-live claim, rather than by a transient that
    has already gone. That is enough to admit the input; it is not determinism,
    and the cost has to be accepted openly.

Gates, and the first one must not be run the way #31's was

#31's gate 1 cost a reboot because the unmanaged arm is the dangerous one.
That is unavoidable here too, so it has to be bounded by the host rather than
by hope.

  1. Does the floors-fit case degrade at all? Two auto-budget K3 opens on a

    64 GB host, against a single-process baseline. Run under a memory cap
    that bounds the damage
    — Linux with memory.max on a cgroup, where the
    OOM killer ends the arm instead of the machine. If the OS degrades
    gracefully when the floors fit, this closes with a dated entry and nothing
    is built.

  2. Would the ledger's outcome have been better? Same pair, both pinned by
    hand to the budget a ledger would have picked — the cap split between them.
    Unlike Two auto-budget opens on one machine each size against the whole machine #31's gate 2 this is a safe arm by construction, since two floors are
    46% of a 128 GB host. If two processes sharing the ceiling are meaningfully
    faster than two contending for all of it, there is something to engineer.
  3. Only then, pick the shape, against the constraints above.

@Lrrr908 has the machine this needs — 128 GB, Linux, and a K3 container, from
#37 — and gate 1 is the arm a 64 GB host can never select. No obligation
attached; noting it because it is the only host in this project's reach where
the question is answerable safely.

Non-goals

  • Any change to explicit --budget.
  • Anything that refuses an open that would otherwise have succeeded. Refusal
    is what exclusive_open already does, opt-in, and Two auto-budget opens on one machine each size against the whole machine #31's criticism of its key
    stands — it just turns out the right predicate has no non-refusing answer in
    the 64 GB case.
  • The 64 GB two-K3 case. §64 closed it; the only things that would cover it are
    refusing, or not duplicating the trunk, which is a different engine shape.

Done when

docs/LEARNED.md has a dated entry with the gate-1 numbers, and either a
mechanism behind them or the reason there is none — the second outcome being as
good as the first.

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