| name | performance-patterns |
| description | Performance optimization patterns — caching, lazy loading, profiling, memory management, database queries, frontend performance. Use when optimizing speed, reducing resource usage, or fixing performance bottlenecks. |
| version | 1.0.0 |
| license | MIT |
| metadata | {"hermes":{"tags":["performance","optimization","caching","profiling","database","frontend"],"related_skills":["clean-code","database-patterns","monitoring-observability","pd"]}} |
Performance Patterns
Overview
Performance optimization is about making the right trade-offs. Profile first, measure always, optimize the bottleneck. Premature optimization is the root of all evil — but ignoring performance is laziness.
Core principle: You can't optimize what you can't measure. Profile before changing code.
When to Use
- Application feels slow or unresponsive
- Response times exceed SLA requirements
- Memory usage growing unexpectedly
- Database queries taking too long
- Frontend Core Web Vitals failing
- User complaints about speed
Don't use for:
- Prototypes or throwaway code
- Features that aren't bottlenecks
- Code that runs once a month
I. Caching Strategies
Cache Levels
| Level | Location | Latency | Use Case |
|---|
| Browser | Client HTTP cache | ~0ms | Static assets, API responses |
| CDN | Edge servers (10-50ms) | ~20ms | Images, videos, static files |
| Application | In-process memory | ~0.01ms | Computed results, DB query cache |
| Database | Query cache, buffer pool | ~1ms | Repeated queries |
| Distributed | Redis/Memcached | ~2ms | Shared state across instances |
Browser Caching
from fastapi import Response
@app.get("/api/products")
async def get_products(response: Response):
response.headers["Cache-Control"] = "public, max-age=300, stale-while-revalidate=60"
response.headers["ETag"] = compute_etag(products)
return products
# Nginx — Static asset caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
CDN Caching
response.headers["Surrogate-Key"] = f"product-{product_id}"
Application-Level Caching
from functools import lru_cache
from time import time
_cache = {}
CACHE_TTL = 300
def cached(key: str, ttl: int = CACHE_TTL):
def decorator(func):
def wrapper(*args, **kwargs):
now = time()
if key in _cache:
value, timestamp = _cache[key]
if now - timestamp < ttl:
return value
result = func(*args, **kwargs)
_cache[key] = (result, now)
return result
return wrapper
return decorator
@cached("product_list", ttl=60)
def get_products():
return db.query("SELECT * FROM products")
Redis Caching
import redis
import json
r = redis.Redis()
def get_product(product_id: int) -> dict:
cache_key = f"product:{product_id}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
product = db.query(Product).get(product_id)
r.setex(cache_key, 300, json.dumps(product.to_dict()))
return product.to_dict()
Cache Invalidation Patterns
| Pattern | Strategy | When to Use |
|---|
| TTL-based | Expire after N seconds | Data changes infrequently |
| Write-through | Update cache on write | Read-heavy, moderate writes |
| Write-behind | Async cache update | High write throughput |
| Cache-aside | Lazy load on miss | General purpose |
The Two Hardest Things: Cache invalidation and naming things. When in doubt, use TTL.
II. Lazy Loading Patterns
Image Lazy Loading
<img src="product.jpg" loading="lazy" width="300" height="200" alt="Product">
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
</script>
Component Lazy Loading (React)
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
const AdminPanel = lazy(() => import('./AdminPanel'));
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<Spinner />}>
<HeavyChart data={chartData} />
</Suspense>
</div>
);
}
Code Splitting
const routes = {
'/dashboard': () => import('./pages/Dashboard'),
'/settings': () => import('./pages/Settings'),
'/reports': () => import('./pages/Reports'),
};
async function loadRoute(path) {
const module = await routes[path]();
return module.default;
}
Database Lazy Loading (ORM)
class Order(Base):
__tablename__ = 'orders'
items = relationship("OrderItem", lazy="select")
items = relationship("OrderItem", lazy="joined")
items = relationship("OrderItem", lazy="dynamic")
orders = session.query(Order).options(joinedload(Order.items)).all()
III. Connection Pooling
Database Connection Pool
from sqlalchemy import create_engine
engine = create_engine(
"postgresql://user:pass@localhost/db",
pool_size=20,
max_overflow=10,
pool_timeout=30,
pool_recycle=1800,
pool_pre_ping=True,
)
from sqlalchemy import event
@event.listens_for(engine, "checkout")
def on_checkout(dbapi_conn, connection_rec, connection_proxy):
"""Track connection checkout time."""
connection_proxy.info['checkout_time'] = time.time()
HTTP Connection Pool (requests)
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
adapter = HTTPAdapter(
pool_connections=10,
pool_maxsize=20,
max_retries=Retry(total=3, backoff_factor=0.5)
)
session.mount("https://", adapter)
session.mount("http://", adapter)
response = session.get("https://api.example.com/data")
IV. Profiling and Benchmarking
Python Profiling
import cProfile
import pstats
def profile_function(func, *args, **kwargs):
profiler = cProfile.Profile()
profiler.enable()
result = func(*args, **kwargs)
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(20)
return result
profile_function(process_large_dataset, data)
Line-Level Profiling
@profile
def slow_function(data):
result = []
for item in data:
processed = transform(item)
result.append(processed)
return sorted(result)
Benchmark Pattern
import time
from contextlib import contextmanager
@contextmanager
def benchmark(label: str):
"""Simple benchmark context manager."""
start = time.perf_counter()
yield
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.4f}s")
with benchmark("Database query"):
results = db.query("SELECT * FROM large_table")
with benchmark("Approach A (loop)"):
result_a = [process(x) for x in data]
with benchmark("Approach B (map)"):
result_b = list(map(process, data))
V. Memory Management
Detecting Memory Leaks
import tracemalloc
import objgraph
tracemalloc.start()
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 memory consumers ]")
for stat in top_stats[:10]:
print(stat)
Weak References for Caches
import weakref
class ExpensiveObject:
def __init__(self, data):
self.data = data
cache = weakref.WeakValueDictionary()
def get_or_create(key, data):
if key not in cache:
cache[key] = ExpensiveObject(data)
return cache[key]
obj = get_or_create("key1", large_dataset)
del obj
Generators for Memory Efficiency
def read_large_file(path):
with open(path) as f:
return f.readlines()
def read_large_file(path):
with open(path) as f:
for line in f:
yield line.strip()
for line in read_large_file("huge_file.log"):
process(line)
VI. CPU Optimization
Algorithm Complexity
| Complexity | Name | Example | 1K items | 1M items |
|---|
| O(1) | Constant | Hash lookup | 1 | 1 |
| O(log n) | Logarithmic | Binary search | 10 | 20 |
| O(n) | Linear | Array scan | 1,000 | 1,000,000 |
| O(n log n) | Linearithmic | Merge sort | 10,000 | 20,000,000 |
| O(n²) | Quadratic | Nested loops | 1,000,000 | 1,000,000,000,000 |
Caching Expensive Computations
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
Batch Processing
for user_id in user_ids:
send_email(user_id, message)
BATCH_SIZE = 100
for i in range(0, len(user_ids), BATCH_SIZE):
batch = user_ids[i:i + BATCH_SIZE]
send_bulk_email(batch, message)
VII. Network Optimization
HTTP/2 and Multiplexing
import httpx
async with httpx.AsyncClient(http2=True) as client:
responses = await asyncio.gather(
client.get("https://api.example.com/users"),
client.get("https://api.example.com/products"),
client.get("https://api.example.com/orders"),
)
Request Batching (GraphQL)
query {
user(id: 1) {
name
email
orders { id, total, date }
preferences { theme, language }
}
}
Compression
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
app.add_middleware(GZipMiddleware, minimum_size=500)
VIII. Database Query Optimization
Indexing Strategy
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_orders_created_at ON orders(created_at DESC);
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
CREATE INDEX idx_orders_pending ON orders(created_at)
WHERE status = 'pending';
Query Optimization
SELECT * FROM orders WHERE user_id = 123;
SELECT id, total, status FROM orders WHERE user_id = 123;
SELECT * FROM orders;
SELECT * FROM order_items WHERE order_id = ?;
SELECT o.*, oi.* FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.user_id = 123;
SELECT *, (SELECT COUNT(*) FROM items WHERE order_id = orders.id) as item_count
FROM orders;
SELECT o.*, COUNT(i.id) as item_count
FROM orders o
LEFT JOIN items i ON o.id = i.order_id
GROUP BY o.id;
Pagination Patterns
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 10000;
SELECT * FROM products
WHERE id > 10000
ORDER BY id
LIMIT 20;
SELECT * FROM products
WHERE created_at < '2024-01-15T10:30:00'
ORDER BY created_at DESC
LIMIT 20;
IX. Frontend Performance (Core Web Vitals)
Core Web Vitals Targets
| Metric | Good | Needs Improvement | Poor |
|---|
| LCP (Largest Contentful Paint) | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP (Interaction to Next Paint) | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
LCP Optimization
<link rel="preload" href="/hero-image.webp" as="image">
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero" width="1200" height="600">
</picture>
CLS Prevention
img, video {
aspect-ratio: 16/9;
width: 100%;
height: auto;
}
.ad-slot {
min-height: 250px;
}
INP Optimization
function processLargeDataset(data) {
const CHUNK_SIZE = 100;
let index = 0;
function processChunk(deadline) {
while (index < data.length && deadline.timeRemaining() > 0) {
processItem(data[index]);
index++;
}
if (index < data.length) {
requestIdleCallback(processChunk);
}
}
requestIdleCallback(processChunk);
}
const worker = new Worker('processor.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (e) => updateUI(e.data);
Decision Tree: What to Optimize?
START
│
├─ App feels slow?
│ ├─ Initial load slow → Optimize LCP (preload, lazy load, CDN)
│ ├─ Interactions laggy → Optimize INP (break long tasks, web workers)
│ └─ Layout jumps → Fix CLS (reserve space, font-display)
│
├─ API responses slow?
│ ├─ Database queries → Add indexes, optimize queries
│ ├─ Computation → Cache results, batch operations
│ └─ Network → Connection pooling, HTTP/2, compression
│
├─ Memory issues?
│ ├─ Growing over time → Check for leaks (tracemalloc)
│ ├─ Large datasets → Use generators, streaming
│ └─ Caches too large → Add TTL, weak references
│
└─ Database bottleneck?
├─ Slow queries → EXPLAIN ANALYZE, add indexes
├─ Too many queries → Batch, JOIN, eager loading
└─ Connection limits → Connection pooling
Anti-Patterns
| Pattern | Problem | Fix |
|---|
| Premature optimization | Wasted time on non-bottlenecks | Profile first, measure always |
| Caching everything | Cache invalidation complexity | Cache only hot paths |
| N+1 queries | 100x more DB calls than needed | Use JOINs or eager loading |
| SELECT * | Loads unused data | Select only needed columns |
| No connection pooling | Connection overhead per request | Use connection pools |
| Ignoring frontend | Slow LCP/INP/CLS | Measure Core Web Vitals |
Verification Checklist
Related Skills
- clean-code — Clean code is easier to profile and optimize. Small functions isolate bottlenecks.
- database-patterns — Schema design, indexing, and migration patterns directly impact query performance.
- monitoring-observability — Logging, metrics, and tracing identify performance bottlenecks in production.
- pd — Master orchestrator. Performance optimization fits into Phase 4 (Coding) and Phase 5 (Testing).