fix(search): hybrid max via reduce + LRU doc-cache (#227) - #229
fix(search): hybrid max via reduce + LRU doc-cache (#227)#229ranxianglei wants to merge 1 commit into
Conversation
Item 1 (P1): hybridAlgorithm.score normalized channel scores with Math.max(...spread), which throws RangeError once docs exceed V8's argument limit (~65K-125K). Reproduced: 160K docs crashed the search tool before returning any result. Now computed with reduce (O(n), no arg limit). Item 2: doc-features cache evicted in insertion order (FIFO), so a corpus larger than the 8MB cap re-tokenized (corpus - cap) on every call (measured: 12MB corpus, warm/cold = 104%). Eviction is now LRU (re-insert on hit), so re-queried docs are retained. setDocCacheCap(>=corpus)/Infinity is documented + tested as the opt-in for a single-session host to cache the whole corpus and make later calls O(docs x query-terms). Why LRU over per-scope (key by doc ref): LRU is self-contained with no interface change; per-scope would require threading a scope through the stateless SearchAlgorithm.score(docs, query) contract (breaking for custom algorithms, or a fragile non-async-safe module global) for a multi-session use case that does not exist yet. The single-session host already gets full-corpus caching via the cap knob. Tests: hybrid no-crash at 160K docs; LRU keeps a re-accessed doc that FIFO would evict; Infinity cap caches the whole corpus. Full suite: 583 pass.
📦 Built Package ArtifactBranch: Option A — Install from npm PR tag (recommended)In your adapter project: npm install acp-kernel@pr-229Each push to this PR publishes a new version under the Option B — Download artifact
tar xzf acp-kernel-pr229.tgz
npm install ./packageThis comment is automatically updated on each push. |
Reviewed PR #229 against the live tree — reproduced both bugs pre-change, then re-ran the full suite on the PR branch. Verdict: mergeable. Both fixes are correct; 583/583 tests pass, typecheck clean, build green. Two notes below: a framing clarification on P2, and a minor finding the "only hazard" claim misses. P1 — hybrid
P2 — LRU + cap knob: correct, but one framing point.
Minor — the "only argument-spread hazard" claim is inaccurate.
Merge cleanliness: PR is based on Happy to open a follow-up issue for the tokenizer |
Fixes #227.
Two independent kernel-layer costs in the
searchBlockspath. Both were reproduced locally on the current tree before changing anything.Item 1 (P1):
hybridAlgorithm.scorehard-crashes at large doc countssrc/search/algorithms/hybrid.tsnormalized the two channel scores with an argument spread:The spread throws
RangeError: Maximum call stack size exceededonce the doc count exceeds V8's argument limit (~65K–125K depending on build). Reproduced: 160,170 docs → the search tool throws instead of degrading, before returning any result.Fix: compute the max with
reduce(O(n), no argument limit, no extra allocation):This was the only argument-spread hazard in the codebase (audited every
Math.max/min(...spread)and array spread).Item 2:
docFeatures8MB cap re-tokenizes large corpora on every callThe doc-features cache evicted in insertion order (FIFO), keyed by full doc text. A corpus larger than the 8MB cap re-tokenized
(corpus − cap)on every call. Reproduced: 12.0MB corpus / 27,741 docs / 8MB cap → cold 7242ms, warm 7524ms (warm/cold = 104%); cache pinned at 8.0MB.Fix (design call — chose LRU over per-scope):
setDocCacheCap(≥corpus)/Infinityis now documented + tested as the opt-in for a single-session host to cache the whole corpus once and make later callsO(docs × query-terms)— this is what the host needs to stop re-tokenizing, without a per-scope cache redesign.Why not per-scope (key by doc
ref): it would require threading a scope through the stateless publicSearchAlgorithm.score(docs, query)contract — either a breaking change for custom algorithms, or a fragile non-async-safe module-level "active scope". That's a lot of surface for a multi-session-server use case that doesn't exist yet. The single-session host already gets full-corpus caching via the cap knob.Tests
hybrid: no RangeError at large doc counts— 160K docs (the crash scale) returns results instead of throwing.LRU: re-accessing a doc keeps it alive (FIFO would evict it)— pins the LRU-vs-FIFO difference via object identity.setDocCacheCap(Infinity): whole corpus cached, no eviction— pins the full-corpus opt-in.Full suite: 583 pass, 0 fail. Typecheck clean, build succeeds.