Back to list
sgcarstrends

performance-profiling

by sgcarstrends

Monorepo for SG Cars Trends backend services

14🍴 1📅 Jan 23, 2026

SKILL.md


name: performance-profiling description: Profile application performance, identify bottlenecks, and optimize slow operations. Use when investigating performance issues, optimizing response times, or improving resource usage. allowed-tools: Read, Edit, Write, Bash, Grep

Performance Profiling Skill

This skill helps you profile and optimize application performance across the stack.

When to Use This Skill

  • Investigating slow requests
  • Optimizing API response times
  • Reducing Lambda cold starts
  • Improving database query performance
  • Optimizing frontend rendering
  • Reducing bundle size
  • Improving Time to First Byte (TTFB)

Performance Metrics

Key Performance Indicators

Backend (API):

  • Response time: < 500ms (p95)
  • Cold start: < 2s
  • Memory usage: < 512MB
  • Error rate: < 1%

Frontend (Web):

  • LCP: < 2.5s
  • FID: < 100ms
  • CLS: < 0.1
  • TTFB: < 600ms
  • FCP: < 1.8s

Database:

  • Query time: < 100ms
  • Connection pool: 80% max
  • Cache hit rate: > 80%

Lambda Performance Profiling

CloudWatch Metrics

# Get Lambda duration metrics
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Duration \
  --dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Average,Maximum,Minimum

# Get memory usage
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name MemoryUtilization \
  --dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Average,Maximum

# Get invocation count
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Invocations \
  --dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Sum

Performance Logging

// apps/api/src/middleware/performance.ts
import { Context, Next } from "hono";
import { log } from "@sgcarstrends/utils/logger";

export const performanceMiddleware = async (c: Context, next: Next) => {
  const start = performance.now();
  const memoryBefore = process.memoryUsage();

  await next();

  const duration = performance.now() - start;
  const memoryAfter = process.memoryUsage();

  log.info("Request completed", {
    method: c.req.method,
    path: c.req.path,
    status: c.res.status,
    duration: Math.round(duration),
    memory: {
      heapUsed: Math.round((memoryAfter.heapUsed - memoryBefore.heapUsed) / 1024 / 1024),
      external: Math.round((memoryAfter.external - memoryBefore.external) / 1024 / 1024),
    },
  });

  // Warn on slow requests
  if (duration > 1000) {
    log.warn("Slow request detected", {
      path: c.req.path,
      duration: Math.round(duration),
    });
  }

  c.header("X-Response-Time", `${Math.round(duration)}ms`);
};

Database Query Profiling

Query Timing

// packages/database/src/profiler.ts
import { performance } from "perf_hooks";
import { log } from "@sgcarstrends/utils/logger";

export const profileQuery = async <T>(
  name: string,
  query: () => Promise<T>
): Promise<T> => {
  const start = performance.now();

  try {
    const result = await query();
    const duration = performance.now() - start;

    log.info("Query executed", {
      query: name,
      duration: Math.round(duration),
    });

    if (duration > 100) {
      log.warn("Slow query detected", {
        query: name,
        duration: Math.round(duration),
      });
    }

    return result;
  } catch (error) {
    const duration = performance.now() - start;
    log.error("Query failed", error as Error, {
      query: name,
      duration: Math.round(duration),
    });
    throw error;
  }
};

// Usage
const cars = await profileQuery("cars.findMany", () =>
  db.query.cars.findMany({ limit: 100 })
);

Query Analysis

-- PostgreSQL: Enable query logging
ALTER DATABASE sgcarstrends SET log_statement = 'all';
ALTER DATABASE sgcarstrends SET log_duration = on;
ALTER DATABASE sgcarstrends SET log_min_duration_statement = 100; -- Log queries > 100ms

-- Analyze slow queries
SELECT
  query,
  calls,
  total_time,
  mean_time,
  max_time
FROM pg_stat_statements
WHERE mean_time > 100
ORDER BY mean_time DESC
LIMIT 20;

-- Check query plan
EXPLAIN ANALYZE
SELECT * FROM cars
WHERE make = 'Toyota'
ORDER BY month DESC
LIMIT 100;

