makepad/tools/map_tiles/spool.md
Admin f2f4b1bb50 Spool v2: streaming frontier — the fleet bakes while pass 4 runs
Pass 4 collects relation jobs, bbox-scans them against the RAM stores,
sorts by NL-spiral distance and publishes store/spool-frontier.txt (key
of the first unfinished relation; spool flushed before every publish).
pbf-base slices a LIVE store behind that gate (--bbox only, torn-tail-
tolerant block reads, pid-scoped sort chunks so concurrent slices never
clobber shared edge blocks), and mapfleet claims block per cell on the
frontier instead of waiting for the whole spool.

Pass-granular resume: passes 2/3 stamp exact spool block lengths +
counters + stats; a restart rolls the spool back to the newest stamp
and reruns only the remaining passes (unit tests + crash drill).

Weave fixes from the Luxembourg end-to-end smoke run: bake outputs land
via temp+rename so the ledger name never exists half-written, and
transmux verification now checks the union of ALL sources with first-
source-wins ownership (it compared source 0 alone and failed every
multi-source weave).

Also: pass-4 worker clamp 12→14, dead paged NodeStore/WayStore and
emit_lines/emit_polygons deleted (flat stores/prepare_* replaced them),
mapfleet --pbf + startup chunk sweep + end-of-spiral drain and final
weave, world-build.sh refreshes the -run binary copies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 22:59:15 +02:00

4.3 KiB

Spool v2 notes — single-machine bottleneck analysis

Written 2026-08-02 during the first planet spool (94GB pbf → 430GB store), which gated a 10-worker bake fleet behind ~3h of serial work. Updated the same night after the v2 implementation round: every viable idea below is now built; dispositions inline.

Observed profile (M-series, 16 cores, NVMe)

pass work time bound by
1 scan pbf block index ~3 min I/O
2 nodes ~10B node coords → compressed store ~25 min CPU+write
3 ways ~1.05B ways resolved ~35 min mixed
4 relations ~10M+ assembled >60 min, the tail random-read latency
5 finalize store manifest/marker ~minutes I/O

CPU during pass 4: ~1100% of 1600% — cores idle while member lookups random-read the store. The heavyweight coastline/boundary multipolygons cluster at the end and stall the counter. (Both symptoms addressed: the stores are fully RAM-resident and the spiral sort fronts the monsters.)

Ideas, by expected value

  1. Spiral-ordered finalization (the structural fix). DONE — as a streaming frontier rather than per-region markers. Pass 4 collects all relation jobs, bbox-scans them in parallel against the RAM stores, sorts by distance from the NL anchor (bbox NEAREST point), and the spool writer publishes store/spool-frontier.txt: the spiral key of the first unfinished relation (atomic tmp+rename, spool flushed first, f64::MAX when done). Soundness: a relation that could touch a cell has bbox-nearest <= the cell far-corner distance, so frontier > far_corner means every such relation is fully on disk. pbf-base accepts a live store for --bbox slices behind that same gate (plus torn-tail-tolerant block reads — a torn tail can only belong to uncovered relations), and mapfleet's claim loop blocks per cell on the frontier instead of waiting for the whole spool. The fleet starts on NL while pass 4 is still assembling the far side of the planet.

  2. Offset-sorted member resolution in pass 4. MOOT — pass 4 now loads FlatNodeStore/FlatWayStore fully into RAM; there are no store random-reads left to sort.

  3. Block cache on the compressed node store. MOOT — same reason. The paged NodeStore/WayStore were deleted outright (git has them).

  4. Wider pass-4 parallelism. DONE — assembly and bbox-scan workers now clamp to cores-2 max 14 (was cores-3 max 12).

  5. Early relation filtering. VERIFIED — the untagged skip runs before member collection in the collect pass; nothing to hoist.

  6. Distribution: don't. Unchanged — the fleet's place is after the sort, and with (1) it starts almost immediately anyway.

  7. Pass-granular resume. DONE — passes 2 and 3 write spool.passN.json stamps (exact per-block spool lengths + counters + stats snapshot). A restart with an incomplete store rolls the spool back to the newest stamp (truncate stamped blocks, delete unstamped ones, drop the stale frontier) and reruns only the remaining passes. Covered by spool unit tests (torn-tail rollback + resume) and a crash-drill on the Luxembourg smoke store.

  8. Largest-first relation scheduling. SUPERSEDED by the spiral sort — the planet-scale monsters (coastlines, country boundaries) have bboxes that contain or near the NL anchor, so their nearest- point key is ~0 and they start at hour zero anyway.

  9. Segment-bbox prefilter in clip_ring (the tail's real cost). DONE — implemented as recursive bisection (bisect_polygon): rings split against tile-midpoint halves, so a ring clips against O(log tiles) spans instead of fully against every tile. Geometric equivalence covered by test.

Interaction with pipeline-v2

The long-term streaming pipeline (slice → bake → q11 → shard append, no intermediate sqlite) composes with (1): the frontier feeds per-cell streaming slices, so planet-refresh wall-time approaches max(spool-of-densest-region, fleet-bake) instead of spool + bake.

Smoke rig

local/maps/pbf/smoke/ holds a Luxembourg pbf + store + 4-cell config that exercises the whole chain in ~2 minutes: spool with stamps and frontier, crash/resume drill, live-frontier pbf-base gate (near cell slices, far cell refused), mapfleet with a real worker box, weave with multi-source verification.