12 KiB
12 KiB
trigger
| trigger |
|---|
| always_on |
Performance Optimization Patterns
When to Use This Skill
Use this skill when:
- Optimizing database queries
- Detecting and fixing memory leaks
- Profiling application performance
- Optimizing connection pooling
- Improving caching strategies
- Identifyings N+1 query problems
- Optimizing batch operations
- Improving application startup time
- Reducing memory usage
- Optimizing API response times
Core Concepts
Performance Metrics
- Response Time: Time to process request
- Throughput: Requests processed per second
- Memory Usage: Memory consumption
- CPU Usage: CPU utilization
- Database Query Time: Query execution time
Optimization Areas
- Database queries
- Memory management
- Connection pooling
- Caching
- Batch processing
- Lazy loading
Database Query Optimization
Query Analysis
// src/core/db/query-analyzer.ts
// EN: Query performance analyzer
// VI: Phân tích hiệu suất query
import { PrismaClient } from '@prisma/client';
import { logger } from '@goodgo/logger';
export class QueryAnalyzer {
constructor(private prisma: PrismaClient) {}
/**
* EN: Analyze query performance
* VI: Phân tích hiệu suất query
*/
async analyzeQuery(query: string): Promise<any> {
const result = await this.prisma.$queryRawUnsafe(`EXPLAIN ANALYZE ${query}`);
logger.info('Query analysis', { query, result });
return result;
}
/**
* EN: Check for missing indexes
* VI: Kiểm tra indexes bị thiếu
*/
async checkIndexes(tableName: string): Promise<any> {
const indexes = await this.prisma.$queryRawUnsafe(`
SELECT * FROM pg_indexes WHERE tablename = '${tableName}'
`);
return indexes;
}
}
N+1 Query Prevention
// EN: Bad: N+1 queries
// VI: Xấu: N+1 queries
async function getUsersWithOrdersBad(): Promise<User[]> {
const users = await userRepository.findAll();
for (const user of users) {
user.orders = await orderRepository.findByUserId(user.id); // N+1!
}
return users;
}
// EN: Good: Use include/join
// VI: Tốt: Sử dụng include/join
async function getUsersWithOrdersGood(): Promise<User[]> {
return await userRepository.findAll({
include: {
orders: true, // EN: Single query with join / VI: Single query với join
},
});
}
Batch Operations
// src/core/db/batch-operations.ts
// EN: Batch database operations
// VI: Batch database operations
export class BatchOperations {
/**
* EN: Batch create operations
* VI: Batch create operations
*/
async batchCreate<T>(items: T[], batchSize: number = 100): Promise<T[]> {
const results: T[] = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await prisma.$transaction(
batch.map((item) => prisma.item.create({ data: item }))
);
results.push(...batchResults);
}
return results;
}
/**
* EN: Batch update operations
* VI: Batch update operations
*/
async batchUpdate(
updates: Array<{ id: string; data: any }>,
batchSize: number = 100
): Promise<void> {
for (let i = 0; i < updates.length; i += batchSize) {
const batch = updates.slice(i, i + batchSize);
await prisma.$transaction(
batch.map(({ id, data }) => prisma.item.update({ where: { id }, data }))
);
}
}
}
Memory Leak Detection
Memory Profiling
// src/core/performance/memory-profiler.ts
// EN: Memory leak detection
// VI: Phát hiện memory leak
import { performance } from 'perf_hooks';
import { logger } from '@goodgo/logger';
export class MemoryProfiler {
private baseline?: NodeJS.MemoryUsage;
private interval?: NodeJS.Timeout;
/**
* EN: Start memory profiling
* VI: Bắt đầu memory profiling
*/
start(): void {
this.baseline = process.memoryUsage();
this.interval = setInterval(() => {
const current = process.memoryUsage();
const delta = {
heapUsed: current.heapUsed - (this.baseline?.heapUsed || 0),
heapTotal: current.heapTotal - (this.baseline?.heapTotal || 0),
external: current.external - (this.baseline?.external || 0),
};
logger.info('Memory usage', {
current: {
heapUsed: `${(current.heapUsed / 1024 / 1024).toFixed(2)} MB`,
heapTotal: `${(current.heapTotal / 1024 / 1024).toFixed(2)} MB`,
},
delta: {
heapUsed: `${(delta.heapUsed / 1024 / 1024).toFixed(2)} MB`,
},
});
// EN: Alert if memory growth is excessive
// VI: Cảnh báo nếu memory tăng quá mức
if (delta.heapUsed > 100 * 1024 * 1024) {
// 100MB growth
logger.warn('Potential memory leak detected', { delta });
}
}, 60000); // EN: Check every minute / VI: Kiểm tra mỗi phút
}
stop(): void {
if (this.interval) {
clearInterval(this.interval);
}
}
/**
* EN: Force garbage collection (if --expose-gc flag is set)
* VI: Ép garbage collection (nếu --expose-gc flag được set)
*/
forceGC(): void {
if (global.gc) {
global.gc();
}
}
}
Connection Pooling
Database Connection Pool
// src/config/database.config.ts
// EN: Optimize Prisma connection pool
// VI: Tối ưu Prisma connection pool
export const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
});
// EN: Connection pool configuration in DATABASE_URL
// VI: Cấu hình connection pool trong DATABASE_URL
// DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=20&pool_timeout=10"
HTTP Connection Pool
// src/core/clients/http-pool.config.ts
// EN: HTTP connection pooling with keep-alive
// VI: HTTP connection pooling với keep-alive
import axios from 'axios';
import https from 'https';
const httpAgent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
});
export const httpClient = axios.create({
httpAgent,
httpsAgent: httpAgent,
timeout: 30000,
});
Performance Monitoring
// src/core/performance/performance-monitor.ts
// EN: Performance monitoring middleware
// VI: Middleware giám sát hiệu suất
import { Request, Response, NextFunction } from 'express';
import { performance } from 'perf_hooks';
import { logger } from '@goodgo/logger';
export function performanceMonitor(req: Request, res: Response, next: NextFunction) {
const start = performance.now();
const startMemory = process.memoryUsage();
res.on('finish', () => {
const duration = performance.now() - start;
const endMemory = process.memoryUsage();
logger.info('Request performance', {
method: req.method,
path: req.path,
status: res.statusCode,
duration: `${duration.toFixed(2)}ms`,
memoryDelta: `${((endMemory.heapUsed - startMemory.heapUsed) / 1024 / 1024).toFixed(2)} MB`,
});
// EN: Alert on slow requests
// VI: Cảnh báo trên requests chậm
if (duration > 1000) {
logger.warn('Slow request detected', {
path: req.path,
duration: `${duration.toFixed(2)}ms`,
});
}
});
next();
}
Caching Optimization
// src/core/cache/cache-optimizer.ts
// EN: Cache optimization strategies
// VI: Chiến lược tối ưu cache
export class CacheOptimizer {
/**
* EN: Preload frequently accessed data
* VI: Tải trước dữ liệu thường truy cập
*/
async preloadHotData(keys: string[]): Promise<void> {
for (const key of keys) {
// EN: Fetch and cache data
// VI: Fetch và cache dữ liệu
await cacheService.getOrSet(key, async () => {
return await this.fetchData(key);
}, 3600);
}
}
/**
* EN: Batch cache operations
* VI: Batch cache operations
*/
async batchGet(keys: string[]): Promise<Map<string, any>> {
const results = new Map<string, any>();
const missing: string[] = [];
// EN: Check cache for all keys
// VI: Kiểm tra cache cho tất cả keys
for (const key of keys) {
const cached = await cacheService.get(key);
if (cached) {
results.set(key, cached);
} else {
missing.push(key);
}
}
// EN: Fetch missing data
// VI: Fetch dữ liệu thiếu
if (missing.length > 0) {
const data = await this.fetchBatch(missing);
for (const [key, value] of Object.entries(data)) {
results.set(key, value);
await cacheService.set(key, value, 3600);
}
}
return results;
}
}
Best Practices
- Database: Use indexes, avoid N+1 queries, use batch operations
- Memory: Monitor memory usage, detect leaks, use streaming for large data
- Caching: Cache frequently accessed data, use appropriate TTLs
- Connection Pooling: Configure pool sizes appropriately
- Profiling: Profile regularly to identify bottlenecks
- Monitoring: Monitor performance metrics continuously
Common Mistakes
-
N+1 Queries: Fetching related data in loops
// ❌ BAD: N+1 for (const user of users) { user.orders = await orderRepo.findByUserId(user.id); } // ✅ GOOD: Use include const users = await userRepo.findAll({ include: { orders: true } }); -
No Connection Pooling: Creating new connections per request
// ❌ BAD: No pooling const client = new PrismaClient(); // per request // ✅ GOOD: Reuse singleton import { prisma } from './db'; // shared instance -
Missing Indexes: Slow queries on frequently searched columns
// ✅ Add indexes in schema @@index([createdAt]) @@index([userId, status]) -
Unbounded Queries: Fetching all data without limits
// ❌ BAD const all = await repo.findAll(); // ✅ GOOD const page = await repo.findAll({ take: 100, skip: offset });
Quick Reference
| Metric | Target | Alert Threshold |
|---|---|---|
| Response Time (p95) | <200ms | >500ms |
| Memory Usage | <70% | >85% |
| CPU Usage | <60% | >80% |
| Query Time | <50ms | >200ms |
Profiling Commands:
# CPU profiling
node --prof app.js
node --prof-process isolate-*.log > profile.txt
# Memory snapshot
node --inspect app.js # Use Chrome DevTools
# Clinic.js
npx clinic doctor -- node app.js
npx clinic flame -- node app.js
Prisma Query Optimization:
// Select only needed fields
await prisma.user.findMany({ select: { id: true, email: true } });
// Use include for relations
await prisma.user.findMany({ include: { posts: true } });
// Batch operations
await prisma.$transaction([...operations]);
// Raw query for complex cases
await prisma.$queryRaw`SELECT ... FROM ...`;
Connection Pool Config:
# In DATABASE_URL
?connection_limit=20&pool_timeout=10
Performance Checklist:
- Add database indexes for frequent queries
- Use pagination for list endpoints
- Implement caching for hot data
- Enable connection pooling
- Profile and monitor regularly
- Use batch operations for bulk updates
Resources
- Node.js Performance
- Prisma Performance
- Caching Patterns - Caching strategies
- Observability & Monitoring - Monitoring patterns
- Database & Prisma - Database patterns