17 — Performance Antipatterns: The Mistakes That Slow Systems Down
Every performance mistake I made was a war story I learned after an incident — until they turned out to be a catalog with names. Writing them down turned the grab-bag into a reference: performance antipatterns are common, structural mistakes (N+1 queries, chatty I/O, unbounded data, missing caches, noisy neighbors, retry storms) that lead to poor performance, and each has a known shape and a known fix. [1] Recognizing them by name is most of the battle.
The framing that landed is that these are not random bugs — they are _patterns_, in the sense that they recur across unrelated systems because they share a root cause. N+1 queries happen whenever code loops over a collection and fetches related data per item. Chatty I/O happens whenever a logical operation is implemented as many small requests instead of one batched one. Retry storms happen whenever retry logic lacks backoff. Naming the pattern points at the fix.
The catalog
The roadmap's antipatterns fall into a few families, and I group them that way because the fixes cluster too [1][2][3]:
Data-access family
- N+1 queries. The system makes N+1 queries to retrieve related data instead of one query with a join or a batched fetch [1]. Fetching 100 orders, then one query per order for its items, is 101 queries. The fix is almost always eager loading or a join.
- Busy database. The database is handling more requests than it can, leading to degraded performance, resource contention, deadlocks, and inconsistencies [4]. The fixes are the database-scaling levers from the database notes: scale out, optimize the schema, cache, index.
- Extraneous fetching. Retrieving more data than the task needs — selecting all columns when only two are used, fetching full objects when a count would do [5]. Leads to performance degradation and wasted network. The fix is projecting only what is needed.
- Monolithic persistence. Using a single database to store all data [6]. Fine at small scale; a bottleneck at large scale, limiting flexibility and scalability. The fixes are federation, sharding, or NoSQL — again, the database-scaling levers.
I/O and resource family
- Chatty I/O. A large number of small I/O requests where one batched operation would do — reading database records one at a time, implementing one logical operation as a series of HTTP requests, reading a file in tiny chunks [7]. Network calls and I/O are inherently slow relative to compute, and the per-request overhead dominates. The fix is batching.
- Synchronous I/O. Blocking the calling thread while I/O completes [8]. The thread enters a wait state, wasting processing resources. A single synchronous I/O call can block an entire call chain. The fix is async I/O.
- No caching. An application handling many concurrent requests repeatedly fetches the same data from an expensive source, repeatedly constructs the same objects, or makes excessive calls to a throttled remote service [9]. The fix is one of the cache strategies from the caching notes.
- Improper instantiation. Creating unnecessary instances of an object, class, or service on every request when a shared instance would do [10]. The fix is reuse — singletons, object pools, connection pools.
Resilience and capacity family
- Retry storm. A large number of retries in a short period, multiplying traffic and resource usage [11]. Often triggered when a downstream service slows and every upstream client retries at once. The fixes are exponential backoff, circuit breaking, and monitoring/alerting.
- Noisy neighbor. One component utilizes a disproportionate share of resources, causing contention for everyone else — one user hogging CPU, one process saturating I/O, one app eating bandwidth [12]. The fixes are resource isolation (the bulkhead pattern) and per-tenant quotas.
- Busy frontend. The user-facing layer — web servers, CDN, browser — handles more work than it can: too many concurrent users, large static assets, heavy client-side rendering, missing caches [13]. The fixes are CDNs for static files, lazy-loading and script optimization, load balancing, and fewer unnecessary API calls.
- Unbounded data. The system retrieves or processes more data than necessary — no pagination, no LIMIT, pulling a whole table into memory [1]. The fix is bounding the result set explicitly.
How I use this
The value of the catalog is diagnostic speed. When a system is slow under load, I run through the families: is it data-access (N+1, busy DB, extraneous fetching)? I/O (chatty, synchronous, no cache)? Resilience (retry storm, noisy neighbor, busy frontend)? Each pattern has a signature in the metrics — N+1 shows as a query count proportional to row count; chatty I/O shows as high request count with low bytes-per-request; retry storm shows as traffic spikes correlated with downstream latency. Naming the pattern from the metric is what points me at the fix within minutes instead of hours. And the discipline of asking "which antipattern is this?" before writing code prevents most of them from shipping in the first place.
References
[1] Microsoft, "Performance antipatterns for cloud applications," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/
[2] Microsoft, "Busy database antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/busy-database/
[3] Microsoft, "Chatty I/O antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/chatty-io/
[4] "How to avoid retry storms in distributed systems," FAUN, 2022. [Online]. Available: https://faun.pub/how-to-avoid-retry-storms-in-distributed-systems-91bf34f43c7f
[5] Microsoft, "Extraneous fetching antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/extraneous-fetching/
[6] Microsoft, "Monolithic persistence antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/monolithic-persistence/
[7] Microsoft, "Improper instantiation antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/
[8] Microsoft, "Synchronous I/O antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/synchronous-io/
[9] Microsoft, "No caching antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/no-caching/
[10] Microsoft, "Noisy neighbor antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor
[11] Microsoft, "Retry storm antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/retry-storm/
[12] Microsoft, "Busy front end antipattern," Azure Architecture Center. [Online]. Available: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/busy-front-end/
Knowledge check · Question 1 of 4
A loop fetches 50 orders, then makes one DB call per order for its line items. This is the ____ antipattern.
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!