Performance Optimization
Learn how to optimize NestLens for minimal performance impact on your application.
Performance Overview
NestLens is designed for minimal overhead:
- Request Tracking: ~0.5-1ms per request
- Query Tracking: ~0.1-0.5ms per query
- Memory Usage: Less than 50MB typical (with buffering)
- CPU Impact: Less than 2% in most applications
Buffer Configuration
The collector uses buffering to minimize database writes.
Default Buffer Settings
// In CollectorService
private readonly BUFFER_SIZE = 100; // Entries before flush
private readonly FLUSH_INTERVAL = 1000; // 1 second
Optimize Buffer Size
Increase buffer size for higher throughput:
// In your fork/extension
class OptimizedCollector extends CollectorService {
private readonly BUFFER_SIZE = 500; // Larger buffer
private readonly FLUSH_INTERVAL = 5000; // Flush every 5 seconds
}
Trade-offs:
- Larger Buffer: Less frequent writes, more memory usage
- Smaller Buffer: More frequent writes, less memory usage
Flush Strategy
Configure when buffered entries are written:
async collect(type: EntryType, payload: any) {
this.buffer.push({ type, payload });
// Immediate flush for critical entries
if (type === 'exception') {
await this.flush();
return;
}
// Buffer others until full
if (this.buffer.length >= this.BUFFER_SIZE) {
await this.flush();
}
}
Database Optimization
Indexing Strategy
Create indexes for common queries:
-- SQLite (default)
CREATE INDEX idx_type ON entries(type);
CREATE INDEX idx_created_at ON entries(createdAt);
CREATE INDEX idx_request_id ON entries(requestId);
CREATE INDEX idx_type_created ON entries(type, createdAt);
-- Compound index for filtered queries
CREATE INDEX idx_type_status ON entries(type, json_extract(payload, '$.statusCode'));
Connection Pooling
Use connection pooling for better performance:
// For custom storage backends
const pool = new Pool({
max: 20, // Maximum connections
min: 5, // Minimum connections
idleTimeoutMillis: 30000,
});
Batch Operations
Use batch inserts instead of individual saves:
// GOOD - Batch insert
async saveBatch(entries: Entry[]): Promise<Entry[]> {
const placeholders = entries.map(() => '(?, ?, ?, ?)').join(',');
const values = entries.flatMap(e => [e.type, JSON.stringify(e.payload), e.requestId, e.createdAt]);
await this.db.run(
`INSERT INTO entries (type, payload, requestId, createdAt) VALUES ${placeholders}`,
values
);
}
// BAD - Individual inserts
for (const entry of entries) {
await this.save(entry);
}
Pruning Optimization
Configure aggressive pruning to keep database small.
Optimized Pruning Config
NestLensModule.forRoot({
pruning: {
enabled: true,
maxAge: 6, // Keep only 6 hours
interval: 15, // Prune every 15 minutes
},
})
Type-Specific Pruning
Implement custom pruning per entry type:
class CustomPruningService extends PruningService {
async prune(): Promise<void> {
// Keep exceptions longer (24 hours)
await this.storage.pruneByType('exception', new Date(Date.now() - 24 * 60 * 60 * 1000));
// Keep requests shorter (1 hour)
await this.storage.pruneByType('request', new Date(Date.now() - 1 * 60 * 60 * 1000));
// Keep logs very short (15 minutes)
await this.storage.pruneByType('log', new Date(Date.now() - 15 * 60 * 1000));
}
}
Vacuum Database
Periodically vacuum SQLite database:
@Cron('0 2 * * *') // Daily at 2 AM
async vacuumDatabase() {
if (this.storage instanceof SqliteStorage) {
await this.storage.run('VACUUM');
this.logger.log('Database vacuumed');
}
}
Watcher Optimization
Disable Unused Watchers
Only enable watchers you need:
NestLensModule.forRoot({
watchers: {
request: true, // Essential
exception: true, // Essential
query: true, // Important
// Disable everything else
log: false,
cache: false,
event: false,
job: false,
schedule: false,
mail: false,
httpClient: false,
redis: false,
model: false,
notification: false,
view: false,
command: false,
gate: false,
batch: false,
dump: false,
},
})
Optimize Query Watcher
NestLensModule.forRoot({
watchers: {
query: {
enabled: true,
slowThreshold: 500, // Higher threshold = fewer entries
ignorePatterns: [
/^SELECT.*FROM sqlite_/, // Ignore system tables
/^PRAGMA/, // Ignore pragmas
/^EXPLAIN/, // Ignore explains
],
},
},
})
Optimize Request Watcher
NestLensModule.forRoot({
watchers: {
request: {
enabled: true,
captureBody: false, // Disable body capture
captureResponse: false, // Disable response capture
captureSession: false, // Disable session capture
maxBodySize: 0, // No body capture
ignorePaths: [
'/health',
'/metrics',
'/favicon.ico',
'/static/*',
],
},
},
})
Entry Filtering Performance
Use Efficient Filters
// GOOD - Fast checks
filter: (entry) => {
if (entry.type === 'request') {
return entry.payload.statusCode >= 400;
}
return true;
}
// BAD - Expensive operations
filter: async (entry) => {
if (entry.type === 'request') {
// Database lookup on every entry - SLOW!
const user = await db.findUser(entry.payload.userId);
return user.trackingEnabled;
}
return true;
}
Cache Filter Results
const filterCache = new Map<string, boolean>();
filter: (entry) => {
const key = `${entry.type}:${entry.payload.path}`;
if (filterCache.has(key)) {
return filterCache.get(key);
}
const shouldCollect = expensiveFilterLogic(entry);
filterCache.set(key, shouldCollect);
return shouldCollect;
}
Use Batch Filters
Batch filtering is more efficient than per-entry:
// GOOD - Process batch
filterBatch: (entries) => {
// Process all at once
return entries.filter(e => e.type !== 'log' || e.payload.level === 'error');
}
// LESS EFFICIENT - Per-entry
filter: (entry) => {
return entry.type !== 'log' || entry.payload.level === 'error';
}
Memory Management
Monitor Memory Usage
setInterval(() => {
const usage = process.memoryUsage();
if (usage.heapUsed > 500 * 1024 * 1024) { // 500MB
logger.warn('High memory usage, flushing buffers');
collector.flush();
}
}, 60000);
Limit Payload Size
filter: (entry) => {
// Truncate large payloads
if (entry.type === 'request' && entry.payload.body) {
const bodyStr = JSON.stringify(entry.payload.body);
if (bodyStr.length > 10000) { // 10KB
entry.payload.body = {
_truncated: true,
_size: bodyStr.length,
};
}
}
return true;
}
Clear Old Data Aggressively
NestLensModule.forRoot({
pruning: {
enabled: true,
maxAge: 1, // 1 hour only
interval: 10, // Prune every 10 minutes
},
})
CPU Optimization
Minimize JSON Operations
// GOOD - Avoid unnecessary parsing
async save(entry: Entry): Promise<Entry> {
const payloadStr = JSON.stringify(entry.payload);
await this.db.run(
'INSERT INTO entries (type, payload) VALUES (?, ?)',
[entry.type, payloadStr]
);
}
// BAD - Multiple JSON operations
async save(entry: Entry): Promise<Entry> {
const temp = JSON.parse(JSON.stringify(entry)); // Unnecessary
const payloadStr = JSON.stringify(temp.payload);
// ...
}
Use Async Operations
Keep operations non-blocking:
// GOOD - Async
async collect(type: EntryType, payload: any) {
this.buffer.push({ type, payload });
if (this.buffer.length >= this.BUFFER_SIZE) {
// Non-blocking flush
this.flush().catch(err => logger.error(err));
}
}
// BAD - Blocking
collect(type: EntryType, payload: any) {
this.buffer.push({ type, payload });
if (this.buffer.length >= this.BUFFER_SIZE) {
// Blocks until complete
this.flushSync();
}
}
Network Optimization
Compress Large Entries
import { gzip } from 'zlib';
import { promisify } from 'util';
const gzipAsync = promisify(gzip);
async save(entry: Entry): Promise<Entry> {
let payload = JSON.stringify(entry.payload);
// Compress if large
if (payload.length > 50000) {
const compressed = await gzipAsync(payload);
payload = compressed.toString('base64');
entry.compressed = true;
}
// Save compressed payload
await this.db.save({ ...entry, payload });
}
Batch API Requests
If using external storage:
// GOOD - Batch requests
async saveBatch(entries: Entry[]): Promise<Entry[]> {
return this.api.post('/entries/batch', { entries });
}
// BAD - Individual requests
for (const entry of entries) {
await this.api.post('/entries', entry);
}
Production Optimizations
Complete Production Config
NestLensModule.forRoot({
// Minimal watchers
watchers: {
request: {
enabled: true,
captureBody: false,
captureResponse: false,
ignorePaths: ['/health', '/metrics'],
},
exception: true,
// All others disabled
},
// Aggressive pruning
pruning: {
enabled: true,
maxAge: 1, // 1 hour
interval: 15, // Every 15 minutes
},
// Efficient filtering
filter: (entry) => {
// Only errors in production
if (entry.type === 'request') {
return entry.payload.statusCode >= 500;
}
return entry.type === 'exception';
},
})
Disable in Production
The safest optimization:
NestLensModule.forRoot({
enabled: process.env.NODE_ENV !== 'production',
})
Benchmarking
Measure NestLens Impact
// Without NestLens
const start = Date.now();
for (let i = 0; i < 1000; i++) {
await makeRequest();
}
const baseline = Date.now() - start;
// With NestLens
const startWithNestLens = Date.now();
for (let i = 0; i < 1000; i++) {
await makeRequest();
}
const withNestLens = Date.now() - startWithNestLens;
const overhead = ((withNestLens - baseline) / baseline) * 100;
console.log(`NestLens overhead: ${overhead.toFixed(2)}%`);
Load Testing
# Use artillery or ab for load testing
artillery quick --count 10 -n 100 http://localhost:3000/api/users
# Monitor performance
node --inspect index.js
Performance Monitoring
Add Metrics
@Injectable()
export class PerformanceMonitor {
private metrics = {
entriesCollected: 0,
entriesFlushed: 0,
flushDuration: [],
bufferSize: 0,
};
trackCollection() {
this.metrics.entriesCollected++;
}
trackFlush(duration: number, count: number) {
this.metrics.entriesFlushed += count;
this.metrics.flushDuration.push(duration);
}
getMetrics() {
return {
...this.metrics,
avgFlushDuration: avg(this.metrics.flushDuration),
entriesPerSecond: this.metrics.entriesCollected / uptime(),
};
}
}
Dashboard Integration
Create a metrics endpoint:
@Controller('admin')
export class MetricsController {
@Get('nestlens/metrics')
async getMetrics() {
return {
bufferSize: collector.getBufferSize(),
storageSize: await storage.getStorageStats(),
performance: performanceMonitor.getMetrics(),
};
}
}
Best Practices
1. Start with Defaults
Begin with default settings, then optimize if needed.
2. Measure Before Optimizing
Profile your application to identify actual bottlenecks.
3. Test Changes
Benchmark before and after optimization changes.
4. Monitor Production
Track NestLens impact in production metrics.
5. Disable if Needed
Don't hesitate to disable NestLens in production if performance is critical.
Troubleshooting
High Memory Usage
- Reduce buffer size
- Enable aggressive pruning
- Disable body/response capture
- Add entry filtering
Slow Response Times
- Disable unused watchers
- Use async collection only
- Optimize filter functions
- Reduce payload capture
Database Growth
- Enable pruning
- Reduce maxAge
- Filter more aggressively
- Implement type-specific retention
Next Steps
- Create Custom Watchers
- Implement Custom Storage
- Configure Entry Filtering