brainy/tests/unit/import/InstancePool.test.ts
David Snelling 52782898a3 feat: implement progressive flush intervals for streaming imports
Progressive intervals adjust dynamically based on current entity count
(not total), making them work for both known and unknown totals.

**Key Features:**
- 0-999 entities: Flush every 100 (frequent early updates for UX)
- 1K-9.9K: Flush every 1000 (balanced performance)
- 10K+: Flush every 5000 (minimal overhead ~0.3%)

**Benefits:**
- Works with known totals (file imports)
- Works with unknown totals (streaming APIs, database cursors)
- Adapts automatically as import grows
- Zero configuration required

**Implementation:**
- Replaced adaptive intervals (requires total count) with progressive
- Added interval transition logging for observability
- Enhanced documentation to highlight engineering sophistication
- Final flush with statistics reporting

**Documentation:**
- Added "Engineering Insight" section showcasing advanced approach
- Updated all interval references from "adaptive" to "progressive"
- Added comprehensive examples in streaming-imports.md

Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 17:36:27 -07:00

317 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy.js'
import { InstancePool, createInstancePool } from '../../../src/import/InstancePool.js'
describe('InstancePool', () => {
let brain: Brainy
let pool: InstancePool
beforeEach(async () => {
brain = new Brainy({ storage: { type: 'memory' } })
await brain.init()
pool = new InstancePool(brain)
})
describe('lazy initialization', () => {
it('should not create instances until requested', () => {
const stats = pool.getStats()
expect(stats.nlpCreated).toBe(false)
expect(stats.extractorCreated).toBe(false)
})
it('should create NLP instance on first access', async () => {
const nlp = await pool.getNLP()
expect(nlp).toBeDefined()
const stats = pool.getStats()
expect(stats.nlpCreated).toBe(true)
expect(stats.nlpReuses).toBe(1)
})
it('should create extractor instance on first access', () => {
const extractor = pool.getExtractor()
expect(extractor).toBeDefined()
const stats = pool.getStats()
expect(stats.extractorCreated).toBe(true)
expect(stats.extractorReuses).toBe(1)
})
})
describe('instance reuse', () => {
it('should return same NLP instance on multiple calls', async () => {
const nlp1 = await pool.getNLP()
const nlp2 = await pool.getNLP()
const nlp3 = await pool.getNLP()
expect(nlp1).toBe(nlp2)
expect(nlp2).toBe(nlp3)
const stats = pool.getStats()
expect(stats.nlpReuses).toBe(3)
})
it('should return same extractor instance on multiple calls', () => {
const extractor1 = pool.getExtractor()
const extractor2 = pool.getExtractor()
const extractor3 = pool.getExtractor()
expect(extractor1).toBe(extractor2)
expect(extractor2).toBe(extractor3)
const stats = pool.getStats()
expect(stats.extractorReuses).toBe(3)
})
it('should track reuse counts correctly', async () => {
await pool.getNLP()
await pool.getNLP()
pool.getExtractor()
pool.getExtractor()
pool.getExtractor()
const stats = pool.getStats()
expect(stats.nlpReuses).toBe(2)
expect(stats.extractorReuses).toBe(3)
})
})
describe('initialization', () => {
it('should initialize all instances with init()', async () => {
await pool.init()
expect(pool.isInitialized()).toBe(true)
const stats = pool.getStats()
expect(stats.nlpCreated).toBe(true)
expect(stats.extractorCreated).toBe(true)
expect(stats.initialized).toBe(true)
})
it('should handle concurrent init calls safely', async () => {
// Call init multiple times concurrently
const promises = [
pool.init(),
pool.init(),
pool.init()
]
await Promise.all(promises)
// Should only initialize once
expect(pool.isInitialized()).toBe(true)
})
it('should auto-initialize NLP when accessed', async () => {
const nlp = await pool.getNLP()
// NLP is lazy-initialized but extractor might not be
const stats = pool.getStats()
expect(stats.nlpCreated).toBe(true)
expect(stats.initialized).toBe(true)
})
it('should provide sync access to NLP', () => {
const nlp = pool.getNLPSync()
expect(nlp).toBeDefined()
const stats = pool.getStats()
expect(stats.nlpCreated).toBe(true)
})
})
describe('statistics', () => {
it('should track creation time', async () => {
await pool.init()
const stats = pool.getStats()
expect(stats.creationTime).toBeGreaterThanOrEqual(0)
})
it('should calculate memory saved', async () => {
// Use instances multiple times
await pool.getNLP()
await pool.getNLP()
await pool.getNLP()
pool.getExtractor()
pool.getExtractor()
const stats = pool.getStats()
expect(stats.memorySaved).toBeGreaterThan(0)
})
it('should reset statistics', async () => {
await pool.getNLP()
pool.getExtractor()
pool.resetStats()
const stats = pool.getStats()
expect(stats.nlpReuses).toBe(0)
expect(stats.extractorReuses).toBe(0)
expect(stats.creationTime).toBe(0)
})
it('should provide string representation', async () => {
await pool.init()
const str = pool.toString()
expect(str).toContain('InstancePool')
expect(str).toContain('nlp=true')
expect(str).toContain('extractor=true')
})
})
describe('memory efficiency', () => {
it('should reuse instances in loop (no memory leak)', async () => {
const initialStats = pool.getStats()
// Simulate import loop
for (let i = 0; i < 1000; i++) {
const nlp = await pool.getNLP()
const extractor = pool.getExtractor()
// All iterations should get same instances
expect(nlp).toBeDefined()
expect(extractor).toBeDefined()
}
const finalStats = pool.getStats()
expect(finalStats.nlpReuses).toBe(1000)
expect(finalStats.extractorReuses).toBe(1000)
// Should have saved ~60GB of memory (1000 iterations × ~60MB)
expect(finalStats.memorySaved).toBeGreaterThan(50 * 1024 * 1024 * 1000) // > 50GB
})
it('should handle rapid concurrent access', async () => {
// Simulate concurrent row processing
const promises = []
for (let i = 0; i < 100; i++) {
promises.push(pool.getNLP())
promises.push(Promise.resolve(pool.getExtractor()))
}
await Promise.all(promises)
const stats = pool.getStats()
expect(stats.nlpReuses).toBe(100)
expect(stats.extractorReuses).toBe(100)
})
})
describe('cleanup', () => {
it('should cleanup instances', async () => {
await pool.init()
expect(pool.isInitialized()).toBe(true)
pool.cleanup()
expect(pool.isInitialized()).toBe(false)
const stats = pool.getStats()
expect(stats.nlpCreated).toBe(false)
expect(stats.extractorCreated).toBe(false)
})
it('should allow reinitialization after cleanup', async () => {
await pool.init()
pool.cleanup()
await pool.init()
expect(pool.isInitialized()).toBe(true)
})
})
describe('factory function', () => {
it('should create pool with auto-init', async () => {
const newPool = await createInstancePool(brain, true)
expect(newPool.isInitialized()).toBe(true)
})
it('should create pool without auto-init', async () => {
const newPool = await createInstancePool(brain, false)
expect(newPool.isInitialized()).toBe(false)
})
it('should default to auto-init', async () => {
const newPool = await createInstancePool(brain)
expect(newPool.isInitialized()).toBe(true)
})
})
describe('error handling', () => {
it('should handle missing NLP instance gracefully', async () => {
const emptyPool = new InstancePool(brain)
// Should create NLP on first access
const nlp = await emptyPool.getNLP()
expect(nlp).toBeDefined()
})
it('should handle missing extractor instance gracefully', () => {
const emptyPool = new InstancePool(brain)
// Should create extractor on first access
const extractor = emptyPool.getExtractor()
expect(extractor).toBeDefined()
})
})
describe('real-world usage', () => {
it('should work with actual NLP operations', async () => {
const nlp = await pool.getNLP()
// Should be initialized and ready to use
expect(nlp).toBeDefined()
// NLP should have init method
expect(typeof nlp.init).toBe('function')
})
it('should work with actual entity extraction', async () => {
const extractor = pool.getExtractor()
// Should be ready to use
expect(extractor).toBeDefined()
// Can call extractor methods
const entities = await extractor.extract('Paris is a beautiful city', {
confidence: 0.5
})
expect(Array.isArray(entities)).toBe(true)
})
it('should handle full import workflow', async () => {
// Initialize pool
await pool.init()
// Simulate processing multiple rows
const rows = [
{ text: 'Paris is beautiful' },
{ text: 'London is historic' },
{ text: 'Tokyo is modern' }
]
for (const row of rows) {
const nlp = await pool.getNLP()
const extractor = pool.getExtractor()
// Process row - extract entities
const entities = await extractor.extract(row.text, { confidence: 0.5 })
expect(nlp).toBeDefined()
expect(extractor).toBeDefined()
expect(entities).toBeDefined()
}
// Verify instances were reused
const stats = pool.getStats()
expect(stats.nlpReuses).toBe(3)
expect(stats.extractorReuses).toBe(3)
})
})
})