Repository navigation
WAL durability modes: none, deferred, strict; batched submission (tracking) #787
Copy link
Copy link
Open
Labels
coreEcho Core TechnologiesEcho Core TechnologiesenhancementNew feature or requestNew feature or requestneeds-witnessMethod closeout state needs-witness.Method closeout state needs-witness.runtimeRuntime coreRuntime core
Description
Tracking container. Groups executable work and owns implementation status for the WAL durability-mode design. Each unit below is one independently mergeable change.
Design
docs/architecture/wal-durability-modes.md (introduced by the PR that references this issue). Summary:
- Three durability modes chosen by the host: none (no WAL), deferred (accepted now, durable shortly after, with an observable durable position), strict (an acknowledgement means durable; today's behavior).
- Batched submission: a frame's submissions share one sync.
- Deferred mode uses an in-process writer thread behind a bounded queue.
- Rules: an acknowledgement states its durability posture; durability reports never feed back into what a tick decides; a full queue or a failed writer is an explicit obstruction, never a silent drop; the bytes on disk are an ordinary Echo WAL so recovery is unchanged.
Evidence
Guarded Docker worker (Linux VM), release build, compare-and-set workload, 120 frames, medians, measured 2026-10-12 on the source of PR #786 merged with #784:
| Actions per frame | No WAL | Filesystem WAL, tmpfs | Filesystem WAL, VM disk |
|---|---|---|---|
| 1 | 0.04 ms | 0.25 ms | 2.13 ms |
| 8 | 0.30 ms | 1.78 ms | 6.20 ms |
| 32 | 1.28 ms | 8.21 ms | 22.57 ms |
At 32 actions per frame on disk, submissions take 15.56 ms (one sync each) and the tick 7.06 ms. One sync_all measured 0.46 ms in the VM and 3.0 ms (F_FULLFSYNC) on the macOS host. A 60 fps frame is 16.67 ms.
Executable units
Edges are proposals, not accepted dependencies.
- Batched strict submission.
submitfor several envelopes appends their ordinary intake transactions and syncs once before acknowledging any. No format change. Witness: sync count per batch is one; crash and fault-injection tests show only a prefix can survive and nothing unacknowledged is reported durable. Prerequisites: none. - Tick record cost. A 32-action tick costs 6.1 ms with a WAL on tmpfs against 1.2 ms without. Find and reduce the encoding and hashing cost. Prerequisites: none.
- Durability posture on acknowledgements. The acknowledgement type states strict-durable or accepted-pending with a log position. Prerequisites: none.
- Deferred mode. In-process writer thread, bounded queue, durable position, explicit obstruction on writer failure. Prerequisite: 3, because deferred acknowledgements need the posture.
- Mode selection and documentation. Host configuration and the WAL topic state the three modes. Prerequisites: 1 and 4.
Open decisions
- Batch atomicity: ordinary transactions with one sync (prefix-durable, no format change) or one transaction carrying many acceptances (all-or-nothing, new transaction shape and recovery support).
- Backpressure policy when the deferred queue is full: block the caller or refuse with an obstruction.
- Whether a strict frame should cover submissions and the tick with a single sync.
Not in scope
An external WAL process or network port; Keep; any release.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
coreEcho Core TechnologiesEcho Core TechnologiesenhancementNew feature or requestNew feature or requestneeds-witnessMethod closeout state needs-witness.Method closeout state needs-witness.runtimeRuntime coreRuntime core
Activity
flyingrobots commentedon Oct 13, 2026
Review corrections to the design (PR #788, now a draft pending owner acceptance; commit fcb881e).
Existing behavior worth its own fix. After a failed append or sync of a single submission,
submit_intent_with_runtime_wal_ackre-reads the log (recover_filesystem_submission_acceptance_after_error) and treats a readable acceptance record as accepted. If the failure was the sync, that record is not known to be durable. This predates this work. It is not changed by #791, which returns the sync error for a batch.flyingrobots commentedon Oct 13, 2026
Unit 2 (tick record cost): findings. Measured in the guarded worker, release build, WAL on tmpfs, 32 Actions per tick.
So the remaining cost is data volume and hashing. Options that need a decision, not just code: retain fewer bytes per Action (what each retained Action outcome must carry), share repeated identities across the frames of one transaction, or hash each payload fewer times on the append path. This matches the study feedback's observation that WAL bytes per fact are large relative to the payload.
flyingrobots commentedon Oct 13, 2026
Pre-existing weaknesses found while reviewing #791 (not introduced or worsened by it; each needs its own change):
TrustedRuntimeWal::from_configruns writable recovery, which can truncate a torn tail, before the writer lock is taken. Two hosts opening one root at the same time could both truncate. Acknowledge a batch of submissions after one WAL sync #791 syncs existing segments at open, which removes the unsynced-tail hazard but not this ordering.refresh_cursor_from_store_for_writerrecordslast_good_commitfor a failed evidence-catalog rebuild from the cursor value before the refresh, not after it.trusted_runtime_host_loop_testsat all, orcausal_wal_hardening_testswith fault injection, before Acknowledge a batch of submissions after one WAL sync #791 added both steps.flyingrobots commentedon Oct 13, 2026
Native macOS measurements (2026-10-13). Apple M5 Pro, internal SSD,
mainat3056a9d9, release profile, Homebrew Rust 1.98.1 (not the pinned 1.96.0), single runs. Storage sync here isF_FULLFSYNC, about 3.7 ms.Scene test: N entities (nodes with one attachment each); each frame submits K compare-and-set Actions on K distinct entities as one batch and ticks until all are committed (64 Actions per head per tick, so K/64 ticks). 30 frames, frame p50 / p95 in ms. "In-memory WAL" builds every WAL record and never touches storage; it is the nearest available stand-in for the caller-side cost of the proposed deferred mode, which is not implemented.
Updates per frame that stay at or above 30 fps (p95 ≤ 33.3 ms): about 1,000 without a WAL (about 850 at 100,000 entities); between 128 and 256 with the in-memory WAL; 64 with the strict disk WAL, with no margin.
Findings:
Fix: state queue waits, mid-batch failure, batch limits and fault pos…
flyingrobots commentedon Oct 13, 2026
Gap found in review of #788:
submit_intents_with_runtime_wal_ackhas no limit on envelope count or encoded bytes, so a caller can submit an arbitrarily large batch against a fixed frame budget. Proposed in the design page: a host-set batch limit, checked against the whole batch before the first append, with a typed over-budget refusal.flyingrobots commentedon Oct 13, 2026
Proposal: deferred durability mode (revision 2, not accepted)
This proposal moved here from PR #788.
docs/architectureholds current architectural truth, so an unaccepted design lives in this issue until the owner accepts it. PR #788 (merged) documents only the batching that shipped in #791, indocs/architecture/batched-strict-submission.md. Revision 2 applies review guidance relayed by the owner on 2026-10-13 to four review findings. That guidance is input to the proposal; the proposal as a whole is not accepted.The idea in one line
The trade is when Echo promises durability. The meaning of "accepted" does not change with the host's performance setting.
Two separate guarantees
A caller learns about durability from a separate value, never from the word "accepted":
Modes
Strict with the in-memory store is not durable; see
docs/architecture/batched-strict-submission.md.Deferred mode
A writer thread inside the Echo process owns the WAL store and its writer-epoch lease. The runtime hands it records through a bounded queue and does not touch storage itself. The writer appends in acceptance order, syncs in groups, and publishes the durable frontier after each sync.
An acknowledgement names its record by identity (writer epoch and transaction) and carries its log position. Identity matters because a position can be reused after a crash loses a record: a durability handle is satisfied only by the record with the same identity.
Admission and queue pressure (replaces the earlier timeout rule)
Review finding: the earlier text let elapsed queue waiting make the host declare a failure, which let writer speed decide what was admitted.
Consequence for determinism: writer timing can change when an input is admitted and therefore which tick first sees it, exactly as host scheduling already can. It cannot change what the runtime decides about an input it has been offered and admitted. Two runs offered the same admitted inputs in the same order produce the same history.
Decided by the owner on 2026-10-13 (the owner explicitly adopted review guidance to this effect). The deferred mode as a whole remains proposed and not accepted:
Writer failure (new)
Review finding: the earlier text did not say what happens to a caller already holding a deferred acknowledgement when the writer fails.
A writer failure is an actual failure signal from the writer (an append or sync error, or the writer thread ending), never an inference from delay.
When the writer fails:
Crash behaviour
A crash in deferred mode may lose records after the durable frontier. Recovery rebuilds a valid prefix of what the runtime accepted, at least up to the frontier and possibly beyond it. It never rebuilds a gap. Recovery reports a lost acknowledgement as lost.
Laws
Also proposed (strict mode)
Alternative not chosen
An external WAL process on a port. Same speed as deferred mode, but it adds a wire protocol, a second process and a second holder of storage authority, and its failure cases differ in both directions (host dies after hand-off; writer dies while host runs on). That is a larger contract than this design needs.
Open decisions (owner)
Whether "not admitted, retryable" leaves evidence.Decided: nothing in authoritative history; optional telemetry only.Whether the submission call waits by default.Decided: return immediately; waiting is opt-in and cancellable.Merge main; document shipped batching only