Commit graph

2 commits

Author SHA1 Message Date
a50726e6a8 fix(persistence): the idle flush trigger debounces under load — deferred to the floor, never dropped, never a flush-per-gap amplifier
Some checks failed
CI / Node 22 (push) Successful in 12m14s
CI / Node 24 (push) Successful in 12m7s
CI / Bun (latest) (push) Has been cancelled
An internal report from cross-engine write-path instrumentation: with
individual writes slower than the idle window (a contended disk), every
inter-write gap looked idle and fired a background full flush — 15 extra
flushes during 100 contended adds, amplifying the very pressure that
slowed the writes. The law now: an idle fire landing within the spacing
floor of the last flush DEFERS to the floor boundary instead of flushing;
the floor is min(interval, 10× the CONFIGURED idle window) — scaled to
caller intent (a tiny idle window keeps fast idle-driven durability;
default 2s/30s config gets a 20s floor), derived from the configured
idle, never from a deferred re-arm delay (which would compound into
runaway deferral). Deferred is never dropped: a lone write on a
then-quiet store still persists at the floor without any further write
arriving.

Pins: the contended-shape pin (six slow-spaced writes fire ≤2 idle
flushes, not one per gap; then still persist) + the original quiet-store
idle pin unchanged. Unit 2055/2055.
2026-08-10 12:15:02 -07:00
3236a01bef feat(persistence): the engine owns its flush cadence — callers never call flush() in hot paths again
Some checks failed
CI / Node 22 (push) Has been cancelled
CI / Node 24 (push) Has been cancelled
CI / Bun (latest) (push) Has been cancelled
A4 of the service-class pair (SELF-ENGINE-LIFECYCLE-SPRINT, David-directed:
'why do we need manual flushes at all?'). The production disease: 829
caller-scheduled per-write flushes convoying into 45-66s write walls —
cadence hand-rolled a layer above the only layer that can see dirty-node
counts and IO pressure.

- BrainyConfig.persistence: policy 'auto' (DEFAULT) | 'manual', with
  flushEveryWrites (512) / flushIntervalMs (30s) / flushOnIdleMs (2s)
  triggers. Auto = the engine kicks ONE single-flight BACKGROUND flush at
  a threshold or when the store goes quiet; write acks NEVER await it (a
  hung flush cannot block a write — pinned); a failed background flush is
  LOUD and re-arms the trigger. 'manual' restores caller-owned cadence.
- Triggers wired at both write chokepoints (single-op post-commit +
  transact post-commit); idle timer unref'd; close() tears the timer down
  and drains the flight before its own final flush.
- RECOVERY SEMANTICS documented on the config: canonical records are
  durable per-write regardless of policy — a crash between background
  flushes loses derived state only, which converges at next open (epoch
  machinery + the new incremental aggregation catch-up), bounded by the
  un-flushed window. Never data loss.

Pins: write-count trigger fires one background flush with zero caller
calls · idle trigger · manual never self-flushes · THE ACK LAW (writes
acknowledge under a never-resolving flush). Gates: unit 1917/1917 ·
integration 760 · conformance 27/27 — green WITH auto as the default.
2026-08-05 16:00:39 -07:00