code wiki / _hdl_build / nx_store_compact.nx

nx_store_compact.nx source

↩ module page · 398 lines · 19759 B

1// nx_store_compact.nx -- collapse a quadratic seg-store plane back to O(rows). RK013 sev9 / F829. 2// 3// THE DEFECT (measured 2026-07-20, root-caused from source, not theorised): 4// sts_seed commits the WHOLE plane as a NEW segment on EVERY write (nx_store_seed_lib:45/65), 5// sts_load does one ss_get PER ROW (:70), and ss_scan_seglist walks ALL segments chronologically 6// for each key (nx_seg_store:1136). So a load costs O(rows x segments). Live numbers: debt- had 7// 464 segments / 42MB of .docs for ~200KB of actual rows (~210x amplification), ~124k segment-key 8// scans per load, one nx_debt add took >15min then FAILED, nx_debt page was OOM-KILLED, and the 9// store reached 15,024 files. The NAS went down under that load the same afternoon. 10// 11// THE INSIGHT THAT MAKES THE FIX SAFE AND CHEAP: because every segment is ALREADY a complete plane 12// snapshot, and ss_scan_seglist resolves each key to its LAST (newest) version, the NEWEST SEGMENT 13// ALONE IS A SEMANTICALLY COMPLETE PLANE -- including tombstones. So compaction does not merge, copy 14// or rewrite any record: it rewrites ONLY THE MANIFEST to reference the newest segment. 15// - The superseded .docs/.idx files are LEFT ON DISK, untouched (rule 13 additive-only). Nothing is 16// deleted; the manifest is the only mutation, and it is backed up first. 17// - Therefore ROLLBACK IS ONE FILE COPY: restore <prefix>manifest.txt.bak-precompact. 18// 19// PROOF, NOT ASSERTION: apply loads the plane BEFORE, rewrites the manifest, loads AFTER, and compares 20// the two buffers BYTE-FOR-BYTE. Any difference -> the manifest is RESTORED and the organ REFUSES 21// (exit 3). A caller can never be left with a silently-lossy plane. 22// nx_store_compact <prefix> check -> segment count + newest segid (CHEAP: manifest only, no load) 23// nx_store_compact <prefix> apply -> the verified rewrite above (sts planes ONLY: every segment 24// is a whole-plane snapshot there) 25// nx_store_compact <prefix> fold -> GENERIC merge compaction via the store's own ss_compact_cap 26// (latest entry per key, tombstones kept, O(data) single pass). 27// REQUIRED for reg planes (nx_registry: one record + index per 28// segment -- newest segment is NOT a complete plane, so apply's 29// manifest re-point would lose every other key). Verified by a 30// pre-fold logical snapshot re-read AFTER through production 31// ss_get; mismatch -> manifest restored, REFUSE (exit 3). 32// FAIL-CLOSED: missing/empty manifest, a load that fills the buffer (cannot verify a truncated view), 33// or a byte-mismatch all refuse without leaving the plane changed. 34// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 35import "nx_store_seed_lib.nx" 36import "nx_seg_store.nx" 37import "nx_syscalls.nx" 38 39const SC_LOADCAP: i64 = 8388608 40const SC_KEYCAP: i64 = 1024 41const SC_MFCAP: i64 = 262144 42const SC_PATHCAP: i64 = 1024 43const SC_MSGCAP: i64 = 1024 44const SC_NL: i64 = 10 45const SC_DASH: i64 = 45 46const SC_ZERO: i64 = 48 47const SC_NINE: i64 = 57 48const SC_MODE: i64 = 420 49const SC_LOCK_EX: i64 = 2 50const SC_STDERR: i64 = 2 51const SC_EXIT_USAGE: i64 = 2 52const SC_EXIT_REFUSED: i64 = 3 53const SC_EXIT_IO: i64 = 1 54 55func sc_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 56func sc_puts(s: *u8) -> i64 { sys_write(1, s, sc_slen(s)); return 0 } 57func sc_werr(s: *u8) -> i64 { sys_write(SC_STDERR, s, sc_slen(s)); return 0 } 58func sc_putn(v: i64) -> i64 { let b: *u8 = sys_mmap(32); let e: i64 = ss_catn(b, 0, v); sys_write(1, b, e); return 0 } 59func sc_eqs(a: *u8, b: *u8) -> i64 { 60 var i: i64 = 0 61 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 62 if b[i] != (0 as u8) { return 0 } 63 return 1 64} 65func sc_path(prefix: *u8, tail: *u8, out: *u8) -> i64 { 66 var o: i64 = ss_cat(out, 0, prefix) 67 o = ss_cat(out, o, tail) 68 out[o] = 0 as u8 69 return o 70} 71func sc_read(path: *u8, b: *u8, cap: i64) -> i64 { 72 let fd: i64 = sys_openat_rd(path) 73 if fd < 0 { return 0 - 1 } 74 var n: i64 = 0 75 var go: i64 = 1 76 while go == 1 { 77 let r: i64 = sys_read(fd, (b as i64 + n) as *u8, cap - n) 78 if r > 0 { n = n + r } else { go = 0 } 79 if n >= cap { go = 0 } 80 } 81 sys_close(fd) 82 return n 83} 84func sc_write(path: *u8, b: *u8, n: i64) -> i64 { 85 let fd: i64 = sys_openat_wr(path, SC_MODE) 86 if fd < 0 { return 0 - 1 } 87 sys_write(fd, b, n) 88 sys_fsync(fd) 89 sys_close(fd) 90 return 0 91} 92// count "seg-" lines and return the MAX segid via segmax[0] 93func sc_scan_manifest(b: *u8, n: i64, segmax: *i64) -> i64 { 94 var cnt: i64 = 0 95 var mx: i64 = 0 - 1 96 var i: i64 = 0 97 while i < n { 98 var le: i64 = i 99 var s: i64 = 1 100 while s == 1 { if le >= n { s = 0 } else { if b[le] == (SC_NL as u8) { s = 0 } else { le = le + 1 } } } 101 if le > i { 102 // parse trailing digits after the last '-' 103 var d: i64 = le 104 var g: i64 = 1 105 while g == 1 { g = 0; if d > i { let c: i64 = b[d-1] as i64; if c >= SC_ZERO { if c <= SC_NINE { d = d - 1; g = 1 } } } } 106 if d < le { if d > i { if b[d-1] == (SC_DASH as u8) { 107 var v: i64 = 0 108 var k: i64 = d 109 while k < le { v = v * 10 + ((b[k] as i64) - SC_ZERO); k = k + 1 } 110 cnt = cnt + 1 111 if v > mx { mx = v } 112 } } } 113 } 114 i = le + 1 115 } 116 segmax[0] = mx 117 return cnt 118} 119 120// ---- FOLD (generic merge compaction, any plane shape) ------------------------------------------------ 121// Snapshot the plane's logical state by ONE chronological pass over every manifest segment's .docs 122// (each file read ONCE via ss_readall -- O(data), NOT O(keys x segments): a per-key ss_get walk over a 123// 1355-segment plane leaked >15GB of per-probe mappings and had to be killed, 2026-07-30). Last entry 124// per key wins, exactly ss_get's scan semantics. Tables: key ptr/len, kind (1=put 2=tombstone), val 125// ptr/len. Returns nk (keys), or -1 manifest read fail, -2 key-table overflow, -3 oversized key. 126func sc_snapshot(prefix: *u8, mfb: *u8, mfn: i64, tkp: *i64, tkl: *i64, tkind: *i64, tvp: *i64, tvl: *i64, maxk: i64) -> i64 { 127 var nk: i64 = 0 128 // DEDUP INDEX (2026-08-07): open-addressed hash -> key-slot+1 (0 = empty), power-of-two so the 129 // probe masks instead of dividing, sized 2x maxk to hold the load factor <= 0.5. The hash is an 130 // INDEX, never an identity -- every probe still byte-verifies with ss_kcmp, so a collision costs 131 // one extra comparison and can never merge two distinct keys. 132 // NOTE the comment at the head of this function: it records fixing the OTHER quadratic here (the 133 // per-key ss_get walk that leaked >15GB on a 1355-segment plane, 2026-07-30). THIS one survived 134 // that fix -- for EVERY record it walked the ENTIRE key table with NO early exit, because the 135 // `if hit < 0` guard skipped only the comparison, never the iteration. 136 // ★FIXING ONE QUADRATIC IN A FUNCTION DOES NOT MAKE THE FUNCTION LINEAR. 137 var hcap: i64 = 16 138 while hcap < maxk * 2 { hcap = hcap * 2 } 139 let hidx: *i64 = sys_mmap(8 * hcap) as *i64 140 var ls: i64 = 0 141 var li: i64 = 0 142 while li <= mfn { 143 var eol: i64 = 0 144 if li == mfn { eol = 1 } else { if mfb[li] == (SC_NL as u8) { eol = 1 } } 145 if eol == 1 { 146 if li > ls { 147 let path: *u8 = sys_mmap(SC_PATHCAP) 148 var o: i64 = ss_cat(path, 0, prefix) 149 var c: i64 = 0 150 while c < li - ls { path[o] = mfb[ls + c]; o = o + 1; c = c + 1 } 151 o = ss_cat(path, o, ".docs" as *u8) 152 path[o] = 0 as u8 153 let szp: *i64 = sys_mmap(16) as *i64 154 let b: *u8 = ss_readall(path, szp) 155 if (b as i64) != 0 { 156 let sz: i64 = szp[0] 157 var i: i64 = 0 158 while i + 9 <= sz { 159 let kind: i64 = b[i] as i64 160 let kl: i64 = ss_r32(b, i + 1) 161 let koff: i64 = i + 5 162 let vl: i64 = ss_r32(b, koff + kl) 163 let voff: i64 = koff + kl + 4 164 if kl >= SC_KEYCAP { return 0 - 3 } 165 let kp: *u8 = ((b as i64) + koff) as *u8 166 var probe: i64 = ss_khash(kp, kl) & (hcap - 1) 167 var hit: i64 = 0 - 1 168 var probing: i64 = 1 169 while probing == 1 { 170 let e: i64 = hidx[probe] 171 if e == 0 { probing = 0 } else { 172 if ss_kcmp(tkp[e - 1] as *u8, tkl[e - 1], kp, kl) == 0 { hit = e - 1; probing = 0 } 173 else { probe = (probe + 1) & (hcap - 1) } 174 } 175 } 176 if hit < 0 { 177 if nk >= maxk { return 0 - 2 } 178 hit = nk 179 hidx[probe] = nk + 1 180 nk = nk + 1 181 } 182 tkp[hit] = (b as i64) + koff 183 tkl[hit] = kl 184 tkind[hit] = kind 185 tvp[hit] = (b as i64) + voff 186 tvl[hit] = vl 187 i = voff + vl 188 } 189 } 190 } 191 ls = li + 1 192 } 193 li = li + 1 194 } 195 return nk 196} 197 198func main(argc: i64, argv: *i64) -> i64 { 199 if argc < 3 { sc_werr("usage: nx_store_compact <prefix> check|apply|fold (apply = sts manifest re-point; fold = generic merge via ss_compact_cap, REQUIRED for reg planes)\n" as *u8); sys_exit(SC_EXIT_USAGE); return SC_EXIT_USAGE } 200 let prefix: *u8 = argv[1] as *u8 201 let verb: *u8 = argv[2] as *u8 202 203 let mfp: *u8 = sys_mmap(SC_PATHCAP) 204 sc_path(prefix, "manifest.txt" as *u8, mfp) 205 let mfb: *u8 = sys_mmap(SC_MFCAP) 206 let mfn: i64 = sc_read(mfp, mfb, SC_MFCAP - 1) 207 if mfn <= 0 { sc_werr("MANIFEST ABSENT/EMPTY (fail-closed): " as *u8); sc_werr(mfp); sc_werr("\n" as *u8); sys_exit(SC_EXIT_REFUSED); return SC_EXIT_REFUSED } 208 let segmax: *i64 = sys_mmap(16) as *i64 209 let segs: i64 = sc_scan_manifest(mfb, mfn, segmax) 210 if segs <= 0 { sc_werr("MANIFEST has no seg- rows (fail-closed)\n" as *u8); sys_exit(SC_EXIT_REFUSED); return SC_EXIT_REFUSED } 211 212 if sc_eqs("check" as *u8, verb) == 1 { 213 sc_puts("COMPACT-CHECK prefix=" as *u8) 214 sc_puts(prefix) 215 sc_puts(" segments=" as *u8) 216 sc_putn(segs) 217 sc_puts(" newest_segid=" as *u8) 218 sc_putn(segmax[0]) 219 sc_puts(" scan_cost_multiplier=" as *u8) 220 sc_putn(segs) 221 sc_puts("x (a load walks every segment per key)\n" as *u8) 222 sys_exit(0) 223 return 0 224 } 225 226 if sc_eqs("fold" as *u8, verb) == 1 { 227 // ---- GENERIC MERGE COMPACTION: ss_compact_cap folds ALL segments into one (latest entry per 228 // key, tombstones kept, retired names archived, atomic manifest swap; every failure path is 229 // BEFORE the swap). Works for reg planes, whose newest segment is NOT a complete snapshot -- 230 // the sts-only "apply" manifest re-point would LOSE data there. Wrapped in proof: snapshot the 231 // logical plane FIRST (one O(data) pass), then re-read every key AFTER through the production 232 // ss_get path; any mismatch -> manifest RESTORED from backup, REFUSE. 233 if segs == 1 { sc_puts("NO-OP already single-segment\n" as *u8); sys_exit(0); return 0 } 234 let lkp2: *u8 = sys_mmap(SC_PATHCAP) 235 sc_path(prefix, "plock" as *u8, lkp2) 236 let lfd2: i64 = sys_openat_append(lkp2, SC_MODE) 237 if lfd2 < 0 { sc_werr("REFUSED: cannot open plane lock (plock)\n" as *u8); sys_exit(SC_EXIT_REFUSED); return SC_EXIT_REFUSED } 238 sys_flock(lfd2, SC_LOCK_EX) 239 // BEFORE: logical snapshot, key-table sized data-driven (every entry >= 9 bytes) 240 var tot: i64 = 0 241 var ls2: i64 = 0 242 var li2: i64 = 0 243 while li2 <= mfn { 244 var eol2: i64 = 0 245 if li2 == mfn { eol2 = 1 } else { if mfb[li2] == (SC_NL as u8) { eol2 = 1 } } 246 if eol2 == 1 { if li2 > ls2 { tot = tot + 1 } ls2 = li2 + 1 } 247 li2 = li2 + 1 248 } 249 let maxk: i64 = SC_LOADCAP / 9 + 16 250 let tkp: *i64 = sys_mmap(8 * maxk) as *i64 251 let tkl: *i64 = sys_mmap(8 * maxk) as *i64 252 let tkind: *i64 = sys_mmap(8 * maxk) as *i64 253 let tvp: *i64 = sys_mmap(8 * maxk) as *i64 254 let tvl: *i64 = sys_mmap(8 * maxk) as *i64 255 let nk: i64 = sc_snapshot(prefix, mfb, mfn, tkp, tkl, tkind, tvp, tvl, maxk) 256 if nk <= 0 { sc_werr("REFUSED: snapshot failed/empty (nothing to verify against)\n" as *u8); sys_exit(SC_EXIT_REFUSED); return SC_EXIT_REFUSED } 257 // back the manifest up FIRST: rollback must be one file copy 258 let bkp2: *u8 = sys_mmap(SC_PATHCAP) 259 sc_path(prefix, "manifest.txt.bak-precompact" as *u8, bkp2) 260 if sc_write(bkp2, mfb, mfn) < 0 { sc_werr("REFUSED: cannot write manifest backup\n" as *u8); sys_exit(SC_EXIT_IO); return SC_EXIT_IO } 261 let segid: i64 = ss_next_segid(prefix) 262 let rc: i64 = ss_compact_cap(prefix, segid, segs + 64) 263 if rc < 0 { 264 sc_werr("REFUSED: ss_compact_cap failed rc=" as *u8) 265 let m0: *u8 = sys_mmap(SC_MSGCAP) 266 var mo0: i64 = ss_catn(m0, 0, rc) 267 mo0 = ss_cat(m0, mo0, " (live manifest untouched -- all failure paths precede the swap)\n" as *u8) 268 sys_write(SC_STDERR, m0, mo0) 269 sys_exit(SC_EXIT_REFUSED) 270 return SC_EXIT_REFUSED 271 } 272 // AFTER: every snapshotted key re-read through the PRODUCTION ss_get path must agree 273 var same: i64 = 1 274 var badk: i64 = 0 - 1 275 let kbuf: *u8 = sys_mmap(SC_KEYCAP) 276 let po: *i64 = sys_mmap(16) as *i64 277 let lo: *i64 = sys_mmap(16) as *i64 278 var vk: i64 = 0 279 var puts_n: i64 = 0 280 var tombs_n: i64 = 0 281 while vk < nk { 282 let kpn: *u8 = tkp[vk] as *u8 283 var kc: i64 = 0 284 while kc < tkl[vk] { kbuf[kc] = kpn[kc]; kc = kc + 1 } 285 kbuf[tkl[vk]] = 0 as u8 286 let st: i64 = ss_get(prefix, kbuf, po, lo) 287 if tkind[vk] == 1 { 288 puts_n = puts_n + 1 289 if st != 1 { same = 0; badk = vk } else { 290 if lo[0] != tvl[vk] { same = 0; badk = vk } else { 291 let va: *u8 = tvp[vk] as *u8 292 let vb: *u8 = po[0] as *u8 293 var q: i64 = 0 294 while q < tvl[vk] { if va[q] != vb[q] { same = 0; badk = vk; q = tvl[vk] } else { q = q + 1 } } 295 } 296 } 297 } else { 298 tombs_n = tombs_n + 1 299 if st != 0 { same = 0; badk = vk } 300 } 301 if same == 0 { vk = nk } else { vk = vk + 1 } 302 } 303 if same == 0 { 304 sc_write(mfp, mfb, mfn) 305 sc_werr("REFUSED: post-fold ss_get view DIFFERS at key #" as *u8) 306 let m1: *u8 = sys_mmap(SC_MSGCAP) 307 var mo1: i64 = ss_catn(m1, 0, badk) 308 mo1 = ss_cat(m1, mo1, " -- manifest RESTORED, plane unchanged\n" as *u8) 309 sys_write(SC_STDERR, m1, mo1) 310 sys_exit(SC_EXIT_REFUSED) 311 return SC_EXIT_REFUSED 312 } 313 sc_puts("FOLDED prefix=" as *u8) 314 sc_puts(prefix) 315 sc_puts(" segments=" as *u8) 316 sc_putn(segs) 317 sc_puts(" -> 1 (seg-" as *u8) 318 sc_putn(segid) 319 sc_puts(") keys=" as *u8) 320 sc_putn(puts_n) 321 sc_puts(" tombstones=" as *u8) 322 sc_putn(tombs_n) 323 sc_puts(" verified via production ss_get BYTE-IDENTICAL; retired segment FILES left on disk + archived (rule 13); rollback = restore " as *u8) 324 sc_puts(bkp2) 325 sc_puts("\n" as *u8) 326 sys_exit(0) 327 return 0 328 } 329 330 if sc_eqs("apply" as *u8, verb) == 1 { 331 if segs == 1 { sc_puts("NO-OP already single-segment\n" as *u8); sys_exit(0); return 0 } 332 // serialize against plane writers (nx_debt/nx_store_put use <prefix>plock) 333 let lkp: *u8 = sys_mmap(SC_PATHCAP) 334 sc_path(prefix, "plock" as *u8, lkp) 335 let lfd: i64 = sys_openat_append(lkp, SC_MODE) 336 // FAIL-CLOSED LOCK (2026-07-29 seq1254): proceeding UNLOCKED on a failed plock open lets a 337 // compact race live writers -- a lost manifest RMW then drops their committed lines silently. 338 if lfd < 0 { sc_werr("REFUSED: cannot open plane lock (plock) -- compacting UNLOCKED can race a live writer and drop its manifest line\n" as *u8); sys_exit(SC_EXIT_REFUSED); return SC_EXIT_REFUSED } 339 sys_flock(lfd, SC_LOCK_EX) 340 341 // BEFORE: the authoritative logical plane 342 let bufA: *u8 = sys_mmap(SC_LOADCAP) 343 let na: i64 = sts_load(prefix, bufA, SC_LOADCAP) 344 if na <= 0 { sc_werr("REFUSED: plane loads empty (nothing to verify against)\n" as *u8); sys_exit(SC_EXIT_REFUSED); return SC_EXIT_REFUSED } 345 if na >= SC_LOADCAP { sc_werr("REFUSED: plane fills the load buffer -- cannot verify a TRUNCATED view\n" as *u8); sys_exit(SC_EXIT_REFUSED); return SC_EXIT_REFUSED } 346 347 // back the manifest up FIRST: rollback must be one file copy 348 let bkp: *u8 = sys_mmap(SC_PATHCAP) 349 sc_path(prefix, "manifest.txt.bak-precompact" as *u8, bkp) 350 if sc_write(bkp, mfb, mfn) < 0 { sc_werr("REFUSED: cannot write manifest backup\n" as *u8); sys_exit(SC_EXIT_IO); return SC_EXIT_IO } 351 352 // the ONLY mutation: a manifest naming just the newest segment 353 let nmb: *u8 = sys_mmap(SC_PATHCAP) 354 var o: i64 = ss_cat(nmb, 0, "seg-" as *u8) 355 o = ss_catn(nmb, o, segmax[0]) 356 nmb[o] = SC_NL as u8 357 o = o + 1 358 if sc_write(mfp, nmb, o) < 0 { sc_werr("REFUSED: cannot write manifest\n" as *u8); sys_exit(SC_EXIT_IO); return SC_EXIT_IO } 359 360 // AFTER: must be byte-identical or we put it all back 361 let bufB: *u8 = sys_mmap(SC_LOADCAP) 362 let nb: i64 = sts_load(prefix, bufB, SC_LOADCAP) 363 var same: i64 = 1 364 if nb != na { same = 0 } else { 365 var i: i64 = 0 366 while i < na { if bufA[i] != bufB[i] { same = 0; i = na } else { i = i + 1 } } 367 } 368 if same == 0 { 369 sc_write(mfp, mfb, mfn) 370 sc_werr("REFUSED: post-compaction load DIFFERS -- manifest RESTORED, plane unchanged. before_bytes=" as *u8) 371 let m: *u8 = sys_mmap(SC_MSGCAP) 372 var mo: i64 = ss_catn(m, 0, na) 373 mo = ss_cat(m, mo, " after_bytes=" as *u8) 374 mo = ss_catn(m, mo, nb) 375 mo = ss_cat(m, mo, "\n" as *u8) 376 sys_write(SC_STDERR, m, mo) 377 sys_exit(SC_EXIT_REFUSED) 378 return SC_EXIT_REFUSED 379 } 380 sc_puts("COMPACTED prefix=" as *u8) 381 sc_puts(prefix) 382 sc_puts(" segments=" as *u8) 383 sc_putn(segs) 384 sc_puts(" -> 1 (seg-" as *u8) 385 sc_putn(segmax[0]) 386 sc_puts(") verified_bytes=" as *u8) 387 sc_putn(na) 388 sc_puts(" BYTE-IDENTICAL; superseded segment FILES left on disk untouched (rule 13); rollback = restore " as *u8) 389 sc_puts(bkp) 390 sc_puts("\n" as *u8) 391 sys_exit(0) 392 return 0 393 } 394 395 sc_werr("usage: nx_store_compact <prefix> check|apply|fold\n" as *u8) 396 sys_exit(SC_EXIT_USAGE) 397 return SC_EXIT_USAGE 398}