2025-08-25 09:52:32 -07:00
/ * *
* Metadata Filtering Performance Analysis
*
* This test suite analyzes the performance impact of the metadata filtering system :
* 1 . Index Build Time - How metadata indexing affects initialization
* 2 . Index Storage Overhead - Storage space required for inverted indexes
* 3 . Search Performance - Filtered vs non - filtered search speeds
* 4 . Memory Usage - Additional memory needed for metadata indexes
* 5 . Write Performance - Impact on add / update / delete operations
* /
import { describe , it , expect , beforeEach } from 'vitest'
import { BrainyData } from '../src/brainyData.js'
import { MetadataIndexManager } from '../src/utils/metadataIndex.js'
// Helper function to measure execution time
const measureTime = async ( fn : ( ) = > Promise < any > ) : Promise < { result : any , time : number } > = > {
const start = performance . now ( )
const result = await fn ( )
const end = performance . now ( )
return { result , time : end - start }
}
// Helper function to estimate memory usage
const measureMemory = ( ) = > {
if ( typeof performance . memory !== 'undefined' ) {
return {
used : performance.memory.usedJSHeapSize ,
total : performance.memory.totalJSHeapSize ,
limit : performance.memory.jsHeapSizeLimit
}
}
return null
}
// Generate realistic test data with metadata
const generateTestDataWithMetadata = ( count : number ) = > {
const departments = [ 'Engineering' , 'Marketing' , 'Sales' , 'HR' , 'Finance' , 'Operations' ]
const levels = [ 'junior' , 'senior' , 'staff' , 'principal' , 'director' ]
const locations = [ 'SF' , 'NYC' , 'LA' , 'Seattle' , 'Austin' , 'Boston' ]
const skills = [ 'JavaScript' , 'Python' , 'React' , 'Node.js' , 'TypeScript' , 'SQL' , 'AWS' , 'Docker' ]
const companies = [ 'TechCorp' , 'DataSys' , 'CloudInc' , 'DevTools' , 'AILabs' ]
return Array . from ( { length : count } , ( _ , i ) = > ( {
text : ` Profile ${ i } : Professional with extensive experience in software development and team leadership ` ,
metadata : {
id : ` profile- ${ i } ` ,
department : departments [ i % departments . length ] ,
level : levels [ i % levels . length ] ,
location : locations [ i % locations . length ] ,
salary : 50000 + ( i % 10 ) * 10000 ,
experience : 1 + ( i % 15 ) ,
skills : skills.slice ( 0 , 2 + ( i % 4 ) ) ,
company : companies [ i % companies . length ] ,
remote : i % 3 === 0 ,
active : i % 5 !== 0 ,
tags : [ ` tag- ${ i % 20 } ` , ` category- ${ i % 10 } ` ] ,
nested : {
profile : {
rating : 1 + ( i % 5 ) ,
verified : i % 4 === 0
} ,
preferences : {
timezone : ` UTC- ${ ( i % 12 ) - 6 } ` ,
workStyle : i % 2 === 0 ? 'collaborative' : 'independent'
}
}
}
} ) )
}
describe ( 'Metadata Filtering Performance Analysis' , ( ) = > {
describe ( '1. Index Build Time Impact' , ( ) = > {
it ( 'should measure initialization time with vs without metadata indexing' , async ( ) = > {
const testData = generateTestDataWithMetadata ( 500 )
console . log ( '\n=== Index Build Time Analysis ===' )
// Test WITHOUT metadata indexing
const withoutIndexing = await measureTime ( async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
hnsw : { M : 8 , efConstruction : 50 } ,
logging : { verbose : false }
// No metadataIndex config
} )
await brainy . init ( )
// Add data
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
return brainy
} )
console . log ( ` WITHOUT indexing: ${ withoutIndexing . time . toFixed ( 2 ) } ms for 500 items ` )
console . log ( ` Per item: ${ ( withoutIndexing . time / 500 ) . toFixed ( 2 ) } ms ` )
// Test WITH metadata indexing
const withIndexing = await measureTime ( async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
hnsw : { M : 8 , efConstruction : 50 } ,
logging : { verbose : false } ,
metadataIndex : {
maxIndexSize : 10000 ,
autoOptimize : true ,
excludeFields : [ 'id' ]
}
} )
await brainy . init ( )
// Add data
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
return brainy
} )
console . log ( ` WITH indexing: ${ withIndexing . time . toFixed ( 2 ) } ms for 500 items ` )
console . log ( ` Per item: ${ ( withIndexing . time / 500 ) . toFixed ( 2 ) } ms ` )
const overhead = ( ( withIndexing . time - withoutIndexing . time ) / withoutIndexing . time ) * 100
console . log ( ` Index build overhead: ${ overhead . toFixed ( 1 ) } % ` )
// Cleanup
await withoutIndexing . result . shutDown ( )
await withIndexing . result . shutDown ( )
} )
it ( 'should measure batch insert performance with indexing' , async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
metadataIndex : { autoOptimize : true } ,
logging : { verbose : false }
} )
await brainy . init ( )
const batchSizes = [ 50 , 100 , 200 , 500 ]
console . log ( '\n=== Batch Insert Performance ===' )
for ( const size of batchSizes ) {
const testData = generateTestDataWithMetadata ( size )
const { time } = await measureTime ( async ( ) = > {
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
} )
console . log ( ` ${ size } items: ${ time . toFixed ( 2 ) } ms ( ${ ( time / size ) . toFixed ( 2 ) } ms per item) ` )
// Clear for next batch
await brainy . clearAll ( { force : true } )
}
await brainy . shutDown ( )
} )
} )
describe ( '2. Index Storage Overhead' , ( ) = > {
it ( 'should analyze storage requirements for metadata indexes' , async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
metadataIndex : { autoOptimize : true } ,
logging : { verbose : false }
} )
await brainy . init ( )
const testData = generateTestDataWithMetadata ( 1000 )
console . log ( '\n=== Storage Overhead Analysis ===' )
// Add data and measure index size
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
// Get index statistics
if ( brainy . metadataIndex ) {
const stats = await brainy . metadataIndex . getStats ( )
console . log ( ` Total index entries: ${ stats . totalEntries } ` )
console . log ( ` Total indexed IDs: ${ stats . totalIds } ` )
console . log ( ` Fields indexed: ${ stats . fieldsIndexed . length } ` )
console . log ( ` Estimated index size: ${ stats . indexSize } bytes ` )
console . log ( ` Fields: ${ stats . fieldsIndexed . join ( ', ' ) } ` )
// Calculate overhead per item
const overheadPerItem = stats . indexSize / 1000
console . log ( ` Storage overhead per item: ${ overheadPerItem . toFixed ( 2 ) } bytes ` )
// Estimate total storage efficiency
const totalDataSize = 1000 * 200 // rough estimate of 200 bytes per item
const storageEfficiency = ( stats . indexSize / totalDataSize ) * 100
console . log ( ` Index storage overhead: ${ storageEfficiency . toFixed ( 1 ) } % of data size ` )
}
await brainy . shutDown ( )
} )
} )
describe ( '3. Search Performance Comparison' , ( ) = > {
it ( 'should compare filtered vs non-filtered search performance' , async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
metadataIndex : { autoOptimize : true } ,
logging : { verbose : false }
} )
await brainy . init ( )
// Add test data
const testData = generateTestDataWithMetadata ( 1000 )
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
console . log ( '\n=== Search Performance Comparison ===' )
const searchQuery = 'Professional software development experience'
const numSearches = 10
// Test 1: No filtering
const noFilterTimes : number [ ] = [ ]
for ( let i = 0 ; i < numSearches ; i ++ ) {
const { time } = await measureTime ( async ( ) = > {
2025-08-26 12:03:45 -07:00
return await brainy . search ( searchQuery , { limit : 20 } )
2025-08-25 09:52:32 -07:00
} )
noFilterTimes . push ( time )
}
const avgNoFilter = noFilterTimes . reduce ( ( a , b ) = > a + b ) / numSearches
console . log ( ` No filtering: ${ avgNoFilter . toFixed ( 2 ) } ms average ` )
// Test 2: Simple metadata filtering (high selectivity)
const simpleFilterTimes : number [ ] = [ ]
for ( let i = 0 ; i < numSearches ; i ++ ) {
const { time } = await measureTime ( async ( ) = > {
2025-08-26 12:03:45 -07:00
return await brainy . search ( searchQuery , { limit : 20 ,
2025-08-25 09:52:32 -07:00
metadata : { department : 'Engineering' }
} )
} )
simpleFilterTimes . push ( time )
}
const avgSimpleFilter = simpleFilterTimes . reduce ( ( a , b ) = > a + b ) / numSearches
console . log ( ` Simple filter (dept=Engineering): ${ avgSimpleFilter . toFixed ( 2 ) } ms average ` )
// Test 3: Complex metadata filtering (low selectivity)
const complexFilterTimes : number [ ] = [ ]
for ( let i = 0 ; i < numSearches ; i ++ ) {
const { time } = await measureTime ( async ( ) = > {
2025-08-26 12:03:45 -07:00
return await brainy . search ( searchQuery , { limit : 20 ,
2025-08-25 09:52:32 -07:00
metadata : {
department : { $in : [ 'Engineering' , 'Marketing' ] } ,
level : { $in : [ 'senior' , 'staff' ] } ,
salary : { $gte : 80000 } ,
remote : true
}
} )
} )
complexFilterTimes . push ( time )
}
const avgComplexFilter = complexFilterTimes . reduce ( ( a , b ) = > a + b ) / numSearches
console . log ( ` Complex filter: ${ avgComplexFilter . toFixed ( 2 ) } ms average ` )
// Test 4: Nested field filtering
const nestedFilterTimes : number [ ] = [ ]
for ( let i = 0 ; i < numSearches ; i ++ ) {
const { time } = await measureTime ( async ( ) = > {
2025-08-26 12:03:45 -07:00
return await brainy . search ( searchQuery , { limit : 20 ,
2025-08-25 09:52:32 -07:00
metadata : {
'nested.profile.rating' : { $gte : 4 } ,
'nested.profile.verified' : true
}
} )
} )
nestedFilterTimes . push ( time )
}
const avgNestedFilter = nestedFilterTimes . reduce ( ( a , b ) = > a + b ) / numSearches
console . log ( ` Nested filter: ${ avgNestedFilter . toFixed ( 2 ) } ms average ` )
// Performance analysis
console . log ( '\nPerformance Impact:' )
console . log ( ` Simple filter overhead: ${ ( ( avgSimpleFilter / avgNoFilter - 1 ) * 100 ) . toFixed ( 1 ) } % ` )
console . log ( ` Complex filter overhead: ${ ( ( avgComplexFilter / avgNoFilter - 1 ) * 100 ) . toFixed ( 1 ) } % ` )
console . log ( ` Nested filter overhead: ${ ( ( avgNestedFilter / avgNoFilter - 1 ) * 100 ) . toFixed ( 1 ) } % ` )
await brainy . shutDown ( )
} )
it ( 'should test search performance with different ef multipliers' , async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
metadataIndex : { autoOptimize : true } ,
hnsw : { efSearch : 50 } , // Base ef for testing multiplier effect
logging : { verbose : false }
} )
await brainy . init ( )
// Add test data
const testData = generateTestDataWithMetadata ( 500 )
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
console . log ( '\n=== EF Multiplier Impact Analysis ===' )
const searchQuery = 'Professional software development experience'
// Test with different selectivity filters
const filters = [
{ name : 'High selectivity' , filter : { department : 'Engineering' } , expected : '~17%' } ,
{ name : 'Medium selectivity' , filter : { level : { $in : [ 'senior' , 'staff' ] } } , expected : '~40%' } ,
{ name : 'Low selectivity' , filter : { active : true } , expected : '~80%' }
]
for ( const { name , filter , expected } of filters ) {
const { result , time } = await measureTime ( async ( ) = > {
2025-08-26 12:03:45 -07:00
return await brainy . search ( searchQuery , { limit : 10 , metadata : filter } )
2025-08-25 09:52:32 -07:00
} )
console . log ( ` ${ name } ( ${ expected } ): ${ time . toFixed ( 2 ) } ms, ${ result . length } results ` )
}
await brainy . shutDown ( )
} )
} )
describe ( '4. Memory Usage Analysis' , ( ) = > {
it ( 'should measure memory consumption of metadata indexes' , async ( ) = > {
if ( ! measureMemory ( ) ) {
console . log ( '\nMemory measurement not available in this environment' )
return
}
console . log ( '\n=== Memory Usage Analysis ===' )
const initialMemory = measureMemory ( ) !
console . log ( ` Initial memory: ${ ( initialMemory . used / 1024 / 1024 ) . toFixed ( 2 ) } MB ` )
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
metadataIndex : { autoOptimize : true } ,
logging : { verbose : false }
} )
await brainy . init ( )
const afterInitMemory = measureMemory ( ) !
console . log ( ` After init: ${ ( afterInitMemory . used / 1024 / 1024 ) . toFixed ( 2 ) } MB ` )
// Add data in batches and measure memory growth
const batchSize = 100
const numBatches = 5
for ( let batch = 1 ; batch <= numBatches ; batch ++ ) {
const testData = generateTestDataWithMetadata ( batchSize )
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
const currentMemory = measureMemory ( ) !
const totalItems = batch * batchSize
console . log ( ` ${ totalItems } items: ${ ( currentMemory . used / 1024 / 1024 ) . toFixed ( 2 ) } MB ` )
}
// Get final index stats
if ( brainy . metadataIndex ) {
const stats = await brainy . metadataIndex . getStats ( )
console . log ( ` Index entries: ${ stats . totalEntries } , Memory per entry: ${ ( ( measureMemory ( ) ! . used - initialMemory . used ) / stats . totalEntries ) . toFixed ( 2 ) } bytes ` )
}
await brainy . shutDown ( )
} )
} )
describe ( '5. Write Performance Impact' , ( ) = > {
it ( 'should measure add/update/delete performance with indexing' , async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
metadataIndex : { autoOptimize : true } ,
logging : { verbose : false }
} )
await brainy . init ( )
console . log ( '\n=== Write Performance Analysis ===' )
// Test ADD performance
const testData = generateTestDataWithMetadata ( 200 )
const { time : addTime } = await measureTime ( async ( ) = > {
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
} )
console . log ( ` ADD: 200 items in ${ addTime . toFixed ( 2 ) } ms ( ${ ( addTime / 200 ) . toFixed ( 2 ) } ms per item) ` )
// Test UPDATE performance
const updateData = testData . slice ( 0 , 50 ) . map ( ( item , i ) = > ( {
. . . item ,
metadata : {
. . . item . metadata ,
level : 'updated-level' ,
salary : item.metadata.salary + 10000 ,
updateCount : i
}
} ) )
const { time : updateTime } = await measureTime ( async ( ) = > {
for ( const item of updateData ) {
await brainy . updateMetadata ( item . metadata . id , item . metadata )
}
} )
console . log ( ` UPDATE: 50 items in ${ updateTime . toFixed ( 2 ) } ms ( ${ ( updateTime / 50 ) . toFixed ( 2 ) } ms per item) ` )
// Test DELETE performance
const idsToDelete = testData . slice ( 100 , 150 ) . map ( item = > item . metadata . id )
const { time : deleteTime } = await measureTime ( async ( ) = > {
for ( const id of idsToDelete ) {
await brainy . delete ( id )
}
} )
console . log ( ` DELETE: 50 items in ${ deleteTime . toFixed ( 2 ) } ms ( ${ ( deleteTime / 50 ) . toFixed ( 2 ) } ms per item) ` )
// Verify index consistency
if ( brainy . metadataIndex ) {
const stats = await brainy . metadataIndex . getStats ( )
console . log ( ` Final index state: ${ stats . totalEntries } entries, ${ stats . totalIds } IDs ` )
// Should have 150 items remaining (200 - 50 deleted)
const expectedItems = 200 - 50
const actualItems = await brainy . size ( )
console . log ( ` Data consistency: ${ actualItems } / ${ expectedItems } items remaining ` )
}
await brainy . shutDown ( )
} )
it ( 'should test concurrent write performance' , async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
metadataIndex : { autoOptimize : true } ,
logging : { verbose : false }
} )
await brainy . init ( )
console . log ( '\n=== Concurrent Write Performance ===' )
const testData = generateTestDataWithMetadata ( 100 )
// Sequential writes
const { time : sequentialTime } = await measureTime ( async ( ) = > {
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
} )
await brainy . clearAll ( { force : true } )
// Concurrent writes (batched)
const batchSize = 20
const { time : concurrentTime } = await measureTime ( async ( ) = > {
const promises : Promise < any > [ ] = [ ]
for ( let i = 0 ; i < testData . length ; i += batchSize ) {
const batch = testData . slice ( i , i + batchSize )
promises . push (
Promise . all ( batch . map ( item = > brainy . add ( item . text , item . metadata ) ) )
)
}
await Promise . all ( promises )
} )
console . log ( ` Sequential: ${ sequentialTime . toFixed ( 2 ) } ms ` )
console . log ( ` Concurrent (batched): ${ concurrentTime . toFixed ( 2 ) } ms ` )
console . log ( ` Speedup: ${ ( sequentialTime / concurrentTime ) . toFixed ( 2 ) } x ` )
await brainy . shutDown ( )
} )
} )
describe ( '6. Index Maintenance and Optimization' , ( ) = > {
it ( 'should analyze index rebuild performance' , async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
metadataIndex : {
autoOptimize : true ,
rebuildThreshold : 0.1
} ,
logging : { verbose : false }
} )
await brainy . init ( )
console . log ( '\n=== Index Maintenance Analysis ===' )
// Add initial data
const testData = generateTestDataWithMetadata ( 300 )
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
// Measure manual rebuild
if ( brainy . metadataIndex ) {
const { time : rebuildTime } = await measureTime ( async ( ) = > {
await brainy . metadataIndex ! . rebuild ( )
} )
const stats = await brainy . metadataIndex . getStats ( )
console . log ( ` Rebuild: ${ rebuildTime . toFixed ( 2 ) } ms for ${ stats . totalEntries } entries ` )
console . log ( ` Per entry: ${ ( rebuildTime / stats . totalEntries ) . toFixed ( 2 ) } ms ` )
// Test flush performance
const { time : flushTime } = await measureTime ( async ( ) = > {
await brainy . metadataIndex ! . flush ( )
} )
console . log ( ` Flush: ${ flushTime . toFixed ( 2 ) } ms ` )
}
await brainy . shutDown ( )
} )
it ( 'should test index cache performance' , async ( ) = > {
const brainy = new BrainyData ( {
storage : { forceMemoryStorage : true } ,
metadataIndex : {
maxIndexSize : 1000 ,
autoOptimize : true
} ,
logging : { verbose : false }
} )
await brainy . init ( )
console . log ( '\n=== Index Cache Performance ===' )
// Add test data
const testData = generateTestDataWithMetadata ( 200 )
for ( const item of testData ) {
await brainy . add ( item . text , item . metadata )
}
if ( ! brainy . metadataIndex ) return
// Test cache hit performance (repeated queries)
const filter = { department : 'Engineering' }
// First query (cache miss)
const { time : cacheMissTime } = await measureTime ( async ( ) = > {
return await brainy . metadataIndex ! . getIdsForCriteria ( filter )
} )
// Subsequent queries (cache hits)
const cacheHitTimes : number [ ] = [ ]
for ( let i = 0 ; i < 10 ; i ++ ) {
const { time } = await measureTime ( async ( ) = > {
return await brainy . metadataIndex ! . getIdsForCriteria ( filter )
} )
cacheHitTimes . push ( time )
}
const avgCacheHit = cacheHitTimes . reduce ( ( a , b ) = > a + b ) / cacheHitTimes . length
console . log ( ` Cache miss: ${ cacheMissTime . toFixed ( 2 ) } ms ` )
console . log ( ` Cache hit (avg): ${ avgCacheHit . toFixed ( 2 ) } ms ` )
console . log ( ` Cache speedup: ${ ( cacheMissTime / avgCacheHit ) . toFixed ( 2 ) } x ` )
await brainy . shutDown ( )
} )
} )
} )