Files
proffesor-for-testing__agen…/docs/performance/learning-engine-optimization-strategy.md
Profa 26908cb677 chore(release): v1.8.1 - Safety & Test Quality Improvements
## Fixed

- **P0: Simulation Mode Runtime Guards** (TestExecutorAgent)
  - Requires AQE_ALLOW_SIMULATION=true environment variable
  - Prevents silent simulation in production environments
  - Throws explicit error with clear instructions

- **P2: Explicit Error Handling** (RealAgentDBAdapter)
  - Replaced silent fallback (return 0) with explicit validation
  - Fails loudly with actionable error messages
  - Detects database corruption and schema mismatches

- **P1: Test Isolation with UUID** (Integration Tests)
  - Replaced Date.now() with randomUUID() for database paths
  - Eliminates race conditions in parallel test execution
  - Uses OS temp directory for proper cleanup

## Changed

- Updated CHANGELOG.md with v1.8.1 release notes
- Updated README.md version badge and What's New section
- Updated package.json version: 1.8.0 → 1.8.1

## Documentation

- Created docs/releases/v1.8.1-RELEASE-NOTES.md (comprehensive guide)
- Created docs/releases/v1.8.1-SUMMARY.md (quick reference)
- Created docs/releases/v1.8.1-VERIFICATION.md (test verification)
- Created docs/reviews/BRUTAL-HONESTY-ISSUE-52-CODE-REVIEW.md (code review)

## Issues

- Closes #52 (Technical Debt Remediation) - 6/8 fixes completed
- Addresses #55 P0, P1, P2 priorities
- Creates #57 for remaining work (performance optimization, adapter verification)

## Impact

- Build Status: ✅ Passing (0 errors)
- Runtime Safety: ✅ Improved (guards added)
- Error Handling: ✅ Improved (explicit errors)
- Test Isolation: ✅ Improved (UUID-based)
- Breaking Changes: ❌ None (fully backward-compatible)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 11:44:44 +00:00

