Read-path performance budgets¶
dacli's markdown store favors inspectability over a hidden process cache. Read performance therefore depends on explicit command/cycle snapshots: load files once at the start of a stable phase, resolve references from the loaded index, and discard or invalidate that snapshot before a mutation. The next command or cycle loads a fresh view, which is also when sibling events become visible.
Run the production-shape and generated-fixture suite with:
go test -run '^$' -bench 'Benchmark(Brief|Generated|FindTask|TaskIndex|Eventlog|ScaleCreateTask)' -benchmem ./internal/perfbench
For a quick, reproducible write-path scale check, use exactly one measured creation at each scale:
go test ./internal/perfbench
go test -run '^$' -bench '^BenchmarkScaleCreateTask$' -benchtime=1x -benchmem ./internal/perfbench
The first command verifies the fixture contract and allocation ratio. The
benchmark reports setup-ms separately from ns/op: setup renders canonical
task markdown with the production codec, verifies it through the production
parser, and creates the final seed through store.CreateTask so the durable
sequence observation is real. Fixture writes intentionally omit the
temp-file/fsync/rename durability transaction; mdstore tests cover that
transaction, while repeating it thousands of times here measures the fixture
rather than task allocation.
A cold run uses an empty GOCACHE; a warm run repeats the command with the same
cache. Compare ns/op, allocations, and setup-ms from both, but report the
whole-command wall time too. On the 2026-09-02 audit machine (Apple M5 Pro), the
warm four-scale command took 1.48 seconds wall time. Setup ranged from 51.6 ms
at 100 tasks to 399.4 ms at 6,400 tasks; creation stayed at 255–257 allocations.
Before fixture seeding, the same command took about 212 seconds even though the
timed production operation was already allocation-flat.
The generated suite covers 100, 400, and 1,600 tasks and events. Scan time and
allocations should scale approximately linearly with fixture size (a 4x input
should remain below 6x, allowing filesystem noise). Ten reference resolutions
after one task-index load must use less than one third of the allocations of ten
independent FindTask walks; a normal test enforces this relative, machine-
independent guard.
Reference budgets are review triggers, not nanosecond CI assertions:
| Scale | Brief load + render | Task snapshot + 10 lookups | Event scan | Allocations |
|---|---|---|---|---|
| Dogfood: 500 tasks, 1,200 notes, 1,000 events, 250 runs | 150 ms | 20 ms | 55 ms | brief 25 MB / 120k; task phase 6 MB / 35k; events 10 MB / 50k |
| Large: 1,600 tasks, 4,000 notes, 3,200 events, 800 runs | 500 ms | 65 ms | 180 ms | brief 85 MB / 400k; task phase 20 MB / 115k; events 32 MB / 165k |
Absolute timings vary by filesystem and CPU, so CI enforces the allocation ratio and correctness boundaries while benchmark review checks the reference budgets. A budget regression needs either a measured explanation or a change that restores the production shape; archaeology for already-fixed quadratic paths belongs in commit/issue history rather than an executable benchmark.
Task sequence allocation is the write-path exception to full markdown scans.
A locked, durable acceleration state records the next sequence together with
task-directory, tombstone, and Git-ref observations. Matching observations make
creation independent of backlog size; any manual file, removal, ref, malformed
state, or crash drift rebuilds from canonical filenames, tombstones, and Git
history before another number is issued. BenchmarkScaleCreateTask covers 100,
400, 1,600, and 6,400 existing tasks and enforces a 10% maximum allocation
growth envelope from 100 to each larger scale. The guard runs with the explicit
benchmark instead of adding thousands of fixture files to every unit-test run.
TestSyntheticScaleFixtureUsesOneProductionCreate separately prevents the benchmark
harness from drifting back to one durable production creation per seed task.