lightcraft/docs
Brandon Thomas 4788f3bfff Issue #37: Catalog: compact the log on a background thread with ordered journal boundaries
At the 2,000-record threshold the UI thread serialised and fsynced the whole catalog (1-2 s
at 85k photos after streaming). Now `persist()` starts `Journal::snapshot_in_background`:

- the worker writes `catalog.snap` for op N from a clone of the catalog (photos are Arc-shared,
  so the clone is cheap) through `Store::background_writer` (a second FsStore handle; MemStore
  opt-in; none on wasm/web, which stays synchronous);
- appends go on as before (fsynced before every command returns) and are also kept in memory
  as the "tail" (records > N);
- `Journal::poll` (from `persist` and the per-frame `persist_if_dirty`) sees the worker done and
  atomically replaces the log with exactly the tail.

Crash points: before the snapshot rename -> old snapshot + whole log; between the rename and the
log rewrite -> new snapshot + whole log (records <= N skipped as stale, existing rule); after ->
new snapshot + tail. A failed snapshot keeps the log whole and retries after another
max_records records (no retry storm). At most one snapshot in flight; a synchronous snapshot
(close, Optimize Library), reopening a library and dropping the Journal first wait for it, so an
older snapshot can never replace a newer one. On-disk format unchanged.

Tests (crates/catalog/src/tests_background.rs, a gated store holds the worker mid-flight and
"crashes" by copying the files): edits during a background snapshot survive crashes at each
point and the trimmed log holds exactly seq N+1.. in order; failure loses nothing and backs
off; sync snapshot waits (no regression); drop waits; real FsStore with a leftover partial
temp file; a proptest over interleavings of edits / start / land / finish / sync snapshot /
crash. Mutations checked: trimming the log to empty, or not waiting in `snapshot`, fail them.
Engine: `threshold_compaction_runs_in_the_background` (crash copy mid-run, reopen without close).
`library.info` -> persistence gains snapshotRunning, failedSnapshots, maxBlockingMs and
lastSnapshot.{background, blockingMs}.

persist_bench, same session (load avg ~160), UI-thread time per threshold compaction:
  85k photos: synchronous 0.85-1.7 s (median 1.25 / 1.34 s) -> background: blocking median
              45 / 74 ms (clone 1.3-154 ms + log rewrite 9-23 ms); worker 1.2-2.4 s; 230-380
              single-op appends during it at median 4-6 ms (same as without)
  10k photos: synchronous 72-233 ms -> blocking median 12.5 / 14 ms

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 16:01:28 -04:00
..
brand Licensing: MIT OR Apache-2.0 files, NOTICE, ArtCraft brand terms 2026-10-04 10:05:02 -04:00
images docs: README (MCP, CLI, export, status, contributing), AGENTS.md (asset enforcement, crash recovery, testing/perf), screenshots re-captured with Inter 2026-09-30 23:52:34 -04:00
showcase M1.4: crash-safe catalog persistence (op log + snapshot) 2026-09-30 23:40:40 -04:00
control-protocol.md Issue #37: Catalog: compact the log on a background thread with ordered journal boundaries 2026-10-04 16:01:28 -04:00
gpu-pipeline.md M5.6c: one memory budget for caches, decode gate and GPU pool; peak RSS 1.9-2.3 GB -> 0.83-0.99 GB 2026-10-01 11:35:08 -04:00
mcp.md Issue #32: Import: custom date-based folder template for Copy 2026-10-04 11:36:38 -04:00
merge.md Merge main (Photo Merge); M1.14b: stack merge results with their sources 2026-10-01 11:00:51 -04:00
parity.md Merge main into feat/gpx-tracklog 2026-10-04 15:31:45 -04:00
web.md Issue #30: Import Photos… as the clear import entry point 2026-10-04 11:44:15 -04:00
whats-new.md Merge main into feat/gpx-tracklog 2026-10-04 15:31:45 -04:00
xmp-interop.md Merge main into feat/copyright-status 2026-10-04 15:26:53 -04:00