458 lines
12 KiB
Markdown
458 lines
12 KiB
Markdown
---
|
|
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
|
|
|
|
1. **Response Time**: Time to process request
|
|
2. **Throughput**: Requests processed per second
|
|
3. **Memory Usage**: Memory consumption
|
|
4. **CPU Usage**: CPU utilization
|
|
5. **Database Query Time**: Query execution time
|
|
|
|
### Optimization Areas
|
|
|
|
- Database queries
|
|
- Memory management
|
|
- Connection pooling
|
|
- Caching
|
|
- Batch processing
|
|
- Lazy loading
|
|
|
|
## Database Query Optimization
|
|
|
|
### Query Analysis
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
1. **Database**: Use indexes, avoid N+1 queries, use batch operations
|
|
2. **Memory**: Monitor memory usage, detect leaks, use streaming for large data
|
|
3. **Caching**: Cache frequently accessed data, use appropriate TTLs
|
|
4. **Connection Pooling**: Configure pool sizes appropriately
|
|
5. **Profiling**: Profile regularly to identify bottlenecks
|
|
6. **Monitoring**: Monitor performance metrics continuously
|
|
|
|
## Common Mistakes
|
|
|
|
1. **N+1 Queries**: Fetching related data in loops
|
|
```typescript
|
|
// ❌ 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 } });
|
|
```
|
|
|
|
2. **No Connection Pooling**: Creating new connections per request
|
|
```typescript
|
|
// ❌ BAD: No pooling
|
|
const client = new PrismaClient(); // per request
|
|
|
|
// ✅ GOOD: Reuse singleton
|
|
import { prisma } from './db'; // shared instance
|
|
```
|
|
|
|
3. **Missing Indexes**: Slow queries on frequently searched columns
|
|
```prisma
|
|
// ✅ Add indexes in schema
|
|
@@index([createdAt])
|
|
@@index([userId, status])
|
|
```
|
|
|
|
4. **Unbounded Queries**: Fetching all data without limits
|
|
```typescript
|
|
// ❌ 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:**
|
|
```bash
|
|
# 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:**
|
|
```typescript
|
|
// 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:**
|
|
```bash
|
|
# 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](https://nodejs.org/en/docs/guides/simple-profiling/)
|
|
- [Prisma Performance](https://www.prisma.io/docs/guides/performance-and-optimization)
|
|
- [Caching Patterns](../caching-patterns/SKILL.md) - Caching strategies
|
|
- [Observability & Monitoring](../observability-monitoring/SKILL.md) - Monitoring patterns
|
|
- [Database & Prisma](../database-prisma/SKILL.md) - Database patterns
|