Profiling and Application Performance
Performance optimization reduces a measured bottleneck against user impact and capacity goals. Without profiling, adding cache, threads, or instances can hide symptoms while increasing cost.
Quick Decision
| Symptom | Measure first | Typical intervention |
|---|---|---|
| High CPU | CPU profile, allocations, serialization | Algorithm, batching, threads, payload |
| Growing memory | Heap/live objects, GC, allocation profile | Lifecycle, bounded cache, leak |
| Slow API | Trace, DB plan, downstream latency | Query/index, parallelism, timeout |
| Low throughput | Queue, pool saturation, lock contention | Backpressure, pools, scaling |
Production Checklist
- Problem: Which user flow or SLO is affected?
- Solution: Is there a baseline, hypothesis, change, and before/after measurement?
- Trade-off: Lower latency can require more cache, CPU, memory, or operational cost.
- Failure mode: Inspect cache stampedes, thread starvation, pool exhaustion, GC pauses, and compression CPU.
- Measurement: Track p50/p95/p99, throughput, errors, CPU, memory, GC, queue age, and unit cost together.
- Security/cost: Profiling data must not leak PII; bound production sampling and retention.
Profiling Method
- Establish a baseline with representative traffic and workload.
- Separate CPU, allocation/heap, lock, I/O, and database-trace signals.
- Make one small change that tests one hypothesis.
- Compare latency, errors, and resources under the same workload.
- Record the gain and new trade-off in an ADR or performance record.
Sampling profilers show general hot paths; tracing shows where a request waits; heap dumps help find retained objects and leaks. Profiling itself adds latency and storage, so scope and duration must be controlled.
API and I/O Optimization
- Return only needed DTO fields; bound payloads with pagination and field selection.
- Use gzip/Brotli for large text payloads after measuring CPU cost for small responses.
- Configure HTTP client pools, keep-alive, connect/read timeouts, and pending-request limits separately.
- Run independent downstream calls with bounded parallelism; without deadline propagation, parallelism can extend the timeout.
- Use lazy loading only when access patterns and transaction boundaries are clear; avoid hidden ORM N+1 queries.
Database and Cache
Do not add cache before measuring query plans, index selectivity, lock wait, pool utilization, and replica lag. Repository/ORM abstractions can hide the real query cost.
Track cache hits, misses, evictions, hot keys, stampedes, and origin fallback. Warm only popular, low-volatility data with bounded concurrency; do not overload the origin during deploys.
Scaling
Horizontal scaling increases capacity through stateless applications, load distribution, and autoscaling. It cannot fix a database, license, or shared connection-pool bottleneck. Vertical scaling is simpler but has a node limit and rising cost.
Autoscaling should not rely on CPU alone; consider request concurrency, queue age, latency, error budget, and downstream saturation. A CDN can move static assets and cacheable responses to the edge; separate personal or authorization-dependent responses carefully.
Outcome
Successful optimization improves measurements without violating correctness, security, availability, or cost goals. Record the profile, change, expected gain, and rollback signal together.
