v0.4.0 — heartbeats stop bloating the beads database
Fix: heartbeats no longer mint a bead + ~4 Dolt commits per beat
beads_global had grown to 2.9 GB / 85,267 Dolt commits, with 21,320 of 21,353 issues being closed State change: hb -> <epoch> event beads (15,698 hb + 5,401 leader-hb).
The obvious suspect — closed-issue volume — was the wrong one. bd admin cleanup --older-than 90 --dry-run deletes zero rows (the DB was days old), and 2.9 GB / 85k commits is ~36 KB/commit: the history is the bloat. That is also why gc only ever reclaimed 2–5% — every commit is reachable from main, so there is nothing unreachable to collect.
Root cause
Both steady-state writers used bd set-state, which by design mints an event bead and rewrites a <dim>:<val> label on every call. Correct for low-cardinality facts (role changed 86 times in the entire history); exactly wrong for a monotonic timestamp that changes every beat.
Measured on the live DB:
| write | Dolt commits | issue rows |
|---|---|---|
set-state on a normal bead |
3.8 | +1 |
bd update -d on a normal bead |
1.0 | 0 |
bd update -d on a --no-history bead |
0.0 | 0 |
set-state on a --no-history bead |
2.0 | +1 (event bead is a new issue) |
Changes
- Heartbeats (session
hb, leader-slotleader_hb) move into the bead's description JSON viabd update -d, folded into the same write that already carried git drift. A timestamp is liveness, not an election decision — it has no audit value. - Session and leader-slot beads are created with
--no-history. rolestays onset-state(genuinely low-cardinality) but is written only on an actual change, instead of spending abdsubprocess every beat to rediscover that nothing changed.
Net: 0 commits and 0 rows per beat for both writers, verified end-to-end.
Also fixed
tests/test_session_isolation.py's fake_bd fixture patched the bd seam in server.py but not in leader.py — and every heartbeat crosses into leader.py. These "unit" tests were spawning real bd against the shared production beads_global. Suite time drops 160–187s → ~6.5s.
Upgrade notes
- Pre-existing leader-slot beads keep costing ~1 commit/beat until migrated by hand:
bd update <slot-id> --no-history. - This stops the bleeding but does not shrink an already-bloated DB. Reclaiming existing space needs
bd flatten --force(squash + GC) with all mailbox daemons stopped. - Caveat:
bdimplements--no-historyby demoting the bead to a wisp (the row moves to thewispstable). Verified thatbd query label=... AND status=openstill returns it with its description intact, solist_sessions/_reap_staleare unaffected — but if presence ever starts dropping sessions that are still heartbeating, wisp TTL compaction is the first thing to suspect.
Full test suite: 64 passed, 1 skipped (opt-in live-bd e2e).
Full Changelog: v0.3.0...v0.4.0