API Profiling

Node.js Built-in Profiler

# Profile API locally
NODE_OPTIONS="--prof" pnpm -F @sgcarstrends/api dev

# Generate profile after running
node --prof-process isolate-*.log > profile.txt

# Analyze profile
cat profile.txt | head -100

Clinic.js

# Install clinic
pnpm add -g clinic

# Profile with Clinic Doctor
clinic doctor -- node apps/api/dist/index.js

# Profile with Clinic Flame (flamegraph)
clinic flame -- node apps/api/dist/index.js

# Profile with Clinic Bubbleprof (async operations)
clinic bubbleprof -- node apps/api/dist/index.js

Custom Profiling

// apps/api/src/profiler.ts
interface PerformanceEntry {
  name: string;
  duration: number;
  timestamp: Date;
}

class Profiler {
  private entries: PerformanceEntry[] = [];

  start(name: string): () => void {
    const startTime = performance.now();

    return () => {
      const duration = performance.now() - startTime;
      this.entries.push({
        name,
        duration,
        timestamp: new Date(),
      });

      if (duration > 100) {
        log.warn("Slow operation", {
          operation: name,
          duration: Math.round(duration),
        });
      }
    };
  }

  getStats() {
    const grouped = this.entries.reduce((acc, entry) => {
      if (!acc[entry.name]) {
        acc[entry.name] = {
          count: 0,
          total: 0,
          min: Infinity,
          max: 0,
        };
      }

      const stats = acc[entry.name];
      stats.count++;
      stats.total += entry.duration;
      stats.min = Math.min(stats.min, entry.duration);
      stats.max = Math.max(stats.max, entry.duration);

      return acc;
    }, {} as Record<string, { count: number; total: number; min: number; max: number }>);

    return Object.entries(grouped).map(([name, stats]) => ({
      name,
      count: stats.count,
      avg: stats.total / stats.count,
      min: stats.min,
      max: stats.max,
    }));
  }

  reset() {
    this.entries = [];
  }
}

export const profiler = new Profiler();

// Usage
const end = profiler.start("fetchCarData");
const data = await fetchCarData();
end();

Frontend Performance

React Profiler

// apps/web/src/components/ProfilerWrapper.tsx
import { Profiler, ProfilerOnRenderCallback } from "react";

const onRender: ProfilerOnRenderCallback = (
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) => {
  console.log({
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime,
  });

  // Log slow renders
  if (actualDuration > 16) {  // > 1 frame at 60fps
    console.warn("Slow render detected", {
      component: id,
      duration: actualDuration,
    });
  }
};

export function ProfilerWrapper({ children }: { children: React.ReactNode }) {
  if (process.env.NODE_ENV === "development") {
    return (
      <Profiler id="App" onRender={onRender}>
        {children}
      </Profiler>
    );
  }

  return <>{children}</>;
}

Chrome DevTools Performance

# 1. Open Chrome DevTools (F12)
# 2. Go to Performance tab
# 3. Click Record
# 4. Perform actions to profile
# 5. Stop recording
# 6. Analyze:
#    - FCP, LCP, CLS
#    - Long tasks
#    - Layout shifts
#    - JavaScript execution time

Lighthouse CI

# .github/workflows/lighthouse.yml
name: Lighthouse CI

on: [pull_request]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v10
        with:
          urls: |
            https://staging.sgcarstrends.com
            https://staging.sgcarstrends.com/cars
            https://staging.sgcarstrends.com/coe
          uploadArtifacts: true
          temporaryPublicStorage: true

Memory Profiling

Node.js Heap Snapshot

// apps/api/src/debug.ts
import { writeHeapSnapshot } from "v8";
import { log } from "@sgcarstrends/utils/logger";

export const captureHeapSnapshot = () => {
  const filename = `heap-${Date.now()}.heapsnapshot`;
  writeHeapSnapshot(filename);
  log.info("Heap snapshot captured", { filename });
  return filename;
};

