| sidebar_position | 12 |
|---|
Fast is good. Efficient is better.
Avoid invisible technical debt. Poor performance and runaway costs compound over time.
Set measurable performance targets:
| Metric | Target | Measurement |
|---|---|---|
| Page Load (p95) | < 2s | Lighthouse, WebPageTest |
| Time to Interactive | < 3s | Lighthouse |
| API Response (p95) | < 200ms | Application metrics |
| Database Queries | < 50ms | Query monitoring |
| Bundle Size | < 200KB gzipped | Webpack Bundle Analyzer |
✅ DO:
- Measure against budgets in CI
- Fail builds that exceed budgets
- Track budgets in monitoring dashboards
- Review budgets quarterly
❌ DON'T:
- Set budgets and ignore them
- Optimize prematurely without data
- Sacrifice correctness for performance
- Ignore mobile/slow network performance
Sources:
:::tip Pin real workflow refs with Ratchet This example uses readable tags for clarity. In a real repository, run Pin workflow refs with Ratchet on your workflow files and commit the rewritten SHA pins. :::
# .github/workflows/performance.yml
name: Performance
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: treosh/lighthouse-ci-action@v10
with:
urls: |
https://staging.example.com/
budgetPath: ./lighthouse-budget.json
uploadArtifacts: trueBudget File:
{
"path": "/*",
"timings": [
{
"metric": "interactive",
"budget": 3000
},
{
"metric": "first-contentful-paint",
"budget": 1000
}
],
"resourceSizes": [
{
"resourceType": "script",
"budget": 200
}
]
}Sources:
CPU Profiling:
# Start with profiling enabled
node --prof app.js
# Generate readable output
node --prof-process isolate-0x*.log > processed.txtMemory Profiling:
// Take heap snapshot
import v8 from "v8";
import fs from "fs";
function takeHeapSnapshot() {
const filename = `heap-${Date.now()}.heapsnapshot`;
const snapshot = v8.writeHeapSnapshot(filename);
console.log(`Heap snapshot written to ${snapshot}`);
}
// Call when memory is highSources:
React DevTools Profiler:
import { Profiler } from 'react';
function onRenderCallback(
id: string,
phase: 'mount' | 'update',
actualDuration: number
) {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}
<Profiler id="UserList" onRender={onRenderCallback}>
<UserList />
</Profiler>Chrome DevTools:
- Open DevTools → Performance tab
- Record interaction
- Analyze flame graph for bottlenecks
Sources:
Identify Slow Queries:
-- PostgreSQL slow query log
ALTER DATABASE myapp SET log_min_duration_statement = 100; -- ms
-- Check slow queries
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;Common Optimizations:
✅ DO:
- Add indices for frequently queried columns
- Use
EXPLAIN ANALYZEto understand query plans - Paginate large result sets
- Use connection pooling
- Cache expensive queries
❌ DON'T:
- Use
SELECT *(select only needed columns) - Query in loops (N+1 problem)
- Index every column (indices have write cost)
Example N+1 Fix:
// ❌ Bad - N+1 queries
const users = await User.findAll();
for (const user of users) {
const orders = await Order.findByUserId(user.id);
user.orders = orders;
}
// ✅ Good - Single query with join
const users = await User.findAll({
include: [Order],
});Sources:
// Terraform example
resource "aws_instance" "app" {
ami = "ami-12345678"
instance_type = "t3.micro"
tags = {
Team = "backend"
Project = "api-gateway"
Environment = "production"
CostCenter = "engineering"
}
}Track costs by:
- Team: Which team owns the resource?
- Project: Which project is it for?
- Environment: dev, staging, production
Example Dashboard Queries:
-- Monthly cost by team
SELECT
tag_team,
SUM(cost) as total_cost
FROM aws_costs
WHERE month = '2024-01'
GROUP BY tag_team
ORDER BY total_cost DESC;Sources:
Set up alerts for unusual spending:
# Alert if daily cost increases > 20%
alert: HighCostIncrease
expr: (daily_cost - daily_cost offset 1d) / daily_cost offset 1d > 0.2
for: 1h
annotations:
summary: "AWS costs increased by {{ $value }}%"Sources:
✅ DO:
- Use auto-scaling instead of over-provisioning
- Choose appropriate instance types
- Use spot instances for non-critical workloads
- Enable cost optimization recommendations
❌ DON'T:
- Run large instances 24/7 for dev environments
- Keep unused resources running
- Ignore right-sizing recommendations
Cost Optimization Checklist:
- Delete unused resources (old snapshots, volumes)
- Stop dev/staging environments outside business hours
- Use reserved instances for predictable workloads
- Enable S3 lifecycle policies for old data
- Review and remove unused security groups/load balancers
Sources:
Reduce compute costs with caching:
Cache Layers:
┌─────────────┐
│ Browser │ ← Cache-Control headers
└─────────────┘
↓
┌─────────────┐
│ CDN │ ← Static assets
└─────────────┘
↓
┌─────────────┐
│ Application │ ← Redis/Memcached
└─────────────┘
↓
┌─────────────┐
│ Database │ ← Query results
└─────────────┘
Example (Redis Caching):
async function getUser(id: string): Promise<User> {
const cacheKey = `user:${id}`;
// Check cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Fetch from database
const user = await db.users.findById(id);
// Cache for 1 hour
await redis.setex(cacheKey, 3600, JSON.stringify(user));
return user;
}Sources:
| Strategy | When to Use | Pros | Cons |
|---|---|---|---|
| Horizontal | Stateless apps, high availability | Better redundancy, easier scaling | More complex, eventual consistency |
| Vertical | Stateful apps, databases | Simple, strong consistency | Limited by hardware, single point of failure |
Horizontal Scaling Example:
# Kubernetes HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Sources:
Validate scaling before production traffic:
// k6 load test
import http from "k6/http";
import { check, sleep } from "k6";
export let options = {
stages: [
{ duration: "2m", target: 100 }, // Ramp up
{ duration: "5m", target: 100 }, // Steady state
{ duration: "2m", target: 200 }, // Spike
{ duration: "5m", target: 200 }, // High load
{ duration: "2m", target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ["p(95)<200"], // 95% under 200ms
http_req_failed: ["rate<0.01"], // < 1% errors
},
};
export default function () {
const res = http.get("https://api.example.com/users");
check(res, {
"status is 200": (r) => r.status === 200,
"response time OK": (r) => r.timings.duration < 200,
});
sleep(1);
}Sources:
Read Replicas:
// Configure read replicas
const db = {
write: createConnection("postgres://primary"),
read: createConnection("postgres://replica"),
};
// Use replica for reads
async function getUsers() {
return db.read.query("SELECT * FROM users");
}
// Use primary for writes
async function createUser(data: User) {
return db.write.query("INSERT INTO users ...", data);
}Sharding (when needed):
// Shard by user ID
function getShard(userId: string): Database {
const shardNumber = hashUserId(userId) % NUM_SHARDS;
return shards[shardNumber];
}
async function getUser(userId: string) {
const shard = getShard(userId);
return shard.query("SELECT * FROM users WHERE id = $1", [userId]);
}Sources:
| Scenario | Cheap | Balanced | Performance |
|---|---|---|---|
| Database | Single instance | Primary + replica | Sharded cluster |
| Caching | None | Redis single node | Redis cluster |
| Compute | t3.micro | t3.medium | c6i.xlarge |
| Storage | S3 Standard | S3 Intelligent-Tiering | S3 + CloudFront |
Decision Framework:
- Start simple: Use balanced option
- Measure: Track performance and costs
- Optimize: Upgrade only when needed
- Iterate: Continuously evaluate trade-offs
Sources: