code wiki / _hdl_build / nx_web_shard_compact.nx

nx_web_shard_compact.nx

buildroot/runtime/_hdl_build/nx_web_shard_compact.nx

27291 B464 linesdepth 8pulls 21 transitivereach 0 importersview sourcekind orphan librarytopic web
docsdependenciesstructsconstsfunctions

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

nx_docportal_search_seg.nx nx_heavyio_lib.nx nx_web_shard_compact.nx

imports: nx_docportal_search_seg.nxnx_heavyio_lib.nx

imported by: nobody (leaf or entry point)

structs

none

consts

33const WSC_MAGIC_1125899906842597: i64 = 1125899906842597
34const WSC_MAGIC_1048576: i64 = 1048576
35const WSC_MAGIC_65536: i64 = 65536
37const WSC_MAXSEGS: i64 = 8192 // manifest lines we can fold in one run
38const WSC_MAXENT: i64 = 134217728 // hard sanity ceiling on entries (2^27; ~an order past any near plan)
39const WSC_HMUL: i64 = 7046029254386353131 // odd 63-bit multiplicative-mix constant (same family as pagerank_build)
40const 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
41const 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.
42const 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
43const 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)
44const WSC_BUDGET_FLOOR: i64 = 67108864 // 64MB: below this the box is too tight to merge anything safely -> refuse loudly rather than thrash
45const 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)
46const WSC_MEMINFO_BUF: i64 = 8192 // scratch size for the bounded /proc/meminfo read (file is ~1.5KB)
47const WSC_MEMINFO_CAP: i64 = 7900 // read cap under the scratch -- headroom below WSC_MEMINFO_BUF
48const WSC_KB: i64 = 1024 // /proc/meminfo reports MemAvailable in kB

functions

50func 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 }
51func wc9_num(v: i64) -> i64
62func wsc_slot(k: *u8, kl: i64, hshift: i64, hmask: i64) -> i64
74func wsc_memavail() -> i64
113func main(argc: i64, argv: *i64) -> i64