fix: flush graph LSM-trees on close to prevent data loss across restarts
GraphAdjacencyIndex.flush() was a no-op — LSM MemTables were never written to SSTables for datasets under the 100K auto-flush threshold. This caused readdir, getRelations, and getDescendants to return empty results after close + reopen. Three fixes: - LSMTree.get(): merge MemTable + SSTable results (data spans both after flush, old early-return missed SSTable data) - GraphAdjacencyIndex.flush(): actually flush all 4 LSM-trees - GraphAdjacencyIndex.close(): close all 4 trees (was only closing 2) Also: brain.close() and shutdown hooks now call close() on graphIndex, HNSW index, and metadataIndex to release timers and file handles.
This commit is contained in:
parent
0add0af45a
commit
ab2493af02
4 changed files with 213 additions and 22 deletions
|
|
@ -461,7 +461,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
let flushedCount = 0
|
||||
for (const instance of Brainy.instances) {
|
||||
if (instance.initialized) {
|
||||
// Full flush: counts + metadata index + graph index + HNSW dirty nodes
|
||||
// Flush all buffered data, then close to release resources (timers, handles)
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
if (instance.storage && typeof (instance.storage as any).flushCounts === 'function') {
|
||||
|
|
@ -484,6 +484,24 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
}
|
||||
})()
|
||||
])
|
||||
// Close components to stop timers that would prevent clean process exit
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
if (instance.graphIndex && typeof instance.graphIndex.close === 'function') {
|
||||
await instance.graphIndex.close()
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
if (instance.index && typeof (instance.index as any).close === 'function') {
|
||||
await (instance.index as any).close()
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
if (instance.metadataIndex && typeof (instance.metadataIndex as any).close === 'function') {
|
||||
await (instance.metadataIndex as any).close()
|
||||
}
|
||||
})(),
|
||||
])
|
||||
flushedCount++
|
||||
}
|
||||
}
|
||||
|
|
@ -6702,7 +6720,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
* This ensures deferred persistence mode data is saved
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
// Flush ALL components before closing to prevent data loss
|
||||
// Phase 1: Flush ALL components in parallel to persist buffered data
|
||||
// This is critical when cortex native providers buffer data in Rust memory
|
||||
await Promise.all([
|
||||
// Flush HNSW dirty nodes (deferred persistence mode)
|
||||
|
|
@ -6731,7 +6749,27 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
})()
|
||||
])
|
||||
|
||||
// Deactivate plugins (safe — all data flushed above)
|
||||
// Phase 2: Close components to release resources (timers, file handles)
|
||||
// Data is already safe on disk from Phase 1
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
if (this.graphIndex && typeof this.graphIndex.close === 'function') {
|
||||
await this.graphIndex.close()
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
if (this.index && typeof (this.index as any).close === 'function') {
|
||||
await (this.index as any).close()
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
if (this.metadataIndex && typeof (this.metadataIndex as any).close === 'function') {
|
||||
await (this.metadataIndex as any).close()
|
||||
}
|
||||
})(),
|
||||
])
|
||||
|
||||
// Deactivate plugins (safe — all data flushed and resources released above)
|
||||
await this.pluginRegistry.deactivateAll()
|
||||
|
||||
// Restore console methods if silent mode was enabled
|
||||
|
|
@ -6751,8 +6789,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// Storage doesn't have close in current interface
|
||||
// We'll just mark as not initialized
|
||||
this.initialized = false
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue