Performance Antipatterns
Most slow systems are slow for the same few reasons. A tour of ten common performance antipatterns, from the busy database and chatty I/O to retry storms and noisy neighbours, with how to spot and fix each one.
In a nutshell
- Most performance problems are a few repeat offenders: too much work in one place, too many round trips, or too much data.
- Measure before fixing: profiles, traces and per-query statistics point at the real cause.
- Fix the shape of the work (batch, cache, move it off the request path) before buying bigger machines.
- Several antipatterns only appear under load, so load-test with realistic data and traffic.
Busy database
Business logic pushed into the database (big stored procedures, heavy formatting, complex calculations in SQL) makes the database the busiest and hardest-to-scale part of the system. Fix: keep the database for storing and querying data; do computation in stateless app servers that scale out.
Busy front end
Web servers doing slow work (image resizing, PDF generation, big exports) inside requests tie up threads, so ordinary requests queue behind them. Fix: move the work to background jobs and return a job ID.
Chatty I/O
Many small calls instead of a few larger ones: one query per item in a loop (N+1), one HTTP call per field, writing a file one line at a time. Each call pays the full round-trip latency.
Chatty vs batched
Diagram as text
- App → Service: 100 × GET /items/{id}
- App → Service: 1 × GET /items?ids=…
Fix: batch reads and writes, use joins or IN (…), design APIs that return what a screen needs in one call.
Extraneous fetching
Fetching far more than needed: SELECT * for two columns, loading 10,000 rows to show 20, pulling a whole table to filter it in application code. Fix: select only needed columns, filter and paginate in the query, aggregate in the database.
Improper instantiation
Creating expensive objects that are meant to be shared, on every request: HTTP clients, database connections, SDK clients. Each creation may open sockets, do TLS handshakes and leak resources. Fix: create them once and reuse them (connection pools, singleton clients).
Monolithic persistence
Every kind of data in one database: orders, logs, sessions, analytics events and file blobs all compete for the same resources. Fix: put each kind of data in a store that suits it (object storage for files, a log system for logs, a cache for sessions).
No caching
Recomputing or refetching the same rarely changing data on every request. Fix: cache it at the right layer with a sensible TTL and invalidation.
Noisy neighbour
In shared infrastructure, one tenant or workload uses so much CPU, I/O or connections that everyone else slows down. Fix: per-tenant quotas and rate limits, separate resource pools (bulkheads), or moving heavy tenants to their own resources.
Retry storm
A service slows down; every caller retries immediately, several times, and its callers retry too. Load multiplies exactly when the service is weakest, so it cannot recover. Fix: limited retries with exponential backoff and jitter, retry budgets, circuit breakers, and retrying at one layer only.
Synchronous I/O
Threads blocked waiting on slow network or disk calls, so a server with 200 threads can only have 200 calls in flight, mostly idle. Fix: asynchronous I/O (async/await, non-blocking clients, virtual threads in modern Java) and moving slow calls off the request path.
| Antipattern | Symptom | First fix |
|---|---|---|
| Busy database | DB CPU high, app servers idle | Move logic to the app tier |
| Busy front end | Slow pages during heavy tasks | Background jobs |
| Chatty I/O | Many tiny calls per request in traces | Batch |
| Extraneous fetching | Large responses, high network use | Select and filter in the query |
| Improper instantiation | Many new connections, socket exhaustion | Reuse clients and pools |
| Monolithic persistence | One DB with unrelated hot spots | Fit-for-purpose stores |
| No caching | Same queries repeated constantly | Cache with TTLs |
| Noisy neighbour | Others slow when one tenant is busy | Quotas and isolation |
| Retry storm | Traffic spikes after errors start | Backoff, jitter, breakers |
| Synchronous I/O | Threads exhausted, CPU low | Async I/O |
Common mistakes
- Scaling hardware up before finding which antipattern is the cause.
- Load-testing with an empty database, which hides N+1 queries and extraneous fetching.
- Fixing averages while the 99th percentile stays slow.
Interview questions
Try answering out loud before you open each one.
Latency doubled after a release. How do you investigate?
Compare traces before and after for the slow endpoints: look for new calls (chatty I/O), bigger queries (extraneous fetching), missing indexes, or new synchronous work in the request. Check database and dependency metrics, connection pool saturation and error-driven retries. Roll back if users are badly affected, then fix.
Check yourself
Related topics
Further reading
- Performance antipatterns for cloud applications Microsoft Azure Architecture Center
Your notes
Saved in this browser as you type.
Finished this topic?
Mark it read to track your progress, then carry on with the next one.