#!/usr/bin/env node /** * find() composition latency benchmark at scale (Brainy 8.0). * * Measures Triple-Intelligence query latency — vector similarity, metadata * filtering, graph traversal, and the full composition of all three — against a * populated in-memory index of N entities. Uses precomputed random vectors so * the embedding model is never loaded (eagerEmbeddings: false) and the numbers * reflect index + query cost only, not embedding throughput. * * Usage: node --max-old-space-size=8192 tests/benchmarks/find-composition-scale.js [N] * N defaults to 1_000_000. Pass a smaller N (e.g. 100000) for a quick check. * * Reports build throughput, per-query p50/p95/p99/mean, and memory footprint. * Every query type asserts a non-empty result so an empty (and therefore * misleadingly fast) query path fails loudly instead of reporting a false win. */ import { Brainy } from '../../dist/index.js' import { NounType, VerbType } from '../../dist/types/graphTypes.js' const N = Number(process.argv[2] ?? 1_000_000) const DIM = 384 const CATEGORIES = 10 const HUBS = 1000 // entities that get an out-edge neighbourhood const FANOUT = 100 // out-edges per hub -> HUBS*FANOUT total edges const QUERIES = 200 // measured iterations per query type // Deterministic-ish PRNG so runs are comparable (no Date.now/crypto needed). let _seed = 0x2545f491 function rnd() { _seed ^= _seed << 13; _seed ^= _seed >>> 17; _seed ^= _seed << 5 return ((_seed >>> 0) % 1_000_000) / 1_000_000 } function randomVector() { const v = new Array(DIM) for (let i = 0; i < DIM; i++) v[i] = rnd() return v } function pct(sorted, p) { const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length)) return sorted[idx] } function timeQueries(label, fn, expectNonEmpty = true) { const lat = [] let emptyCount = 0 return (async () => { for (let i = 0; i < QUERIES; i++) { const t = process.hrtime.bigint() const res = await fn(i) const ms = Number(process.hrtime.bigint() - t) / 1e6 lat.push(ms) if (!res || res.length === 0) emptyCount++ } lat.sort((a, b) => a - b) const mean = lat.reduce((s, x) => s + x, 0) / lat.length const flag = expectNonEmpty && emptyCount === QUERIES ? ' ⚠️ ALL EMPTY' : '' console.log( `${label.padEnd(34)} p50 ${pct(lat, 50).toFixed(2).padStart(8)}ms ` + `p95 ${pct(lat, 95).toFixed(2).padStart(8)}ms ` + `p99 ${pct(lat, 99).toFixed(2).padStart(8)}ms ` + `mean ${mean.toFixed(2).padStart(8)}ms (empty ${emptyCount}/${QUERIES})${flag}` ) return { label, p50: pct(lat, 50), p95: pct(lat, 95), p99: pct(lat, 99), mean, emptyCount } })() } async function main() { console.log('='.repeat(96)) console.log(`Brainy 8.0 — find() composition benchmark @ N=${N.toLocaleString()} (dim ${DIM}, memory storage)`) console.log('='.repeat(96)) const brain = new Brainy({ storage: { type: 'memory' }, eagerEmbeddings: false, // never load the WASM model — we pass vectors requireSubtype: false, // keep the harness focused on query cost silent: true }) await brain.init() console.log(`recall preset: ${brain.config?.vector?.recall ?? 'balanced (default)'}`) // ---- Build phase ---------------------------------------------------------- const ids = new Array(N) const vecs = new Array(N) // kept so the triple query can target a real connected entity const CHUNK = 5000 let buildStart = Date.now() let batch = [] let written = 0 for (let i = 0; i < N; i++) { const v = randomVector() vecs[i] = v batch.push({ vector: v, type: NounType.Document, metadata: { idx: i, category: i % CATEGORIES, score: Math.floor(rnd() * 1000), active: (i & 1) === 0 } }) if (batch.length === CHUNK) { const r = await brain.addMany({ items: batch, parallel: true, chunkSize: 500 }) for (let k = 0; k < r.successful.length; k++) ids[written++] = r.successful[k] batch = [] if (written % 10000 === 0) { const rate = Math.round(written / ((Date.now() - buildStart) / 1000)) console.log(` built ${written.toLocaleString()} / ${N.toLocaleString()} (${rate.toLocaleString()}/s)`) } } } if (batch.length) { const r = await brain.addMany({ items: batch, parallel: true, chunkSize: 500 }) for (let k = 0; k < r.successful.length; k++) ids[written++] = r.successful[k] } const buildMs = Date.now() - buildStart console.log(`\nbuild: ${written.toLocaleString()} entities in ${(buildMs / 1000).toFixed(1)}s ` + `(${Math.round(written / (buildMs / 1000)).toLocaleString()} entities/s)`) // ---- Edges (for graph composition) --------------------------------------- const edgeStart = Date.now() let edges = 0 for (let h = 0; h < HUBS; h++) { const from = ids[h] for (let f = 0; f < FANOUT; f++) { const to = ids[(h * FANOUT + f + HUBS) % N] await brain.relate({ from, to, type: VerbType.References, weight: 0.8 }) edges++ } } const edgeMs = Date.now() - edgeStart console.log(`edges: ${edges.toLocaleString()} in ${(edgeMs / 1000).toFixed(1)}s ` + `(${Math.round(edges / (edgeMs / 1000)).toLocaleString()} edges/s)`) // ---- Warmup --------------------------------------------------------------- for (let i = 0; i < 20; i++) await brain.find({ vector: randomVector(), limit: 10 }) // ---- Query phase ---------------------------------------------------------- console.log('-'.repeat(96)) const out = [] out.push(await timeQueries('vector only (k=10)', () => brain.find({ vector: randomVector(), limit: 10 }))) out.push(await timeQueries('metadata only (category=)', (i) => brain.find({ where: { category: i % CATEGORIES }, limit: 10 }))) out.push(await timeQueries('vector + metadata', (i) => brain.find({ vector: randomVector(), where: { category: i % CATEGORIES }, limit: 10 }))) out.push(await timeQueries('graph only (1-hop out)', (i) => brain.find({ connected: { from: ids[i % HUBS], via: VerbType.References, depth: 1, direction: 'out' }, limit: 10 }))) // Triple composition: query vector targets a genuine out-neighbour of the hub, // so the vector-NN ∩ graph-connected ∩ metadata sets actually overlap (a random // query vector would never land in a hub's arbitrary neighbourhood → empty). out.push(await timeQueries('TRIPLE: vector+metadata+graph', (i) => { const hub = i % HUBS const neighborIdx = (hub * FANOUT + HUBS) % N // hub's first out-neighbour return brain.find({ vector: vecs[neighborIdx], where: { category: neighborIdx % CATEGORIES }, connected: { from: ids[hub], via: VerbType.References, depth: 1, direction: 'out' }, limit: 10 }) })) // ---- Memory --------------------------------------------------------------- const mem = process.memoryUsage() console.log('-'.repeat(96)) console.log(`memory: RSS ${(mem.rss / 1e9).toFixed(2)} GB heap ${(mem.heapUsed / 1e9).toFixed(2)} GB ` + `(${Math.round(mem.rss / written).toLocaleString()} bytes/entity RSS)`) console.log('='.repeat(96)) await brain.close() // Machine-readable summary line for downstream capture. console.log('JSON ' + JSON.stringify({ n: written, dim: DIM, edges, buildEntitiesPerSec: Math.round(written / (buildMs / 1000)), rssGB: +(mem.rss / 1e9).toFixed(2), bytesPerEntityRss: Math.round(mem.rss / written), queries: out })) } main().catch((e) => { console.error(e); process.exit(1) })