code wiki / _hdl_build / nx_segbloom.nx
nx_segbloom.nx
buildroot/runtime/_hdl_build/nx_segbloom.nx
about
nx_segbloom.nx -- EVIDENCE FIRST: does a per-segment TERM BLOOM actually skip enough segments to
be worth wiring into the hot search path? MEASURES ONLY. It writes nothing, changes no live code
and touches no existing call site -- because the honest order is prove-the-win, then wire it.
THE DEFECT IT TARGETS (measured 2026-08-06): /api/search costs a FIXED ~6.6s even for a query that
matches ZERO documents, against a 42ms control on the same daemon. ss_term (nx_seg_store:2441)
walks EVERY live segment and calls ss_terms_find on that segment's mmap'd terms block:
while s < ns { let tb = h[5+8*s]; ss_terms_find(tb, h[6+8*s], term, outs) ... }
The blocks are already mapped, so the cost is ONE PAGE FAULT PER SEGMENT -- ~896 segments x ~8ms.
A resident bloom per segment answers "definitely not here" without touching the mapping at all,
so the fault never happens. Segments are IMMUTABLE in an append-only store, which is what makes
this sound: a per-segment term bloom is computed once and can never go stale.
REUSE, NOT REINVENTION: the bloom is nx_sketch_bloom (v2, murmur3 + Kirsch-Mitzenmacher double
hashing, already used in production by nx_ingest_runner) and the bits/item + k constants are the
pre-computed FPR breakpoints from nx_bloom_capacity. Nothing here is a new filter.
nx_segbloom stat <domain> [maxsegs]
nx_segbloom probe <domain> <profile 1=10pct|2=1pct> <term> [maxsegs]
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 3 imports · 0 importers
imports: nx_seg_store.nxnx_sketch_bloom.nxnx_syscalls.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 25 | const SB_PATHCAP: i64 = 512 |
| 26 | const SB_SMALL: i64 = 64 |
| 27 | const SB_MAXSEGS: i64 = 8192 |
| 29 | const SB_BITS_10PCT: i64 = 5 |
| 30 | const SB_K_10PCT: i64 = 3 |
| 31 | const SB_BITS_1PCT: i64 = 10 |
| 32 | const SB_K_1PCT: i64 = 7 |
functions
| 34 | func sb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 35 | func sb_w(s: *u8, n: i64) -> i64 { sys_write(1, s, n); return 0 } |
| 36 | func sb_puts(s: *u8) -> i64 { sb_w(s, sb_slen(s)); return 0 } |
| 37 | func sb_num(v: i64) -> i64 |
| 50 | func sb_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o+i] = s[i]; i = i + 1 } return o + i } called by 1: sb_prefix |
| 53 | func sb_prefix(domain: *u8, out: *u8) -> i64 |
| 61 | func sb_next_pow2(n: i64) -> i64 called by 1: cmd_probe |
| 66 | func sb_open(domain: *u8) -> *i64 |
| 74 | func cmd_stat(domain: *u8, maxsegs: i64) -> i64 |
| 103 | func cmd_probe(domain: *u8, profile: i64, term: *u8, maxsegs: i64) -> i64 |
| 190 | func sb_atoi(s: *u8) -> i64 called by 1: main |
| 202 | func main(argc: i64, argv: *i64) -> i64 |