Files
pos-system/.agent/rules/performance-optimization.md

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

  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

// 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

  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

    // ❌ 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

    // ❌ 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

    // ✅ Add indexes in schema
    @@index([createdAt])
    @@index([userId, status])
    
  4. 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