// Usage: Call on high memory usage
if (process.memoryUsage().heapUsed > 400 * 1024 * 1024) {  // > 400MB
  captureHeapSnapshot();
}

Memory Leak Detection

// apps/api/src/memory-monitor.ts
import { log } from "@sgcarstrends/utils/logger";

class MemoryMonitor {
  private baseline: NodeJS.MemoryUsage | null = null;

  start() {
    this.baseline = process.memoryUsage();
    log.info("Memory monitoring started", {
      baseline: this.formatMemory(this.baseline),
    });
  }

  check() {
    const current = process.memoryUsage();
    if (!this.baseline) return;

    const diff = {
      heapUsed: current.heapUsed - this.baseline.heapUsed,
      external: current.external - this.baseline.external,
      rss: current.rss - this.baseline.rss,
    };

    log.info("Memory usage", {
      current: this.formatMemory(current),
      diff: this.formatMemory(diff),
    });

    // Warn on memory growth
    if (diff.heapUsed > 100 * 1024 * 1024) {  // > 100MB growth
      log.warn("High memory growth detected", {
        growth: Math.round(diff.heapUsed / 1024 / 1024),
      });
    }
  }

  private formatMemory(mem: Partial<NodeJS.MemoryUsage>) {
    return Object.entries(mem).reduce((acc, [key, value]) => {
      acc[key] = `${Math.round((value || 0) / 1024 / 1024)}MB`;
      return acc;
    }, {} as Record<string, string>);
  }
}

export const memoryMonitor = new MemoryMonitor();

Performance Optimization

Lambda Optimization

// infra/api.ts
export function API({ stack }: StackContext) {
  const api = new Function(stack, "api", {
    handler: "apps/api/src/index.handler",
    runtime: "nodejs20.x",
    architecture: "arm64",  // Graviton2 for better performance
    memory: "1024 MB",  // More memory = faster CPU
    timeout: "30 seconds",
    environment: {
      NODE_ENV: "production",
      LOG_LEVEL: "info",
    },
    nodejs: {
      esbuild: {
        minify: true,
        bundle: true,
        target: "node20",
        sourcemap: true,
        external: ["@aws-sdk/*"],  // Don't bundle AWS SDK
      },
    },
  });
}

Caching Strategy

// apps/api/src/cache.ts
import { redis } from "@sgcarstrends/utils";

export const withCache = async <T>(
  key: string,
  ttl: number,
  fn: () => Promise<T>
): Promise<T> => {
  const start = performance.now();

  // Check cache
  const cached = await redis.get(key);
  if (cached) {
    const duration = performance.now() - start;
    log.info("Cache hit", {
      key,
      duration: Math.round(duration),
    });
    return JSON.parse(cached as string);
  }

  // Cache miss - execute function
  const result = await fn();
  await redis.set(key, JSON.stringify(result), { ex: ttl });

  const duration = performance.now() - start;
  log.info("Cache miss", {
    key,
    duration: Math.round(duration),
  });

  return result;
};

// Usage
const cars = await withCache("cars:all", 3600, async () => {
  return await db.query.cars.findMany();
});

Performance Testing

Load Testing with k6

// load-test.js
import http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  stages: [
    { duration: "2m", target: 100 },  // Ramp up to 100 users
    { duration: "5m", target: 100 },  // Stay at 100 users
    { duration: "2m", target: 200 },  // Ramp up to 200 users
    { duration: "5m", target: 200 },  // Stay at 200 users
    { duration: "2m", target: 0 },    // Ramp down to 0 users
  ],
  thresholds: {
    http_req_duration: ["p(95)<500"],  // 95% of requests < 500ms
    http_req_failed: ["rate<0.01"],    // < 1% failures
  },
};

export default function () {
  const res = http.get("https://api.sgcarstrends.com/api/v1/cars/makes");

  check(res, {
    "status is 200": (r) => r.status === 200,
    "response time < 500ms": (r) => r.timings.duration < 500,
  });

  sleep(1);
}
# Run load test
k6 run load-test.js

# Run with more virtual users
k6 run --vus 500 --duration 5m load-test.js

