code wiki / _hdl_build / nx_web_shard_compact.nx
nx_web_shard_compact.nx
buildroot/runtime/_hdl_build/nx_web_shard_compact.nx
about
nx_web_shard_compact.nx -- SCALE compaction for big multi-segment shards (the web shard's rung).
ss_compact/ss_compact_cap have EXACT semantics (last entry per key wins, tombstones kept, retired
segments archived then an atomic manifest swap) but their key dedup is a LINEAR table scan =
O(entries^2): fine at 4k keys, unusable at the web shard's ~1.4M entries (bulk docs + pr: priors).
This organ is the same merge with an open-addressing HASH dedup = O(entries), plus a post-swap
verification pass. Semantics kept byte-equal to ss_compact: chronological walk, last wins, ALL kinds
carried (incl. kind-2 tombstones), archive-before-swap, every failure path returns BEFORE the swap.
BYTE-BUDGETED SUFFIX FOLD (2026-07-29): an unbounded whole-shard merge materializes the merged
segment in anonymous RAM (the writer buffer, plus aux blobs and key tables that all scale with
merged bytes) -- the OOM killer reaped it twice at bytes-in=1883885124 on 2026-07-25, mid-merge,
before the swap (never-brick held; shard untouched). The merge therefore folds a contiguous run
whose .docs bytes fit a budget derived from the box itself (MemAvailable/WSC_BUDGET_SHARE, clamped
to WSC_BUDGET_CEIL; argv[2] in MB overrides, same clamp). Repeated runs converge the shard
LSM-style with bounded memory; a streaming (file-backed) writer that lifts the budget entirely is
the named next rung.
RANGE FOLD (2026-08-10, supersedes suffix-only selection). The suffix rule stalls forever at the
first FAT segment: once a merged product nears the budget it heads every suffix walk (runbytes
starts at the tail), so each later fold must carry it and the hundreds of small segments BEHIND it
can never be reached -- measured on this shard: ~836 pre-08-08 segments stranded behind a ~240MB
merged tail while the live count climbed 878 -> 1205 and query latency followed. A fold of ANY
contiguous run, replaced IN PLACE in the manifest, preserves last-wins semantics by the same
argument the suffix fold's header makes: shadowing depends only on RELATIVE manifest order, and the
merged segment sits exactly where the run sat -- newer kept segments still shadow it, and it still
shadows older ones, precisely as the unmerged run did. Kind-2 tombstones are carried as before.
FAT SKIP: a segment >= budget/WSC_FAT_DIV is an already-merged product; re-folding it buys almost
no count reduction per byte, so runs are chosen from non-fat segments only, newest first.
usage: nx_web_shard_compact <domain> [budget-MB]
license_tier: ORIGINAL
dependencies 2 imports · 0 importers
imports: nx_docportal_search_seg.nxnx_heavyio_lib.nx
imported by: nobody (leaf or entry point)
structs
| none |
consts
| 33 | const WSC_MAGIC_1125899906842597: i64 = 1125899906842597 |
| 34 | const WSC_MAGIC_1048576: i64 = 1048576 |
| 35 | const WSC_MAGIC_65536: i64 = 65536 |
| 37 | const WSC_MAXSEGS: i64 = 8192 // manifest lines we can fold in one run |
| 38 | const WSC_MAXENT: i64 = 134217728 // hard sanity ceiling on entries (2^27; ~an order past any near plan) |
| 39 | const WSC_HMUL: i64 = 7046029254386353131 // odd 63-bit multiplicative-mix constant (same family as pagerank_build) |
| 40 | const WSC_BUDGET_SHARE: i64 = 32 // /32 (was /16) 2026-07-30: the /16 reasoning below is RIGHT about the 9-10x amplification but it reserves a share of MemAvailable AT START on a box with OTHER GROWING CONSUMERS (torrent stack, jellyfin, syno services) -- the share is stale the moment it is computed, and it assumes this job is the only claimant. MEASURED TODAY: with ~25GB available at start the /16 budget projected a peak of ~15GB and this organ was caught at 21GB RSS with 536MB free, minutes from OOM, taking mgmt + the tools daemon down with it. /32 projects ~7-8GB, which also keeps it clear of the 16GiB RLIMIT_AS ceiling now imposed by hc_spawn_searchpipe_job -- TWO CONTROLS THAT CONTRADICT EACH OTHER ARE ONE CONTROL AND ONE OUTAGE, so the soft budget must project a peak BELOW the hard cap. Original note, still accurate on the amplification: fold budget = MemAvailable/16. MEASURED 2026-07-29 (death #3, OOM in ss_build_terms): a 1.88GB run took MemAvailable from 19.4GB to 1.2GB before the kill => PEAK TOUCH ~9-10x run bytes (occ arrays + pairs + writer + key tables + input page cache), not the ~6x the seq1048 memo estimated. /16 caps projected peak at ~60% of MemAvailable so the box keeps serving |
| 41 | const WSC_BUDGET_CEIL: i64 = 209715200 // 200MB HARD CEILING on the fold budget, argv and auto alike. MEASURED: explicit budgets 128/200/230MB all PASSed (2026-08-08) while 256MB died ENOMEM in ss_build_terms under the 16GiB RLIMIT_AS; on 2026-08-10 the UNCLAMPED auto budget (MemAvailable/32 = ~744MB with ~23GB free) died in merge-write on EVERY spawner run -- the searchcompact spawner passes no budget, so the unattended path MUST be self-capping or compaction is structurally dead exactly when nobody is watching. /32 bounds the SHARE of the box; this bounds the ABSOLUTE peak against the address-space cap. Raise only with a re-measured amplification factor. |
| 42 | const WSC_FAT_DIV: i64 = 2 // fat threshold divisor: a segment >= budget/WSC_FAT_DIV is an already-merged product -- skipped by run selection, never re-folded at this budget |
| 43 | const WSC_BUDGET_FALLBACK: i64 = 268435456 // 256MB: used ONLY when /proc/meminfo is unreadable -- conservative enough to be safe on any box that boots this stack, keeps the pinned no-arg hostctl sub from wedging into a permanent refuse (the CEIL clamp above still applies) |
| 44 | const WSC_BUDGET_FLOOR: i64 = 67108864 // 64MB: below this the box is too tight to merge anything safely -> refuse loudly rather than thrash |
| 45 | const WSC_LOCK_EX: i64 = 2 // flock LOCK_EX on <prefix>plock -- the plane-lock discipline segguard + nx_seg_compact_cli already honor; a lockless manifest swap under a concurrent fold LOSES rows (the proven seq349 race) |
| 46 | const WSC_MEMINFO_BUF: i64 = 8192 // scratch size for the bounded /proc/meminfo read (file is ~1.5KB) |
| 47 | const WSC_MEMINFO_CAP: i64 = 7900 // read cap under the scratch -- headroom below WSC_MEMINFO_BUF |
| 48 | const WSC_KB: i64 = 1024 // /proc/meminfo reports MemAvailable in kB |
functions
| 50 | func wc9_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 51 | func wc9_num(v: i64) -> i64 |
| 62 | func wsc_slot(k: *u8, kl: i64, hshift: i64, hmask: i64) -> i64 |
| 74 | func wsc_memavail() -> i64 |
| 113 | func main(argc: i64, argv: *i64) -> i64 |