Skip to content

Benchmark Report — Digital Twin (v1.7.0)

Date: 2026-07-08 Package: mineproductivity.digital_twin (software v1.7.0) Scenarios: benchmark/scenarios/digital_twin/ Environment: Windows 11 (AMD64), CPython 3.12.10, mineproductivity editable-installed. Single process, single thread, no other load control — these are reference numbers for order-of-magnitude regression tracking, not a controlled lab measurement.

Both scenarios are standalone scripts (the mineproductivity.benchmark harness package is not yet implemented — see benchmark/README.md); re-run them with:

python benchmark/scenarios/digital_twin/repository_latency.py
python benchmark/scenarios/digital_twin/cold_start_replay.py

1. TwinRepository.get()/list() latency

Populations span "a large site" (10³ twins) through "an enterprise-wide, multi-site deployment, never pruned" (10⁵). get() figures are means over 10,000 calls; list() over 5.

twins get (µs) list all (ms) list by_scope (ms) scope matches
1,000 0.07 0.01 0.78 500
10,000 0.09 0.12 8.97 5,000
100,000 0.08 2.33 99.81 50,000

Reading: get() — the hot per-synchronization path — is flat (~0.08 µs) across a 100× population spread, the O(1) bound design spec §33 requires of the reference implementation and expects any production backend to preserve. list() (with or without a specification) is linear by core.BaseRepository's own contract — the discovery path (§18), not the sync hot path; at the enterprise scale a scope-filtered listing costs ~100 ms, acceptable for the interactive/reporting use it serves.

2. Cold-start reconstruction cost vs. event-history length

Two paths per history length (§15, §33): full replay (EventStore.replay from genesis, folding every relevant envelope through _apply) and snapshot-seeded catch-up (restore from a TwinSnapshot at the 90% point, fold only the 10% tail via a bounded EventStore.query(since_utc=...)).

events full replay (ms) snapshot-seeded (ms) speedup
1,000 0.97 0.53 1.8×
10,000 8.42 4.89 1.7×
50,000 51.59 36.03 1.4×

Reading: full-replay cost is linear in history length, exactly as §33 states (~1 µs/event through the whole replay+fold path). Snapshot seeding cuts the fold work by 90%, but the observed speedup is a modest 1.4–1.8× because the reference _InMemoryEventStore's query()/replay() both scan the full log linearly and implement no EventSnapshot acceleration of their own — digital_twin deliberately inherits whatever replay acceleration the EventStore implementation provides (§33), which for the in-memory reference is none. A production EventStore with time-indexed queries or EventSnapshot-accelerated replay would turn the seeded path's cost into ~10% of the full path; the twin-level mechanics for exploiting it (TwinSnapshot restore + bounded tail fold) are proven correct here (both paths converge on the identical TwinState, asserted on every run).

Regression guidance

Re-run both scenarios before each release touching digital_twin; investigate if get() exceeds ~1 µs at any population, full-replay cost exceeds ~5 µs/event, or the snapshot-seeded path stops converging with full replay (a correctness failure, not a performance one).