518 lines
14 KiB
Markdown
Raw Permalink 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.
# LearningEngine Performance Optimization Strategy
## Issue #52: O(n) Pattern Query Performance
**Date**: 2025-11-17
**Agent**: Performance Validator Subagent
**Status**: Implementation Ready
## Problem Analysis
### Critical Performance Bottleneck Identified
**Location**: `src/core/memory/SwarmMemoryManager.ts:1229-1259`
```typescript
async queryPatternsByAgent(agentId: string, minConfidence: number = 0): Promise<Pattern[]> {
const rows = await this.queryAll<any>(
`SELECT id, pattern, confidence, usage_count, metadata, ttl, created_at
FROM patterns
WHERE confidence >= ?
AND (expires_at IS NULL OR expires_at > ?)
AND (metadata LIKE ? OR metadata LIKE ?) // ⚠️ FULL TABLE SCAN
ORDER BY confidence DESC`,
[
minConfidence,
now,
`%"agent_id":"${agentId}"%`, // ⚠️ JSON SEARCH IN TEXT
`%"agentId":"${agentId}"%` // ⚠️ JSON SEARCH IN TEXT
]
);
}
```
### Performance Issues
1. **Full Table Scan**: `LIKE '%..%'` pattern cannot use indexes
2. **JSON Text Search**: Searching JSON strings in metadata column (O(n×m) complexity)
3. **Multiple Metadata Formats**: Checking both `agent_id` and `agentId` variants
4. **No Agent-Specific Index**: Pattern table has no direct agent_id column
5. **Called Frequently**: Invoked on every pattern update in LearningEngine (lines 535, 599-611)
### Current Performance
- **Complexity**: O(n×m) where n = pattern count, m = average metadata size
- **Impact**: Degrades linearly with pattern database growth
- **Worst Case**: 10,000 patterns × 500 char metadata = 5M character scans per query
## Optimization Strategy
### Phase 1: Database Schema Migration (CRITICAL)
**Add dedicated agent_id column**
```sql
-- Migration: Add agent_id column
ALTER TABLE patterns ADD COLUMN agent_id TEXT;
-- Backfill from existing metadata
UPDATE patterns
SET agent_id = json_extract(metadata, '$.agent_id')
WHERE agent_id IS NULL AND json_valid(metadata);
-- Create composite index for O(log n) lookups
CREATE INDEX idx_patterns_agent_confidence
ON patterns(agent_id, confidence DESC, expires_at);
-- Single-column fallback index
CREATE INDEX idx_patterns_agent
ON patterns(agent_id);
```
**Performance Improvement**: O(n×m) → O(log n)
### Phase 2: Query Optimization
**Before (O(n×m))**:
```sql
WHERE metadata LIKE '%"agent_id":"xyz"%'
```
**After (O(log n))**:
```sql
WHERE agent_id = 'xyz'
AND confidence >= 0.5
AND (expires_at IS NULL OR expires_at > ?)
```
**Index Usage**:
- `idx_patterns_agent_confidence` covers entire query
- No table scan required
- Sorted by confidence (no ORDER BY overhead)
### Phase 3: In-Memory Caching
**LRU Cache for Hot Patterns**
```typescript
class PatternCache {
private cache = new Map<string, {patterns: Pattern[], timestamp: number}>();
private maxSize = 100;
private ttl = 60000; // 60 seconds
get(key: string): Pattern[] | null {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() - entry.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
// Move to end (LRU)
this.cache.delete(key);
this.cache.set(key, entry);
return entry.patterns;
}
set(key: string, patterns: Pattern[]): void {
// Evict oldest if at capacity
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, {
patterns,
timestamp: Date.now()
});
}
}
```
**Cache Key Format**: `patterns:${agentId}:${minConfidence}`
### Phase 4: Batch Operations
**Optimize updatePatterns() in LearningEngine**
```typescript
// Before: Query on every pattern update
const agentPatterns = await this.memoryStore.queryPatternsByAgent(this.agentId, 0);
// After: Batch queries
private patternUpdateQueue: TaskExperience[] = [];
private async flushPatternUpdates(): Promise<void> {
if (this.patternUpdateQueue.length === 0) return;
// Single query for all patterns
const agentPatterns = await this.memoryStore.queryPatternsByAgent(this.agentId, 0);
const patternMap = new Map(agentPatterns.map(p => [p.pattern, p]));
// Batch process all updates
for (const exp of this.patternUpdateQueue) {
const key = `${exp.taskType}:${exp.action.strategy}`;
const existing = patternMap.get(key);
// ... update logic
}
this.patternUpdateQueue = [];
}
```
## Implementation Plan
### Step 1: Schema Migration Script
**File**: `scripts/migrations/add-pattern-agent-id.ts`
```typescript
import { SwarmMemoryManager } from '../../src/core/memory/SwarmMemoryManager';
export async function migratePatternSchema() {
const manager = new SwarmMemoryManager({ dbPath: '.agentic-qe/agentdb.db' });
await manager.initialize();
console.log('Adding agent_id column...');
await manager.run(`ALTER TABLE patterns ADD COLUMN agent_id TEXT`);
console.log('Backfilling agent_id from metadata...');
await manager.run(`
UPDATE patterns
SET agent_id = json_extract(metadata, '$.agent_id')
WHERE agent_id IS NULL AND json_valid(metadata)
`);
console.log('Creating composite index...');
await manager.run(`
CREATE INDEX IF NOT EXISTS idx_patterns_agent_confidence
ON patterns(agent_id, confidence DESC, expires_at)
`);
console.log('Creating agent index...');
await manager.run(`
CREATE INDEX IF NOT EXISTS idx_patterns_agent
ON patterns(agent_id)
`);
console.log('Migration complete!');
await manager.close();
}
```
### Step 2: Update storePattern Method
**File**: `src/core/memory/SwarmMemoryManager.ts`
```typescript
async storePattern(pattern: Pattern): Promise<string> {
// Extract agent_id from metadata
const agentId = pattern.metadata?.agent_id || pattern.metadata?.agentId || null;
await this.run(
`INSERT OR REPLACE INTO patterns
(id, pattern, confidence, usage_count, metadata, ttl, expires_at, created_at, agent_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
id,
pattern.pattern,
pattern.confidence,
pattern.usageCount || 0,
metadata,
pattern.ttl || this.TTL_POLICY.patterns,
expiresAt,
now,
agentId // Add agent_id column
]
);
}
```
### Step 3: Optimize queryPatternsByAgent
**File**: `src/core/memory/SwarmMemoryManager.ts`
```typescript
async queryPatternsByAgent(agentId: string, minConfidence: number = 0): Promise<Pattern[]> {
if (!this.db) {
throw new Error('Memory manager not initialized');
}
const now = Date.now();
// O(log n) query using agent_id index
const rows = await this.queryAll<any>(
`SELECT id, pattern, confidence, usage_count, metadata, ttl, created_at, agent_id
FROM patterns
WHERE agent_id = ?
AND confidence >= ?
AND (expires_at IS NULL OR expires_at > ?)
ORDER BY confidence DESC`,
[agentId, minConfidence, now]
);
return rows.map((row: any) => ({
id: row.id,
pattern: row.pattern,
confidence: row.confidence,
usageCount: row.usage_count,
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
ttl: row.ttl,
createdAt: row.created_at
}));
}
```
### Step 4: Add Pattern Cache
**File**: `src/core/memory/PatternCache.ts` (new)
```typescript
export class PatternCache {
private cache = new Map<string, CacheEntry>();
private maxSize: number;
private ttl: number;
constructor(options: { maxSize?: number; ttl?: number } = {}) {
this.maxSize = options.maxSize || 100;
this.ttl = options.ttl || 60000; // 1 minute
}
get(key: string): Pattern[] | null {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() - entry.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
// LRU: move to end
this.cache.delete(key);
this.cache.set(key, entry);
return entry.patterns;
}
set(key: string, patterns: Pattern[]): void {
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, {
patterns,
timestamp: Date.now()
});
}
invalidate(agentId: string): void {
for (const key of this.cache.keys()) {
if (key.startsWith(`patterns:${agentId}:`)) {
this.cache.delete(key);
}
}
}
clear(): void {
this.cache.clear();
}
getStats(): CacheStats {
return {
size: this.cache.size,
maxSize: this.maxSize,
hitRate: 0, // TODO: track hits/misses
ttl: this.ttl
};
}
}
interface CacheEntry {
patterns: Pattern[];
timestamp: number;
}
interface CacheStats {
size: number;
maxSize: number;
hitRate: number;
ttl: number;
}
```
### Step 5: Integrate Cache in SwarmMemoryManager
```typescript
export class SwarmMemoryManager {
private patternCache: PatternCache;
constructor(config: MemoryConfig = {}) {
// ...
this.patternCache = new PatternCache({
maxSize: config.cacheSize || 100,
ttl: config.cacheTTL || 60000
});
}
async queryPatternsByAgent(agentId: string, minConfidence: number = 0): Promise<Pattern[]> {
// Check cache first
const cacheKey = `patterns:${agentId}:${minConfidence}`;
const cached = this.patternCache.get(cacheKey);
if (cached) {
return cached;
}
// Query database
const patterns = await this.queryPatternsByAgentUncached(agentId, minConfidence);
// Cache result
this.patternCache.set(cacheKey, patterns);
return patterns;
}
async storePattern(pattern: Pattern): Promise<string> {
const id = await this.storePatternUncached(pattern);
// Invalidate cache for this agent
const agentId = pattern.metadata?.agent_id || pattern.metadata?.agentId;
if (agentId) {
this.patternCache.invalidate(agentId);
}
return id;
}
}
```
## Performance Benchmarks
### Test Script
**File**: `tests/performance/pattern-query-benchmark.ts`
```typescript
import { SwarmMemoryManager } from '../../src/core/memory/SwarmMemoryManager';
async function benchmark() {
const manager = new SwarmMemoryManager();
await manager.initialize();
// Seed database with patterns
const agentIds = ['agent-1', 'agent-2', 'agent-3'];
const patternCounts = [100, 1000, 10000, 50000];
for (const count of patternCounts) {
console.log(`\n=== Testing with ${count} patterns ===`);
// Seed
await seedPatterns(manager, agentIds, count);
// Benchmark queries
for (const agentId of agentIds) {
const start = performance.now();
const patterns = await manager.queryPatternsByAgent(agentId);
const duration = performance.now() - start;
console.log(`Query ${agentId}: ${duration.toFixed(2)}ms (${patterns.length} patterns)`);
}
// Clear cache and test again
console.log('\n--- Without cache ---');
manager.clearCache();
for (const agentId of agentIds) {
const start = performance.now();
const patterns = await manager.queryPatternsByAgent(agentId);
const duration = performance.now() - start;
console.log(`Query ${agentId}: ${duration.toFixed(2)}ms (${patterns.length} patterns)`);
}
}
}
```
### Expected Results
| Pattern Count | Before (ms) | After (ms) | Improvement |
|--------------|-------------|------------|-------------|
| 100 | 15 | 0.5 | 30× |
| 1,000 | 85 | 1.2 | 70× |
| 10,000 | 650 | 3.5 | 185× |
| 50,000 | 3,200 | 8.0 | 400× |
**With Cache (2nd query)**:
- 100 patterns: 0.05ms
- 10,000 patterns: 0.05ms
- 50,000 patterns: 0.05ms
## Rollout Plan
### Phase 1: Migration (Zero Downtime)
1. Run migration script on existing database
2. Verify indexes created successfully
3. Backfill agent_id column
### Phase 2: Code Update (Backward Compatible)
1. Update `storePattern` to include agent_id
2. Update `queryPatternsByAgent` to use new column
3. Keep old LIKE fallback for unmigrated data
### Phase 3: Cache Integration
1. Add PatternCache class
2. Integrate into SwarmMemoryManager
3. Add cache invalidation on pattern updates
### Phase 4: Monitoring
1. Add performance metrics logging
2. Track cache hit rates
3. Monitor query latencies
## Verification Checklist
- [x] Full table scan identified (metadata LIKE query)
- [x] Schema migration designed (agent_id column)
- [x] Composite index planned (agent_id, confidence, expires_at)
- [x] Cache strategy defined (LRU with 60s TTL)
- [x] Benchmark tests designed
- [ ] Migration script implemented
- [ ] Optimized queries implemented
- [ ] Cache integrated
- [ ] Benchmarks run
- [ ] Performance verified (>100× improvement)
## Risk Mitigation
### Backward Compatibility
- Migration preserves existing metadata
- Old queries still work (LIKE fallback)
- agent_id column nullable (doesn't break existing code)
### Data Integrity
- Backfill from metadata ensures consistency
- UNIQUE constraint on pattern prevents duplicates
- TTL/expiration logic unchanged
### Performance Regression
- Indexes improve all pattern queries
- Cache is optional (can be disabled)
- LRU eviction prevents memory bloat
## Success Metrics
1. **Query Performance**: <5ms for 10,000 patterns (vs 650ms)
2. **Cache Hit Rate**: >80% for repeated queries
3. **Memory Usage**: <10MB for 100 cached entries
4. **Zero Regressions**: All existing tests pass
## References
- Issue: #52
- Files Modified:
- `src/core/memory/SwarmMemoryManager.ts`
- `src/core/memory/PatternCache.ts` (new)
- `src/learning/LearningEngine.ts` (cache integration)
- Migration: `scripts/migrations/add-pattern-agent-id.ts`
- Benchmarks: `tests/performance/pattern-query-benchmark.ts`