@soulcraft/brainy 3.47.0 β 3.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brainy.js +17 -9
- package/dist/hnsw/typeAwareHNSWIndex.js +104 -48
- package/package.json +1 -1
package/dist/brainy.js
CHANGED
|
@@ -2523,6 +2523,14 @@ export class Brainy {
|
|
|
2523
2523
|
}
|
|
2524
2524
|
return;
|
|
2525
2525
|
}
|
|
2526
|
+
// OPTIMIZATION: Instant check - if index already has data, skip immediately
|
|
2527
|
+
// This gives 0s startup for warm restarts (vs 50-100ms of async checks)
|
|
2528
|
+
if (this.index.size() > 0) {
|
|
2529
|
+
if (!this.config.silent) {
|
|
2530
|
+
console.log(`β
Index already populated (${this.index.size().toLocaleString()} entities) - 0s startup!`);
|
|
2531
|
+
}
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2526
2534
|
// BUG #2 FIX: Don't trust counts - check actual storage instead
|
|
2527
2535
|
// Counts can be lost/corrupted in container restarts
|
|
2528
2536
|
const entities = await this.storage.getNouns({ pagination: { limit: 1 } });
|
|
@@ -2543,31 +2551,31 @@ export class Brainy {
|
|
|
2543
2551
|
graphIndexSize === 0 ||
|
|
2544
2552
|
this.config.disableAutoRebuild === false; // Explicitly enabled
|
|
2545
2553
|
if (!needsRebuild) {
|
|
2546
|
-
// All indexes populated, no rebuild needed
|
|
2554
|
+
// All indexes already populated, no rebuild needed
|
|
2547
2555
|
return;
|
|
2548
2556
|
}
|
|
2549
2557
|
// Small dataset: Rebuild all indexes for best performance
|
|
2550
2558
|
if (totalCount < AUTO_REBUILD_THRESHOLD || this.config.disableAutoRebuild === false) {
|
|
2551
2559
|
if (!this.config.silent) {
|
|
2552
2560
|
console.log(this.config.disableAutoRebuild === false
|
|
2553
|
-
? 'π Auto-rebuild explicitly enabled - rebuilding all indexes...'
|
|
2554
|
-
: `π Small dataset (${totalCount} items) - rebuilding all indexes...`);
|
|
2561
|
+
? 'π Auto-rebuild explicitly enabled - rebuilding all indexes from persisted data...'
|
|
2562
|
+
: `π Small dataset (${totalCount} items) - rebuilding all indexes from persisted data...`);
|
|
2555
2563
|
}
|
|
2556
|
-
// BUG #1 FIX: Actually call graphIndex.rebuild()
|
|
2557
|
-
// BUG #4 FIX: Actually call HNSW index.rebuild()
|
|
2558
2564
|
// Rebuild all 3 indexes in parallel for performance
|
|
2559
|
-
|
|
2565
|
+
// Indexes load their data from storage (no recomputation)
|
|
2566
|
+
const rebuildStartTime = Date.now();
|
|
2560
2567
|
await Promise.all([
|
|
2561
2568
|
metadataStats.totalEntries === 0 ? this.metadataIndex.rebuild() : Promise.resolve(),
|
|
2562
2569
|
hnswIndexSize === 0 ? this.index.rebuild() : Promise.resolve(),
|
|
2563
2570
|
graphIndexSize === 0 ? this.graphIndex.rebuild() : Promise.resolve()
|
|
2564
2571
|
]);
|
|
2565
|
-
const
|
|
2572
|
+
const rebuildDuration = Date.now() - rebuildStartTime;
|
|
2566
2573
|
if (!this.config.silent) {
|
|
2567
|
-
console.log(`β
All indexes rebuilt in ${
|
|
2574
|
+
console.log(`β
All indexes rebuilt in ${rebuildDuration}ms:\n` +
|
|
2568
2575
|
` - Metadata: ${await this.metadataIndex.getStats().then(s => s.totalEntries)} entries\n` +
|
|
2569
2576
|
` - HNSW Vector: ${this.index.size()} nodes\n` +
|
|
2570
|
-
` - Graph Adjacency: ${await this.graphIndex.size()} relationships`
|
|
2577
|
+
` - Graph Adjacency: ${await this.graphIndex.size()} relationships\n` +
|
|
2578
|
+
` π‘ Indexes loaded from persisted storage (no recomputation)`);
|
|
2571
2579
|
}
|
|
2572
2580
|
}
|
|
2573
2581
|
else {
|
|
@@ -292,65 +292,121 @@ export class TypeAwareHNSWIndex {
|
|
|
292
292
|
prodLog.warn('TypeAwareHNSW rebuild skipped: no storage adapter');
|
|
293
293
|
return;
|
|
294
294
|
}
|
|
295
|
+
const batchSize = options.batchSize || 1000;
|
|
295
296
|
// Determine which types to rebuild
|
|
296
297
|
const typesToRebuild = options.types || this.getAllNounTypes();
|
|
297
|
-
prodLog.info(`Rebuilding ${typesToRebuild.length} type-aware HNSW indexes...`);
|
|
298
|
-
|
|
299
|
-
// Rebuild each type's index with type-filtered pagination
|
|
298
|
+
prodLog.info(`Rebuilding ${typesToRebuild.length} type-aware HNSW indexes from persisted data...`);
|
|
299
|
+
// Clear all indexes we're rebuilding
|
|
300
300
|
for (const type of typesToRebuild) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
301
|
+
const index = this.getIndexForType(type);
|
|
302
|
+
index.nouns.clear();
|
|
303
|
+
}
|
|
304
|
+
// Determine preloading strategy (adaptive caching) for entire dataset
|
|
305
|
+
const stats = await this.storage.getStatistics();
|
|
306
|
+
const entityCount = stats?.totalNodes || 0;
|
|
307
|
+
const vectorMemory = entityCount * 1536; // 384 dims Γ 4 bytes
|
|
308
|
+
// Use first index's cache (they all share the same UnifiedCache)
|
|
309
|
+
const firstIndex = this.getIndexForType(typesToRebuild[0]);
|
|
310
|
+
const cacheStats = firstIndex.unifiedCache.getStats();
|
|
311
|
+
const availableCache = cacheStats.maxSize * 0.80;
|
|
312
|
+
const shouldPreload = vectorMemory < availableCache;
|
|
313
|
+
if (shouldPreload) {
|
|
314
|
+
prodLog.info(`HNSW: Preloading ${entityCount.toLocaleString()} vectors at init ` +
|
|
315
|
+
`(${(vectorMemory / 1024 / 1024).toFixed(1)}MB < ${(availableCache / 1024 / 1024).toFixed(1)}MB cache)`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
prodLog.info(`HNSW: Adaptive caching for ${entityCount.toLocaleString()} vectors ` +
|
|
319
|
+
`(${(vectorMemory / 1024 / 1024).toFixed(1)}MB > ${(availableCache / 1024 / 1024).toFixed(1)}MB cache) - loading on-demand`);
|
|
320
|
+
}
|
|
321
|
+
// Load ALL nouns ONCE and route to correct type indexes
|
|
322
|
+
// This is O(N) instead of O(31*N) from the previous parallel approach
|
|
323
|
+
let cursor = undefined;
|
|
324
|
+
let hasMore = true;
|
|
325
|
+
let totalLoaded = 0;
|
|
326
|
+
const loadedByType = new Map();
|
|
327
|
+
while (hasMore) {
|
|
328
|
+
const result = await this.storage.getNounsWithPagination({
|
|
329
|
+
limit: batchSize,
|
|
330
|
+
cursor
|
|
331
|
+
});
|
|
332
|
+
// Route each noun to its type index
|
|
333
|
+
for (const nounData of result.items) {
|
|
334
|
+
try {
|
|
335
|
+
// Determine noun type from multiple possible sources
|
|
336
|
+
const nounType = nounData.nounType || nounData.metadata?.noun || nounData.metadata?.type;
|
|
337
|
+
// Skip if type not in rebuild list
|
|
338
|
+
if (!nounType || !typesToRebuild.includes(nounType)) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
// Get the index for this type
|
|
342
|
+
const index = this.getIndexForType(nounType);
|
|
343
|
+
// Load HNSW graph data
|
|
344
|
+
const hnswData = await this.storage.getHNSWData(nounData.id);
|
|
345
|
+
if (!hnswData) {
|
|
346
|
+
continue; // No HNSW data
|
|
347
|
+
}
|
|
348
|
+
// Create noun with restored connections
|
|
349
|
+
const noun = {
|
|
350
|
+
id: nounData.id,
|
|
351
|
+
vector: shouldPreload ? nounData.vector : [],
|
|
352
|
+
connections: new Map(),
|
|
353
|
+
level: hnswData.level
|
|
354
|
+
};
|
|
355
|
+
// Restore connections from storage
|
|
356
|
+
for (const [levelStr, nounIds] of Object.entries(hnswData.connections)) {
|
|
357
|
+
const level = parseInt(levelStr, 10);
|
|
358
|
+
noun.connections.set(level, new Set(nounIds));
|
|
359
|
+
}
|
|
360
|
+
// Add to type-specific index
|
|
361
|
+
;
|
|
362
|
+
index.nouns.set(nounData.id, noun);
|
|
363
|
+
// Track high-level nodes
|
|
364
|
+
if (noun.level >= 2 && noun.level <= index.MAX_TRACKED_LEVELS) {
|
|
365
|
+
if (!index.highLevelNodes.has(noun.level)) {
|
|
366
|
+
;
|
|
367
|
+
index.highLevelNodes.set(noun.level, new Set());
|
|
331
368
|
}
|
|
369
|
+
;
|
|
370
|
+
index.highLevelNodes.get(noun.level).add(nounData.id);
|
|
332
371
|
}
|
|
333
|
-
|
|
334
|
-
|
|
372
|
+
// Track progress
|
|
373
|
+
loadedByType.set(nounType, (loadedByType.get(nounType) || 0) + 1);
|
|
374
|
+
totalLoaded++;
|
|
375
|
+
if (options.onProgress && totalLoaded % 100 === 0) {
|
|
376
|
+
options.onProgress(nounType, loadedByType.get(nounType) || 0, totalLoaded);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
prodLog.error(`Failed to restore HNSW data for ${nounData.id}:`, error);
|
|
335
381
|
}
|
|
336
|
-
prodLog.info(`β
Rebuilt ${type} index: ${index.size().toLocaleString()} entities`);
|
|
337
382
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
383
|
+
hasMore = result.hasMore;
|
|
384
|
+
cursor = result.nextCursor;
|
|
385
|
+
// Progress logging
|
|
386
|
+
if (totalLoaded % 1000 === 0) {
|
|
387
|
+
prodLog.info(`Progress: ${totalLoaded.toLocaleString()} entities loaded...`);
|
|
342
388
|
}
|
|
343
389
|
}
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
390
|
+
// Restore entry points for each type
|
|
391
|
+
for (const type of typesToRebuild) {
|
|
392
|
+
const index = this.getIndexForType(type);
|
|
393
|
+
let maxLevel = 0;
|
|
394
|
+
let entryPointId = null;
|
|
395
|
+
for (const [id, noun] of index.nouns.entries()) {
|
|
396
|
+
if (noun.level > maxLevel) {
|
|
397
|
+
maxLevel = noun.level;
|
|
398
|
+
entryPointId = id;
|
|
399
|
+
}
|
|
351
400
|
}
|
|
401
|
+
;
|
|
402
|
+
index.entryPointId = entryPointId;
|
|
403
|
+
index.maxLevel = maxLevel;
|
|
404
|
+
const loaded = loadedByType.get(type) || 0;
|
|
405
|
+
const cacheInfo = shouldPreload ? ' (vectors preloaded)' : ' (adaptive caching)';
|
|
406
|
+
prodLog.info(`β
Rebuilt ${type} index: ${loaded.toLocaleString()} entities, ` +
|
|
407
|
+
`${maxLevel + 1} levels, entry point: ${entryPointId || 'none'}${cacheInfo}`);
|
|
352
408
|
}
|
|
353
|
-
prodLog.info(`β
TypeAwareHNSW rebuild complete: ${this.size().toLocaleString()} total entities across ${this.indexes.size} types`);
|
|
409
|
+
prodLog.info(`β
TypeAwareHNSW rebuild complete: ${this.size().toLocaleString()} total entities across ${this.indexes.size} types (loaded from persisted graph structure)`);
|
|
354
410
|
}
|
|
355
411
|
/**
|
|
356
412
|
* Get comprehensive statistics
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "3.47.
|
|
3
|
+
"version": "3.47.1",
|
|
4
4
|
"description": "Universal Knowledge Protocolβ’ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns Γ 40 verbs for infinite expressiveness.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|