Skip to content

v0.4.0 — heartbeats stop bloating the beads database

Choose a tag to compare

@zach-source zach-source released this 31 Jul 14:45
· 12 commits to main since this release
e7e6486

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-slot leader_hb) move into the bead's description JSON via bd 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.
  • role stays on set-state (genuinely low-cardinality) but is written only on an actual change, instead of spending a bd subprocess 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: bd implements --no-history by demoting the bead to a wisp (the row moves to the wisps table). Verified that bd query label=... AND status=open still returns it with its description intact, so list_sessions/_reap_stale are 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