shared/guidelines/performance
Source:
shared/guidelines/performance.md
guideline: performance
Loaded when the task involves a perf regression, optimization, capacity question, or budget.
Hard rules
- Measure first. No “this might be slow” without a profile. No “this is faster” without a benchmark.
- Budgets are per-route, per-query, per-endpoint. Number them: “API p99 < 200ms”, “page LCP < 2.5s”, “query < 100ms cold”.
- One change at a time when optimizing. Profile, change one thing, re-profile.
- Cache invalidation is a problem. Don’t add a cache without a plan for invalidating it.
- Pre-optimization is debt. Default to the simple solution; optimize when measurement shows you must.
Common bottlenecks (look for these)
- N+1 queries: ORM in a loop without
.includes()/.select_related(). Profile + batch. - Sync over async:
requests.getin an async handler. Use the async client. - Allocation in hot paths: object creation inside a tight loop. Reuse / pool.
- Cold cache thundering herd: many requests miss simultaneously after invalidation. Lock or stagger.
- Cardinality explosion: a metric / log with user_id as tag. Hits storage and query cost.
- Front-end JS over budget: unnecessary deps, unused exports, no code split, no compression.
Profiling tools (by context)
- Backend latency: APM (Datadog, NewRelic) for prod; py-spy / async-profiler / pprof local.
- Backend memory: heap dump → diff over time. Watch for growing structures.
- DB query:
EXPLAIN ANALYZE, slow-query log, query plan. - Frontend: Lighthouse for synthetic; RUM (Datadog RUM) for real users; Chrome DevTools Performance tab for local.
Caching layers (in order of placement)
- CDN (static / edge cache) → biggest win for read-heavy public content.
- Reverse proxy (Varnish / Cloudflare) → near-edge for dynamic but cacheable.
- App cache (Redis / Memcached) → per-tenant / per-user.
- Process memory (LRU cache in code) → for re-used computations in a single process.
- DB query cache → bounded; usually not enough by itself.
Anti-patterns
- “Faster” without measurement. Show the before/after numbers.
- Caching everything. Cache what you’ve shown is slow + cacheable.
- Premature parallelism. Async + concurrency adds complexity; show you need it.
- Hand-rolled coroutine schedulers. Use the platform’s (asyncio / Tokio / Goroutines).
- Caching mutable shared state without invalidation. Use staleness annotations.
Cite when reviewing
- The profile output (link to flame graph, APM trace, DB query plan).
- The metric / monitor showing the regression.
observability.mdfor the metric naming + tags.