Files
typewriter/docs/notes/sync-latency.md
Julien Calixte 456c4c43e7 perf(sync): instrument and benchmark commit-staging latency
Break the stage+commit window into sub-phases (FAT working-tree walk vs
object writes) via `commit split —` log lines, and add two micro-benchmarks
(sd_bench for SD/FAT primitive ops, git_bench for libgit2 object overhead)
with justfile recipes. Documents the walk-vs-writes cost model in
tradeoff-curves/sync-commit-staging.md to decide whether explicit-path
staging over the editor's dirty set is worth replacing add_all(["*"]).
2026-07-12 12:24:50 +02:00

5.0 KiB
Raw Permalink Blame History

Sync latency — where the ~16 s cold :sync goes

Measured 2026-07-11 on hardware, via the :sync timing — log line in firmware::git_sync (publish_cycle). A cold :sync (first of a power cycle) is ~16.0 s power-on of Wi-Fi → push done; a warm one skips the one-time setup and is just the ~10 s publish. This note breaks the number down and records why most of it is a floor, not a bug.

Notes index: README.md. Docs index: ../README.md. Why the raw number matters less than it looks: ctrl-g-perceived-latency.md. Energy/keep-Wi-Fi-up tradeoff: ../tradeoff-curves/wifi-auto-sync.md. Sibling timing note: boot-time-budget.md.

The waterfall (cold sync)

From the serial log, first :sync after a cold boot (… wifi 3654ms, clock 2108ms, tls 304ms, publish(commit+push) 9944ms, total 16012ms):

Phase ~ms One-time? Lever
Wi-Fi assoc + DHCP ~3650 yes (per power cycle) radio off until first :sync; association floor
SNTP first sync ~2100 yes varies with NTP RTT (4.2 s the prior run); needed before TLS + commit time
TLS trust store install ~300 yes write ~6 KB CA bundle to SD + set libgit2 option
publish = stage+commit + push ~9900 every sync see below
Total ~16000

The three one-time phases (~6.1 s) only pay on the first sync of a power cycle — Wi-Fi, the clock, and the trust store are set up once and reused, so a warm sync is just the ~10 s publish. Publish splits as:

Sub-phase ~ms Note
stage + commit ~3150 add_all(["*"]) walking the SD/FAT working tree, then commit to FAT
push: TLS handshake ~2400 one mbedTLS handshake to github.com
push: pack negotiate + upload ~4400 tiny delta — cost is negotiation/round-trips, not payload

The win: one TLS handshake, not two

The first hardware run (2026-07-11) measured 23.7 s because it did a pre-commit fetch — a second full TLS handshake plus a ref exchange — on every sync, to absorb a foreign push before committing. That's ~3 s wasted on a normal sync (remote unchanged), and it did ~6 s of real work the one time it absorbed a maintenance commit.

The optimistic-retry rewrite (commit 3386969) drops it: push onto the current tip first; only if the remote rejects the push non-fast-forward do we fetch, reconcile, and retry. The happy path — what runs ~99 % of the time — is now a single handshake. That took the true normal-cold baseline from ~19 s to 16.0 s (and the inflated 23.7 s figure will never recur, since it was the one-time reconcile).

Foreign pushes: reconcile-and-replay, last-writer-wins

On a rejected push, reconcile_onto_origin fetches origin and does a mixed reset onto it — moving the branch ref + index but leaving the working tree, so the just-saved note survives — then stage_and_commit replays the note on the new tip and retries. For this single-writer appliance that resolves last-writer-wins: a concurrent remote edit to the same note loses to ours, and a remote-only added file the card doesn't have would be dropped by the replay's add --all. Both need a real merge (increment B) and don't arise from the device's own use. This path is hardware-verified for the happy case (9b635c42 fast-forwarded clean); the reconcile branch itself is compile-verified but not yet exercised on device.

Can cold sync go lower?

The big rocks are physics or protocol, not slack:

  • Wi-Fi assoc ~3.6 s and SNTP ~24 s are one-time per power cycle and mostly out of our hands (association floor, NTP RTT). Keeping Wi-Fi up between syncs trades battery for latency — see ../tradeoff-curves/wifi-auto-sync.md.
  • TLS handshake ~2.4 s and push negotiate/upload ~4.4 s are inherent to libgit2-over-mbedTLS on this part; the payload is tiny, so there's little to shave.
  • stage + commit ~3.1 s is the one soft spot: staging over the editor's dirty set (add_path) instead of add_all(["*"]) would skip the SD/FAT tree walk (likely → sub-second) without losing multi-file — the dirty set is the file list. Whether the walk actually dominates the ~4 s commit is now being measured by the commit split — log line; the cost model and the rule it decides live in ../tradeoff-curves/sync-commit-staging.md.

Conclusion: ~16 s cold / ~10 s warm is close to the floor for "commit to FAT + one TLS push over Wi-Fi with a fresh clock." It reads as slow only if you wait on it — and by design you don't: :sync is a deliberate action with a snackbar, and ctrl-g-perceived-latency.md argues the perceived cost is set by when durability is surfaced, not by wall-clock. Recorded here so the number is scoped against the protocol, not treated as a regression.