Performance Dashboard

CloudWatch Dashboard

// infra/dashboard.ts
import { aws_cloudwatch as cloudwatch } from "aws-cdk-lib";

export function PerformanceDashboard({ stack }: StackContext) {
  new cloudwatch.Dashboard(stack, "PerformanceDashboard", {
    dashboardName: "sgcarstrends-performance",
    widgets: [
      [
        // Lambda duration
        new cloudwatch.GraphWidget({
          title: "API Response Time",
          left: [
            new cloudwatch.Metric({
              namespace: "AWS/Lambda",
              metricName: "Duration",
              dimensionsMap: {
                FunctionName: "sgcarstrends-api-prod",
              },
              statistic: "Average",
            }),
          ],
        }),

        // Error rate
        new cloudwatch.GraphWidget({
          title: "Error Rate",
          left: [
            new cloudwatch.Metric({
              namespace: "AWS/Lambda",
              metricName: "Errors",
              dimensionsMap: {
                FunctionName: "sgcarstrends-api-prod",
              },
              statistic: "Sum",
            }),
          ],
        }),
      ],

      [
        // Memory usage
        new cloudwatch.GraphWidget({
          title: "Memory Usage",
          left: [
            new cloudwatch.Metric({
              namespace: "AWS/Lambda",
              metricName: "MemoryUtilization",
              dimensionsMap: {
                FunctionName: "sgcarstrends-api-prod",
              },
              statistic: "Average",
            }),
          ],
        }),

        // Invocations
        new cloudwatch.GraphWidget({
          title: "Invocations",
          left: [
            new cloudwatch.Metric({
              namespace: "AWS/Lambda",
              metricName: "Invocations",
              dimensionsMap: {
                FunctionName: "sgcarstrends-api-prod",
              },
              statistic: "Sum",
            }),
          ],
        }),
      ],
    ],
  });
}

Best Practices

1. Profile Before Optimizing

// ❌ Optimize without profiling
// Just guessing what's slow

// ✅ Profile first, then optimize
const end = profiler.start("operation");
await operation();
end();

// Check profiler stats to find bottleneck
console.log(profiler.getStats());

2. Set Performance Budgets

// lighthouse.config.js
module.exports = {
  ci: {
    collect: {
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        "categories:performance": ["error", { minScore: 0.9 }],
        "first-contentful-paint": ["error", { maxNumericValue: 1800 }],
        "largest-contentful-paint": ["error", { maxNumericValue: 2500 }],
        "cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }],
      },
    },
  },
};

3. Monitor Continuously

// ✅ Continuous monitoring
setInterval(() => {
  memoryMonitor.check();
}, 60000);  // Check every minute

// ✅ Log performance metrics
app.use(performanceMiddleware);

Troubleshooting

High Response Times

# 1. Check CloudWatch metrics
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Duration \
  --dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
  --statistics Average,Maximum \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300

# 2. Check logs for slow operations
aws logs filter-log-events \
  --log-group-name /aws/lambda/sgcarstrends-api-prod \
  --filter-pattern "duration > 1000"

# 3. Profile locally
NODE_OPTIONS="--prof" pnpm -F @sgcarstrends/api dev

# 4. Optimize bottleneck

Memory Issues

# 1. Check memory usage
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name MemoryUtilization \
  --dimensions Name=FunctionName,Value=sgcarstrends-api-prod \
  --statistics Average,Maximum

# 2. Capture heap snapshot
# Add to code: captureHeapSnapshot()

# 3. Analyze with Chrome DevTools
# Load .heapsnapshot file in DevTools Memory tab

# 4. Fix memory leaks

References

Best Practices Summary

  1. Profile First: Always profile before optimizing
  2. Monitor Continuously: Track performance metrics
  3. Set Budgets: Define performance budgets and enforce them
  4. Log Performance: Log slow operations automatically
  5. Cache Aggressively: Cache expensive operations
  6. Optimize Critical Path: Focus on user-facing operations
  7. Load Test: Test under realistic load
  8. Memory Awareness: Monitor memory usage and prevent leaks

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon