2025-09-11 16:23:32 -07:00
#!/usr/bin/env node
/ * *
* Brainy 3.0 Performance Benchmark
2025-09-30 17:09:15 -07:00
* Compare v2 ( Brainy ) vs v3 ( Brainy ) performance
2025-09-11 16:23:32 -07:00
* /
2025-09-30 17:09:15 -07:00
import { Brainy } from '../../dist/index.js'
import { NounType , VerbType } from '../../dist/types/graphTypes.js'
2025-09-11 16:23:32 -07:00
const ITERATIONS = 1000
const BATCH _SIZE = 100
async function benchmarkV2 ( ) {
2025-09-30 17:09:15 -07:00
console . log ( '\n📊 Brainy v2 Performance' )
2025-09-11 16:23:32 -07:00
console . log ( '═' . repeat ( 50 ) )
2025-09-30 17:09:15 -07:00
const brain = new Brainy ( {
2025-09-11 16:23:32 -07:00
storage : { type : 'memory' } ,
embeddingFunction : async ( ) => new Array ( 384 ) . fill ( 0 ) . map ( ( ) => Math . random ( ) )
} )
await brain . init ( )
// Test 1: Add operations
const start1 = performance . now ( )
const ids = [ ]
for ( let i = 0 ; i < ITERATIONS ; i ++ ) {
const id = await brain . addNoun (
new Array ( 384 ) . fill ( 0 ) . map ( ( ) => Math . random ( ) ) ,
'document' ,
{ index : i , title : ` Doc ${ i } ` }
)
ids . push ( id )
}
const addTime = performance . now ( ) - start1
console . log ( ` ✅ Add ${ ITERATIONS } items: ${ addTime . toFixed ( 2 ) } ms ( ${ ( ITERATIONS / ( addTime / 1000 ) ) . toFixed ( 0 ) } ops/sec) ` )
// Test 2: Get operations
const start2 = performance . now ( )
for ( let i = 0 ; i < Math . min ( 100 , ids . length ) ; i ++ ) {
await brain . getNoun ( ids [ i ] )
}
const getTime = performance . now ( ) - start2
console . log ( ` ✅ Get 100 items: ${ getTime . toFixed ( 2 ) } ms ( ${ ( 100 / ( getTime / 1000 ) ) . toFixed ( 0 ) } ops/sec) ` )
// Test 3: Search operations
const start3 = performance . now ( )
feat(8.0): API simplification — remove neural()/Db.search, one storage `path` key, integration→0
8.0 RC cleanup toward "one place per thing, zero-config, no deprecation":
- Remove the `brain.neural()` clustering namespace (ImprovedNeuralAPI + the dead
legacy NeuralAPI + the neural CLI + neural-only types). Similarity is `find({vector})`
/ `similar({to})`; attribute grouping is the aggregation `GROUP BY` engine. The separate
entity-extraction / smart-import feature (NeuralImport, NeuralEntityExtractor, SmartExtractor,
NaturalLanguageProcessor, `brain.extract()`/`brain.nlp()`) is kept.
- Remove `Db.search()`; `find()` is the one query verb (accepts a bare string or FindParams).
Fix the bundled MCP client, which called a non-existent `brain.search(query, limit)` →
now `find({ query, limit })`.
- Storage config: collapse to one canonical top-level `path` key. The pre-8.0 aliases
(`rootDirectory`, `options.*`, `fileSystemStorage.*`) are removed and now THROW with the
exact rename instead of silently defaulting to `./brainy-data` on upgrade. A single resolver
feeds createStorage, the 7.x→8.0 migration probe, and the plugin-factory handoff, so a native
storage provider resolves the identical root (no split-brain).
- Fix `similar({ threshold })`: the min-similarity filter was silently dropped; it is now
applied as a post-filter on `result.score` (the documented way to bound semantic results).
- Fix `vfs.rename()` on a directory: child path updates spread the entity vector into `update()`
and failed dimension validation; they are metadata-only updates now.
- Fix `vfs.move()`: copy+delete orphaned the content-addressed content blob (the destination
shared the source hash, then unlink removed it). `move()` now delegates to `rename()` — an
in-place path change that preserves the blob and the entity id, for files and directories.
- Fix streaming import: the bulk fast path never flushed mid-import nor signalled queryability.
Entity writes are now chunked by a progressive flush interval (100 → 1000 → 5000); each chunk
flushes and emits `progress.queryable`, so imported data is queryable during the import.
- Sweep all docs, comments, and JSDoc for the removed/changed APIs.
Integration suite: 49 files / 588 passed / 0 failed. Unit: 80 files / 1456 passed, no type errors.
2026-06-20 13:31:11 -07:00
await brain . find ( {
2025-09-11 16:23:32 -07:00
query : new Array ( 384 ) . fill ( 0 ) . map ( ( ) => Math . random ( ) ) ,
limit : 10
} )
const searchTime = performance . now ( ) - start3
console . log ( ` ✅ Vector search: ${ searchTime . toFixed ( 2 ) } ms ` )
// Test 4: Metadata filter
const start4 = performance . now ( )
await brain . find ( {
where : { index : { greaterThan : 500 } } ,
limit : 10
} )
const filterTime = performance . now ( ) - start4
console . log ( ` ✅ Metadata filter: ${ filterTime . toFixed ( 2 ) } ms ` )
// Test 5: Relationship operations
const start5 = performance . now ( )
for ( let i = 0 ; i < 50 ; i ++ ) {
await brain . addVerb (
ids [ i ] ,
'references' ,
ids [ i + 1 ] ,
0.8
)
}
const relateTime = performance . now ( ) - start5
console . log ( ` ✅ Create 50 relationships: ${ relateTime . toFixed ( 2 ) } ms ( ${ ( 50 / ( relateTime / 1000 ) ) . toFixed ( 0 ) } ops/sec) ` )
await brain . close ( )
return {
add : addTime ,
get : getTime ,
search : searchTime ,
filter : filterTime ,
relate : relateTime
}
}
async function benchmarkV3 ( ) {
console . log ( '\n🚀 Brainy v3 Performance' )
console . log ( '═' . repeat ( 50 ) )
const brain = new Brainy ( {
storage : { type : 'memory' } ,
warmup : false ,
embedder : async ( ) => new Array ( 384 ) . fill ( 0 ) . map ( ( ) => Math . random ( ) )
} )
await brain . init ( )
// Test 1: Add operations
const start1 = performance . now ( )
const ids = [ ]
for ( let i = 0 ; i < ITERATIONS ; i ++ ) {
const id = await brain . add ( {
vector : new Array ( 384 ) . fill ( 0 ) . map ( ( ) => Math . random ( ) ) ,
type : NounType . Document ,
metadata : { index : i , title : ` Doc ${ i } ` }
} )
ids . push ( id )
}
const addTime = performance . now ( ) - start1
console . log ( ` ✅ Add ${ ITERATIONS } items: ${ addTime . toFixed ( 2 ) } ms ( ${ ( ITERATIONS / ( addTime / 1000 ) ) . toFixed ( 0 ) } ops/sec) ` )
// Test 2: Get operations
const start2 = performance . now ( )
for ( let i = 0 ; i < Math . min ( 100 , ids . length ) ; i ++ ) {
await brain . get ( ids [ i ] )
}
const getTime = performance . now ( ) - start2
console . log ( ` ✅ Get 100 items: ${ getTime . toFixed ( 2 ) } ms ( ${ ( 100 / ( getTime / 1000 ) ) . toFixed ( 0 ) } ops/sec) ` )
// Test 3: Search operations
const start3 = performance . now ( )
await brain . find ( {
vector : new Array ( 384 ) . fill ( 0 ) . map ( ( ) => Math . random ( ) ) ,
limit : 10
} )
const searchTime = performance . now ( ) - start3
console . log ( ` ✅ Vector search: ${ searchTime . toFixed ( 2 ) } ms ` )
// Test 4: Metadata filter
const start4 = performance . now ( )
await brain . find ( {
where : { 'metadata.index' : { $gt : 500 } } ,
limit : 10
} )
const filterTime = performance . now ( ) - start4
console . log ( ` ✅ Metadata filter: ${ filterTime . toFixed ( 2 ) } ms ` )
// Test 5: Relationship operations
const start5 = performance . now ( )
for ( let i = 0 ; i < 50 ; i ++ ) {
await brain . relate ( {
source : ids [ i ] ,
verb : VerbType . References ,
target : ids [ i + 1 ] ,
weight : 0.8
} )
}
const relateTime = performance . now ( ) - start5
console . log ( ` ✅ Create 50 relationships: ${ relateTime . toFixed ( 2 ) } ms ( ${ ( 50 / ( relateTime / 1000 ) ) . toFixed ( 0 ) } ops/sec) ` )
// Test 6: Batch operations (v3 exclusive)
const start6 = performance . now ( )
const batchData = Array ( BATCH _SIZE ) . fill ( 0 ) . map ( ( _ , i ) => ( {
vector : new Array ( 384 ) . fill ( 0 ) . map ( ( ) => Math . random ( ) ) ,
type : NounType . Document ,
metadata : { batch : true , index : i }
} ) )
const batchResult = await brain . addMany ( { items : batchData } )
const batchTime = performance . now ( ) - start6
console . log ( ` ✅ Batch add ${ BATCH _SIZE } items: ${ batchTime . toFixed ( 2 ) } ms ( ${ ( BATCH _SIZE / ( batchTime / 1000 ) ) . toFixed ( 0 ) } ops/sec) ` )
console . log ( ` Success: ${ batchResult . successful . length } , Failed: ${ batchResult . failed . length } ` )
await brain . close ( )
return {
add : addTime ,
get : getTime ,
search : searchTime ,
filter : filterTime ,
relate : relateTime ,
batch : batchTime
}
}
async function compare ( ) {
console . log ( '\n🧠 Brainy Performance Comparison' )
console . log ( '═' . repeat ( 50 ) )
console . log ( ` Test iterations: ${ ITERATIONS } ` )
console . log ( ` Batch size: ${ BATCH _SIZE } ` )
const v2Times = await benchmarkV2 ( )
const v3Times = await benchmarkV3 ( )
console . log ( '\n📈 Performance Comparison' )
console . log ( '═' . repeat ( 50 ) )
const operations = [ 'add' , 'get' , 'search' , 'filter' , 'relate' ]
for ( const op of operations ) {
const v2 = v2Times [ op ]
const v3 = v3Times [ op ]
const diff = ( ( v2 - v3 ) / v2 * 100 ) . toFixed ( 1 )
const symbol = v3 < v2 ? '🟢' : v3 > v2 * 1.1 ? '🔴' : '🟡'
console . log ( ` ${ symbol } ${ op . padEnd ( 10 ) } : v2= ${ v2 . toFixed ( 2 ) } ms, v3= ${ v3 . toFixed ( 2 ) } ms ( ${ diff > 0 ? '+' : '' } ${ diff } %) ` )
}
if ( v3Times . batch ) {
console . log ( ` 🚀 batch : v3= ${ v3Times . batch . toFixed ( 2 ) } ms (v3 exclusive feature) ` )
}
console . log ( '\n✨ Summary' )
console . log ( '═' . repeat ( 50 ) )
const totalV2 = Object . values ( v2Times ) . reduce ( ( a , b ) => a + b , 0 )
const totalV3 = Object . values ( v3Times ) . reduce ( ( a , b ) => a + b , 0 ) - ( v3Times . batch || 0 )
const improvement = ( ( totalV2 - totalV3 ) / totalV2 * 100 ) . toFixed ( 1 )
if ( totalV3 < totalV2 ) {
console . log ( ` ✅ v3 is ${ improvement } % faster overall! ` )
} else {
console . log ( ` ⚠️ v3 is ${ Math . abs ( improvement ) } % slower (needs optimization) ` )
}
console . log ( '\n💡 Key Insights:' )
console . log ( '- v3 adds batch operations for better throughput' )
console . log ( '- v3 has cleaner, more consistent API' )
console . log ( '- v3 includes streaming pipeline support' )
console . log ( '- Both versions use mock embeddings for fair comparison' )
}
// Run the benchmark
compare ( ) . catch ( console . error )