open-brainy/src/import/InstancePool.ts

270 lines
7 KiB
TypeScript
Raw Normal View History

/**
* InstancePool - Shared instance management for memory efficiency
*
* Production-grade instance pooling to prevent memory leaks during imports.
* Critical for scaling to billions of entities.
*
* Problem: Creating new NLP/Extractor instances in loops memory leak
* Solution: Reuse shared instances across entire import session
*
* Memory savings:
* - Without pooling: 100K rows × 50MB per instance = 5TB RAM (OOM!)
* - With pooling: 50MB total (shared across all rows)
*/
import { Brainy } from '../brainy.js'
import { NaturalLanguageProcessor } from '../neural/naturalLanguageProcessor.js'
import { NeuralEntityExtractor } from '../neural/entityExtractor.js'
/**
* InstancePool - Manages shared instances for memory efficiency
*
* Lifecycle:
* 1. Create pool at import start
* 2. Reuse instances across all rows
* 3. Pool is garbage collected when import completes
*
* Thread safety: Not thread-safe (single import session per pool)
*/
export class InstancePool {
private brain: Brainy
// Shared instances (created lazily)
private nlpInstance: NaturalLanguageProcessor | null = null
private extractorInstance: NeuralEntityExtractor | null = null
// Initialization state
private nlpInitialized = false
private initializationPromise: Promise<void> | null = null
// Statistics
private stats = {
nlpReuses: 0,
extractorReuses: 0,
creationTime: 0
}
constructor(brain: Brainy) {
this.brain = brain
}
/**
* Get shared NaturalLanguageProcessor instance
*
* Lazy initialization - created on first access
* All subsequent calls return same instance
*
* @returns Shared NLP instance
*/
async getNLP(): Promise<NaturalLanguageProcessor> {
if (!this.nlpInstance) {
const startTime = Date.now()
this.nlpInstance = new NaturalLanguageProcessor(this.brain)
this.stats.creationTime += Date.now() - startTime
}
// Ensure initialized before returning
if (!this.nlpInitialized) {
await this.ensureNLPInitialized()
}
this.stats.nlpReuses++
return this.nlpInstance
}
/**
* Get shared NeuralEntityExtractor instance
*
* Lazy initialization - created on first access
* All subsequent calls return same instance
*
* @returns Shared extractor instance
*/
getExtractor(): NeuralEntityExtractor {
if (!this.extractorInstance) {
const startTime = Date.now()
this.extractorInstance = new NeuralEntityExtractor(this.brain)
this.stats.creationTime += Date.now() - startTime
}
this.stats.extractorReuses++
return this.extractorInstance
}
/**
* Get shared NLP instance (synchronous, may return uninitialized)
*
* Use when you need NLP synchronously and will handle initialization yourself.
* Prefer getNLP() for async code.
*
* @returns Shared NLP instance (possibly uninitialized)
*/
getNLPSync(): NaturalLanguageProcessor {
if (!this.nlpInstance) {
this.nlpInstance = new NaturalLanguageProcessor(this.brain)
}
this.stats.nlpReuses++
return this.nlpInstance
}
/**
* Initialize all instances upfront
*
* Call at start of import to avoid lazy initialization overhead
* during processing. Improves predictability and first-row performance.
*
* @returns Promise that resolves when all instances are ready
*/
async init(): Promise<void> {
// Prevent duplicate initialization
if (this.initializationPromise) {
return this.initializationPromise
}
this.initializationPromise = this.initializeInternal()
return this.initializationPromise
}
/**
* Internal initialization implementation
*/
private async initializeInternal(): Promise<void> {
const startTime = Date.now()
// Create instances
if (!this.nlpInstance) {
this.nlpInstance = new NaturalLanguageProcessor(this.brain)
}
if (!this.extractorInstance) {
this.extractorInstance = new NeuralEntityExtractor(this.brain)
}
// Initialize NLP (loads pattern library)
await this.ensureNLPInitialized()
this.stats.creationTime = Date.now() - startTime
}
/**
* Ensure NLP is initialized (loads 220 patterns)
*
* Handles concurrent initialization requests safely
*/
private async ensureNLPInitialized(): Promise<void> {
if (this.nlpInitialized) {
return
}
if (!this.nlpInstance) {
throw new Error('NLP instance not created yet')
}
await this.nlpInstance.init()
this.nlpInitialized = true
}
/**
* Check if instances are initialized
*
* @returns True if NLP is initialized and ready to use
*/
isInitialized(): boolean {
return this.nlpInitialized && this.nlpInstance !== null
}
/**
* Get pool statistics
*
* Useful for performance monitoring and memory leak detection
*
* @returns Statistics about instance reuse
*/
getStats() {
return {
...this.stats,
nlpCreated: this.nlpInstance !== null,
extractorCreated: this.extractorInstance !== null,
initialized: this.isInitialized(),
// Memory savings estimate
memorySaved: this.calculateMemorySaved()
}
}
/**
* Calculate estimated memory saved by pooling
*
* Assumes ~50MB per NLP instance, ~10MB per extractor instance
*
* @returns Estimated memory saved in bytes
*/
private calculateMemorySaved(): number {
const nlpSize = 50 * 1024 * 1024 // 50MB per instance
const extractorSize = 10 * 1024 * 1024 // 10MB per instance
// Without pooling: size × reuses
// With pooling: size × 1
// Saved: size × (reuses - 1)
const nlpSaved = nlpSize * Math.max(0, this.stats.nlpReuses - 1)
const extractorSaved = extractorSize * Math.max(0, this.stats.extractorReuses - 1)
return nlpSaved + extractorSaved
}
/**
* Reset statistics (useful for testing)
*/
resetStats(): void {
this.stats = {
nlpReuses: 0,
extractorReuses: 0,
creationTime: 0
}
}
/**
* Get string representation (for debugging)
*/
toString(): string {
const stats = this.getStats()
return `InstancePool(nlp=${stats.nlpCreated}, extractor=${stats.extractorCreated}, initialized=${stats.initialized}, nlpReuses=${stats.nlpReuses}, extractorReuses=${stats.extractorReuses})`
}
/**
* Cleanup method (for explicit resource management)
*
* Note: Usually not needed - pool is garbage collected when import completes.
* Use only if you need explicit cleanup for some reason.
*/
cleanup(): void {
// Clear references to allow garbage collection
this.nlpInstance = null
this.extractorInstance = null
this.nlpInitialized = false
this.initializationPromise = null
}
}
/**
* Create a new instance pool
*
* Convenience factory function
*
* @param brain Brainy instance
* @param autoInit Whether to initialize instances immediately
* @returns Instance pool
*/
export async function createInstancePool(
brain: Brainy,
autoInit = true
): Promise<InstancePool> {
const pool = new InstancePool(brain)
if (autoInit) {
await pool.init()
}
return pool
}