code wiki / _hdl_build / nx_store_compact.nx
nx_store_compact.nx source
↩ module page · 380 lines · 18445 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 var ls: i64 = 0
129 var li: i64 = 0
130 while li <= mfn {
131 var eol: i64 = 0
132 if li == mfn { eol = 1 } else { if mfb[li] == (SC_NL as u8) { eol = 1 } }
133 if eol == 1 {
134 if li > ls {
135 let path: *u8 = sys_mmap(SC_PATHCAP)
136 var o: i64 = ss_cat(path, 0, prefix)
137 var c: i64 = 0
138 while c < li - ls { path[o] = mfb[ls + c]; o = o + 1; c = c + 1 }
139 o = ss_cat(path, o, ".docs" as *u8)
140 path[o] = 0 as u8
141 let szp: *i64 = sys_mmap(16) as *i64
142 let b: *u8 = ss_readall(path, szp)
143 if (b as i64) != 0 {
144 let sz: i64 = szp[0]
145 var i: i64 = 0
146 while i + 9 <= sz {
147 let kind: i64 = b[i] as i64
148 let kl: i64 = ss_r32(b, i + 1)
149 let koff: i64 = i + 5
150 let vl: i64 = ss_r32(b, koff + kl)
151 let voff: i64 = koff + kl + 4
152 if kl >= SC_KEYCAP { return 0 - 3 }
153 var hit: i64 = 0 - 1
154 var t: i64 = 0
155 while t < nk {
156 if hit < 0 { if ss_kcmp(tkp[t] as *u8, tkl[t], (b as i64 + koff) as *u8, kl) == 0 { hit = t } }
157 t = t + 1
158 }
159 if hit < 0 {
160 if nk >= maxk { return 0 - 2 }
161 hit = nk
162 nk = nk + 1
163 }
164 tkp[hit] = (b as i64) + koff
165 tkl[hit] = kl
166 tkind[hit] = kind
167 tvp[hit] = (b as i64) + voff
168 tvl[hit] = vl
169 i = voff + vl
170 }
171 }
172 }
173 ls = li + 1
174 }
175 li = li + 1
176 }
177 return nk
178}
179
180func main(argc: i64, argv: *i64) -> i64 {
181 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 }
182 let prefix: *u8 = argv[1] as *u8
183 let verb: *u8 = argv[2] as *u8
184
185 let mfp: *u8 = sys_mmap(SC_PATHCAP)
186 sc_path(prefix, "manifest.txt" as *u8, mfp)
187 let mfb: *u8 = sys_mmap(SC_MFCAP)
188 let mfn: i64 = sc_read(mfp, mfb, SC_MFCAP - 1)
189 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 }
190 let segmax: *i64 = sys_mmap(16) as *i64
191 let segs: i64 = sc_scan_manifest(mfb, mfn, segmax)
192 if segs <= 0 { sc_werr("MANIFEST has no seg- rows (fail-closed)\n" as *u8); sys_exit(SC_EXIT_REFUSED); return SC_EXIT_REFUSED }
193
194 if sc_eqs("check" as *u8, verb) == 1 {
195 sc_puts("COMPACT-CHECK prefix=" as *u8)
196 sc_puts(prefix)
197 sc_puts(" segments=" as *u8)
198 sc_putn(segs)
199 sc_puts(" newest_segid=" as *u8)
200 sc_putn(segmax[0])
201 sc_puts(" scan_cost_multiplier=" as *u8)
202 sc_putn(segs)
203 sc_puts("x (a load walks every segment per key)\n" as *u8)
204 sys_exit(0)
205 return 0
206 }
207
208 if sc_eqs("fold" as *u8, verb) == 1 {
209 // ---- GENERIC MERGE COMPACTION: ss_compact_cap folds ALL segments into one (latest entry per
210 // key, tombstones kept, retired names archived, atomic manifest swap; every failure path is
211 // BEFORE the swap). Works for reg planes, whose newest segment is NOT a complete snapshot --
212 // the sts-only "apply" manifest re-point would LOSE data there. Wrapped in proof: snapshot the
213 // logical plane FIRST (one O(data) pass), then re-read every key AFTER through the production
214 // ss_get path; any mismatch -> manifest RESTORED from backup, REFUSE.
215 if segs == 1 { sc_puts("NO-OP already single-segment\n" as *u8); sys_exit(0); return 0 }
216 let lkp2: *u8 = sys_mmap(SC_PATHCAP)
217 sc_path(prefix, "plock" as *u8, lkp2)
218 let lfd2: i64 = sys_openat_append(lkp2, SC_MODE)
219 if lfd2 < 0 { sc_werr("REFUSED: cannot open plane lock (plock)\n" as *u8); sys_exit(SC_EXIT_REFUSED); return SC_EXIT_REFUSED }
220 sys_flock(lfd2, SC_LOCK_EX)
221 // BEFORE: logical snapshot, key-table sized data-driven (every entry >= 9 bytes)
222 var tot: i64 = 0
223 var ls2: i64 = 0
224 var li2: i64 = 0
225 while li2 <= mfn {
226 var eol2: i64 = 0
227 if li2 == mfn { eol2 = 1 } else { if mfb[li2] == (SC_NL as u8) { eol2 = 1 } }
228 if eol2 == 1 { if li2 > ls2 { tot = tot + 1 } ls2 = li2 + 1 }
229 li2 = li2 + 1
230 }
231 let maxk: i64 = SC_LOADCAP / 9 + 16
232 let tkp: *i64 = sys_mmap(8 * maxk) as *i64
233 let tkl: *i64 = sys_mmap(8 * maxk) as *i64
234 let tkind: *i64 = sys_mmap(8 * maxk) as *i64
235 let tvp: *i64 = sys_mmap(8 * maxk) as *i64
236 let tvl: *i64 = sys_mmap(8 * maxk) as *i64
237 let nk: i64 = sc_snapshot(prefix, mfb, mfn, tkp, tkl, tkind, tvp, tvl, maxk)
238 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 }
239 // back the manifest up FIRST: rollback must be one file copy
240 let bkp2: *u8 = sys_mmap(SC_PATHCAP)
241 sc_path(prefix, "manifest.txt.bak-precompact" as *u8, bkp2)
242 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 }
243 let segid: i64 = ss_next_segid(prefix)
244 let rc: i64 = ss_compact_cap(prefix, segid, segs + 64)
245 if rc < 0 {
246 sc_werr("REFUSED: ss_compact_cap failed rc=" as *u8)
247 let m0: *u8 = sys_mmap(SC_MSGCAP)
248 var mo0: i64 = ss_catn(m0, 0, rc)
249 mo0 = ss_cat(m0, mo0, " (live manifest untouched -- all failure paths precede the swap)\n" as *u8)
250 sys_write(SC_STDERR, m0, mo0)
251 sys_exit(SC_EXIT_REFUSED)
252 return SC_EXIT_REFUSED
253 }
254 // AFTER: every snapshotted key re-read through the PRODUCTION ss_get path must agree
255 var same: i64 = 1
256 var badk: i64 = 0 - 1
257 let kbuf: *u8 = sys_mmap(SC_KEYCAP)
258 let po: *i64 = sys_mmap(16) as *i64
259 let lo: *i64 = sys_mmap(16) as *i64
260 var vk: i64 = 0
261 var puts_n: i64 = 0
262 var tombs_n: i64 = 0
263 while vk < nk {
264 let kpn: *u8 = tkp[vk] as *u8
265 var kc: i64 = 0
266 while kc < tkl[vk] { kbuf[kc] = kpn[kc]; kc = kc + 1 }
267 kbuf[tkl[vk]] = 0 as u8
268 let st: i64 = ss_get(prefix, kbuf, po, lo)
269 if tkind[vk] == 1 {
270 puts_n = puts_n + 1
271 if st != 1 { same = 0; badk = vk } else {
272 if lo[0] != tvl[vk] { same = 0; badk = vk } else {
273 let va: *u8 = tvp[vk] as *u8
274 let vb: *u8 = po[0] as *u8
275 var q: i64 = 0
276 while q < tvl[vk] { if va[q] != vb[q] { same = 0; badk = vk; q = tvl[vk] } else { q = q + 1 } }
277 }
278 }
279 } else {
280 tombs_n = tombs_n + 1
281 if st != 0 { same = 0; badk = vk }
282 }
283 if same == 0 { vk = nk } else { vk = vk + 1 }
284 }
285 if same == 0 {
286 sc_write(mfp, mfb, mfn)
287 sc_werr("REFUSED: post-fold ss_get view DIFFERS at key #" as *u8)
288 let m1: *u8 = sys_mmap(SC_MSGCAP)
289 var mo1: i64 = ss_catn(m1, 0, badk)
290 mo1 = ss_cat(m1, mo1, " -- manifest RESTORED, plane unchanged\n" as *u8)
291 sys_write(SC_STDERR, m1, mo1)
292 sys_exit(SC_EXIT_REFUSED)
293 return SC_EXIT_REFUSED
294 }
295 sc_puts("FOLDED prefix=" as *u8)
296 sc_puts(prefix)
297 sc_puts(" segments=" as *u8)
298 sc_putn(segs)
299 sc_puts(" -> 1 (seg-" as *u8)
300 sc_putn(segid)
301 sc_puts(") keys=" as *u8)
302 sc_putn(puts_n)
303 sc_puts(" tombstones=" as *u8)
304 sc_putn(tombs_n)
305 sc_puts(" verified via production ss_get BYTE-IDENTICAL; retired segment FILES left on disk + archived (rule 13); rollback = restore " as *u8)
306 sc_puts(bkp2)
307 sc_puts("\n" as *u8)
308 sys_exit(0)
309 return 0
310 }
311
312 if sc_eqs("apply" as *u8, verb) == 1 {
313 if segs == 1 { sc_puts("NO-OP already single-segment\n" as *u8); sys_exit(0); return 0 }
314 // serialize against plane writers (nx_debt/nx_store_put use <prefix>plock)
315 let lkp: *u8 = sys_mmap(SC_PATHCAP)
316 sc_path(prefix, "plock" as *u8, lkp)
317 let lfd: i64 = sys_openat_append(lkp, SC_MODE)
318 // FAIL-CLOSED LOCK (2026-07-29 seq1254): proceeding UNLOCKED on a failed plock open lets a
319 // compact race live writers -- a lost manifest RMW then drops their committed lines silently.
320 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 }
321 sys_flock(lfd, SC_LOCK_EX)
322
323 // BEFORE: the authoritative logical plane
324 let bufA: *u8 = sys_mmap(SC_LOADCAP)
325 let na: i64 = sts_load(prefix, bufA, SC_LOADCAP)
326 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 }
327 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 }
328
329 // back the manifest up FIRST: rollback must be one file copy
330 let bkp: *u8 = sys_mmap(SC_PATHCAP)
331 sc_path(prefix, "manifest.txt.bak-precompact" as *u8, bkp)
332 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 }
333
334 // the ONLY mutation: a manifest naming just the newest segment
335 let nmb: *u8 = sys_mmap(SC_PATHCAP)
336 var o: i64 = ss_cat(nmb, 0, "seg-" as *u8)
337 o = ss_catn(nmb, o, segmax[0])
338 nmb[o] = SC_NL as u8
339 o = o + 1
340 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 }
341
342 // AFTER: must be byte-identical or we put it all back
343 let bufB: *u8 = sys_mmap(SC_LOADCAP)
344 let nb: i64 = sts_load(prefix, bufB, SC_LOADCAP)
345 var same: i64 = 1
346 if nb != na { same = 0 } else {
347 var i: i64 = 0
348 while i < na { if bufA[i] != bufB[i] { same = 0; i = na } else { i = i + 1 } }
349 }
350 if same == 0 {
351 sc_write(mfp, mfb, mfn)
352 sc_werr("REFUSED: post-compaction load DIFFERS -- manifest RESTORED, plane unchanged. before_bytes=" as *u8)
353 let m: *u8 = sys_mmap(SC_MSGCAP)
354 var mo: i64 = ss_catn(m, 0, na)
355 mo = ss_cat(m, mo, " after_bytes=" as *u8)
356 mo = ss_catn(m, mo, nb)
357 mo = ss_cat(m, mo, "\n" as *u8)
358 sys_write(SC_STDERR, m, mo)
359 sys_exit(SC_EXIT_REFUSED)
360 return SC_EXIT_REFUSED
361 }
362 sc_puts("COMPACTED prefix=" as *u8)
363 sc_puts(prefix)
364 sc_puts(" segments=" as *u8)
365 sc_putn(segs)
366 sc_puts(" -> 1 (seg-" as *u8)
367 sc_putn(segmax[0])
368 sc_puts(") verified_bytes=" as *u8)
369 sc_putn(na)
370 sc_puts(" BYTE-IDENTICAL; superseded segment FILES left on disk untouched (rule 13); rollback = restore " as *u8)
371 sc_puts(bkp)
372 sc_puts("\n" as *u8)
373 sys_exit(0)
374 return 0
375 }
376
377 sc_werr("usage: nx_store_compact <prefix> check|apply|fold\n" as *u8)
378 sys_exit(SC_EXIT_USAGE)
379 return SC_EXIT_USAGE
380}