Incidentsanti-patternsguessingcardinalitydashboardsalerting

Performance and Observability Anti-Patterns

Every one of these is a plausible move that a competent engineer makes under pressure, and every one shares a single property: no measurement before, or no measurement after. That is the tell, and it is the only thing they have in common.

Follow the diagnosis

Frame the diagnosis

Performance work starts from a symptom and a signal — never from a resource dashboard.

Diagnostic question
Which of these plausible performance moves are actually guesses wearing engineering clothes?
Symptom
Optimization work that ships regularly and produces no durable improvement in user-facing latency, alongside monitoring that grows continuously and answers fewer questions each quarter.
Signal
The absence of a baseline before the change and a comparison after it. Every anti-pattern here is identifiable by that gap alone, without knowing anything about the specific technique.
SymptomSignalMeasurementHypothesisEvidenceRoot CauseChangeValidationRegression Check

Performance anti-patterns

These are not strawmen. Each one is a reasonable-sounding response to real pressure, usually proposed by someone experienced, often in a meeting where the alternative is admitting nobody knows where the time goes. They are worth naming because naming them is what makes them refusable.

The common structure: a technique that *sometimes* works is applied without establishing that its precondition holds. Adding an index works when the query is index-eligible and the scan is the cost; it works badly when the write amplification exceeds the read benefit, when the planner ignores it, or when the actual cost was lock waiting (Low CPU, High Latency: Lock Contention). The technique is not wrong. Applying it without checking the precondition is.

Notice how many of these appear during incidents specifically. Time pressure converts "I do not know where the time goes" into "let us try the thing that worked last time", and last time's constraint is rarely this time's (The Bottleneck Moves After Every Fix).

The move, why it is tempting, and what it actually does
Anti-patternWhy it is temptingWhat it actually doesThe check it skipped
Optimize without measuringThe hot spot feels obviousEffort lands on code that was not the constraintA profile or trace attribution (Measure Before You Optimize)
Cache everythingCaching helped beforeInvalidation bugs, staleness, memory cost, stampedesWhich requests miss, and what a miss actually costs (Cache Stampede: Everyone Misses at Once)
Raise thread/worker countMore workers means more throughputMore contention and context switching; moves the queue downstreamWhether the constraint is concurrency or a downstream resource (Twenty Workers, All Busy, Five Hundred Waiting)
Buy a bigger instanceIt is fast and requires no analysisMasks the issue at recurring cost; useless if the constraint is I/O or a dependencyWhich resource is actually saturated (USE: Utilization, Saturation, Errors)
Tune to the benchmarkThe benchmark improves reliablyWins on a workload no user has (Microbenchmark or End-to-End: Why p99 Did Not Move)Whether the benchmark resembles production traffic
Ignore p99The average looks fineThe worst experiences stay invisible while the mean reassuresThe distribution, not the mean (The Average Was Fine and Users Were Not)
No baselineThe change is obviously an improvementNothing can be validated or reverted with evidenceMeasure before (Regression or Tuesday? Telling a Real Change from Noise)
Ignore queueingUtilization looks acceptableLatency explodes near the knee while utilization still reads "fine"Wait time, not just utilization (Queueing: Why Systems Get Slow Before They Get Broken)
Log verbosely in hot pathsLogs are useful and feel freeSerialization CPU and I/O inside the critical pathCost per request of the logging itself (The Log Bill and What It Is Buying)
Add indexes blindlyIndexes make queries fastWrite amplification, more storage, unused indexes the planner ignoresThe plan, and whether scanning was the cost (An Index Scan Is Not Automatically Faster)
Scale workers when the DB is the constraintThe queue is growing, so add consumersMore concurrent load onto the saturated resource; often makes it worseWhich resource is saturated (The Backlog Arithmetic: Four Levers and a Drain Time)
Retry without backoffRetries improve reliabilityAmplifies load exactly when the dependency is failingWhether the dependency is degraded or down (Retry Storms: The Load You Generated Yourself)

Observability anti-patterns

The observability failures are subtler because their cost is deferred. An unbounded label does not break anything today; it breaks the metrics backend in four months, usually during an unrelated incident when you need it most (Cardinality: The Label That Took Down Monitoring). A dashboard with two hundred panels is not wrong, it is simply unusable at 3am, and the unusability only manifests when someone is under pressure.

The dashboard case deserves specific attention because it is so common. Dashboards accumulate: every incident adds a panel, nothing is ever removed, and after two years the service overview answers no question quickly. A dashboard should be built around the questions a responder asks in order — is it healthy, is traffic normal, are users slow, are errors rising, which dependency, is anything saturated — and everything not serving one of those belongs on a secondary page (Dashboards Built Around Questions).

The most expensive one is alerting on every threshold. It produces a rotation where most pages are noise, and a rotation where most pages are noise reliably produces one where a real page gets acknowledged and set aside (Alert Fatigue: The Page Nobody Reads). The failure is not the noisy alert; it is the trained response to all alerts that the noisy alert creates.

Observability failures and their deferred costs
Anti-patternLooks likeDeferred costInstead
Unbounded metric cardinalityA user_id or request_id labelMetrics backend degrades or falls over months laterBounded labels; identifiers belong in traces and logs (Label Sets That Survive a Year)
Logging secretsLogging the full request for debuggingTokens and personal data in storage with wide read accessRedact at the logging boundary (What You Just Wrote Into a Log Half the Company Can Read)
Dashboards with no questionTwo hundred panels, nothing removedNobody can triage from it under pressureBuild around the responder's question order (Dashboards Built Around Questions)
Tracing every payloadFull request/response bodies as span attributesStorage cost, privacy exposure, slow trace queriesAttributes that identify, not attributes that duplicate (Trace, Span, Attribute, Status)
Alert on every thresholdCPU > 80% pages someoneAlert fatigue; real pages get ignoredAlert on user-visible symptoms and SLO burn (Burn-Rate Alerts: How Fast Is the Budget Going?)
No correlation idsEach service logs independentlyA cross-service failure cannot be reconstructed at allPropagate a request id everywhere (Correlation IDs: Turning Lines Into a Story)
No deploy markersCharts with no change annotations"What changed?" takes twenty minutes instead of five secondsAnnotate from every change source ("What Changed?" — Deploy Markers and the Invisible Deploys)
No user-centric SLOResource dashboards onlyThe system is "healthy" while users are failingDefine an SLI from the user's perspective (SLIs: Measuring What the User Actually Feels)

The tell

Every performance anti-pattern above is identifiable without domain knowledge, by a single question: what measurement established that this is the constraint, and what measurement will show that the change worked? If both answers exist, the change is engineering even if it turns out to be wrong. If either is missing, it is a guess even if it turns out to be right.

This is why "we tried it and it got faster" is weaker evidence than it feels. Without a baseline you cannot tell an improvement from normal variance, a traffic dip, or a cache that happened to be warm. Without knowing which resource was the constraint, you cannot tell whether your change or an unrelated coincidence produced the improvement (Correlation Is Not the Root Cause).

The organizational version is worth stating: teams do not adopt these anti-patterns because they are careless. They adopt them because measuring first is slower than acting, and acting is visible while measuring is not. The fix is cultural — make the baseline a required part of the change, so that "I do not know where the time goes" is an acceptable thing to say in a meeting where the alternative is a plausible guess.

A guess in engineering clothes
1Ticket: "Checkout is slow — add Redis caching to the orders lookup"
2
3· which requests are slow? not established
4· where does the time go? not measured
5· is the orders lookup on the path? assumed
6· expected improvement? "should help a lot"
7· how will we know it worked? "it should feel faster"
8
9shipped. p99 unchanged (the time was in the payment
10dependency). cache retained anyway. now there is an
11invalidation bug waiting, and no one will connect it
12back to this ticket.
The same instinct, made checkable
1Ticket: "Checkout p99 2.4s vs 500ms SLO"
2
3· slow requests: /checkout only, all regions [p99 by route]
4· time attribution: payment span 1.9s of 2.4s [trace p99]
5· constraint: external dependency, not ours [CPU 22%]
6· hypothesis: payment call is on critical path and
7 blocks the response unnecessarily
8· expected: p99 → ~0.5s if made async
9· validation: p99 by route, 24h, vs last Tuesday
10· guard: CI check on checkout critical-path
11 span count; alert on payment p99
12
13result: p99 2.4s0.31s. Redis never needed.

The instinct in both cases was reasonable — checkout is slow, caching helps slow things. The difference is four measurements taken before the work, which cost twenty minutes and redirected a week of effort away from a component that was contributing 4% of the latency.

Key points

  • Every performance anti-pattern is a technique applied without establishing that its precondition holds — the technique is not wrong, the missing check is.
  • Observability anti-patterns defer their cost: unbounded cardinality, secret-bearing logs and purposeless dashboards all fail months later, usually mid-incident.
  • Alert fatigue is not caused by any single noisy alert but by the trained response to all alerts that noisy alerts create.
  • The universal tell: what measurement established the constraint, and what measurement will show the change worked?
  • "We tried it and it got faster" without a baseline cannot distinguish improvement from variance, a traffic dip, or a warm cache.

Follow the diagnosis

The causal chain, hop by hop — and the readings that invite the wrong conclusion.

  1. 1
    Pressure → proposal: a plausible technique is proposed, usually one that solved a previous, different problem.
  2. 2
    Proposal → skipped check: the precondition (which resource is constrained, which requests are slow) is assumed rather than measured, because measuring is slower and less visible.
  3. 3
    Change → ambiguous result: the metric moves a little or not at all, and with no baseline the result cannot be distinguished from variance.
  4. 4
    Ambiguous result → retention: the change is kept because reverting requires evidence that was never collected, so its costs accrue permanently.
  5. 5
    Accumulation → system: over quarters, the system carries many such changes — caches nobody can invalidate, indexes nobody uses, workers nobody needs — each with a maintenance cost and no measured benefit.
What this evidence makes people conclude — wrongly
  • "It got faster after we shipped it" — without a baseline and a controlled comparison, this is compatible with variance, a traffic dip, or an unrelated change.
  • "This worked last time" — last time's constraint is rarely this time's, and the technique that relieved it is only correct when the same resource binds (The Bottleneck Moves After Every Fix).
  • "More monitoring is better monitoring" — every metric, label, dashboard and alert has a cost, and past a point they collectively reduce the ability to answer questions.
  • "CPU > 80% needs an alert" — utilization without user impact is not actionable; alert on symptoms and burn rate instead (Alerts Worth Waking Someone For).
  • "We can clean up the dashboards later" — dashboards are only judged during incidents, and incidents are when cleanup is impossible.

Measure, fix, validate

An optimization is not finished until the metric that motivated it has moved.

How to measure it
  • • A baseline for the target metric over a representative window before any change — same weekday and hour, since traffic shape confounds shorter comparisons.
  • • Attribution of where request time actually goes, from traces, before choosing which component to optimize ([[critical-path]]).
  • • Utilization and saturation across all resources, to establish which one is the constraint rather than which one is familiar ([[use-method]]).
  • • Metric series cardinality per metric name, tracked over time, so unbounded labels are caught while they are cheap to fix.
  • • Alert-to-action ratio per alert rule: how often each alert fired and how often it led to a human doing something.
What actually fixes it
  • • Require a baseline measurement and an attribution step before approving performance work — make "we do not know yet" an acceptable status.
  • • State the expected improvement quantitatively before shipping, so an unexpected result is recognisable as a signal that the model is wrong.
  • • Track metric cardinality as an operational metric and alert on growth, before the backend degrades ([[cardinality]]).
  • • Prune dashboards and alerts on a schedule, judged by whether each panel answers a triage question and whether each alert has ever led to an action.
  • • Revert optimizations that cannot demonstrate a measured benefit, rather than keeping them because reverting feels risky.
How you know it worked
  • • The change moves the target metric by roughly the predicted amount, compared against the same window on a comparable day.
  • • The attribution shifts as predicted: time leaves the component you optimized and appears in the next constraint ([[bottleneck-migration]]).
  • • Dashboard pruning is validated by timing a triage drill — how long does it take a responder to answer the six questions?
  • • Alert pruning is validated by the alert-to-action ratio improving without any incident going undetected.
What it costs
  • • Requiring measurement before action genuinely slows the response, and during a severe incident a fast guess with a cheap rollback can be the right call.
  • • Pruning dashboards and alerts risks removing the one panel that matters next quarter; keep them on a secondary page rather than deleting outright.
  • • Cardinality limits reduce the dimensions available for debugging exactly when a novel problem needs a novel dimension.
  • • A decision log is administrative overhead that only pays off across quarters and staff turnover, which makes it easy to abandon.
Stop it coming back
  • Add a CI benchmark or load-test assertion for the specific property you fixed, so the regression fails a build rather than a customer (Regression or Tuesday? Telling a Real Change from Noise).
  • Alert on cardinality growth, log volume growth and cost per request — the three costs that grow silently until they are expensive.
  • Keep a short performance decision log: what was measured, what was changed, what was expected, what happened. It is what stops the next team repeating a change that did not work.
  • Revisit dashboards after every incident: what did the responder need that was not there, and what did they scroll past?

Accuracy

Performance numbers are conditional. These are the conditions.

What these numbers depend on
  • ILLUSTRATIVEThe ticket examples and their outcomes are constructed to show the reasoning gap. Whether any specific technique here is an anti-pattern depends entirely on whether its precondition was checked.
  • WORKLOAD-SPECIFICSeveral entries — caching, indexes, worker counts — are excellent engineering in the right context. They appear here as anti-patterns only when applied without establishing that the constraint they address is the one binding.

Misconceptions

Claim
“These are beginner mistakes.”
Reality
They cluster in experienced engineers under time pressure, because experience supplies plausible techniques quickly and pressure removes the time to check preconditions. The junior engineer who says "I do not know where the time goes" is closer to correct.
Claim
“Adding an index is safe even if it does not help.”
Reality
Every index costs write throughput, storage and planner time, and an unused index is a permanent tax with no benefit. "Safe" changes with recurring costs and unmeasured benefits are how systems accumulate drag (An Index Scan Is Not Automatically Faster).
Claim
“More observability is always better.”
Reality
Observability has cost and a signal-to-noise ratio. Past a point, additional metrics, dashboards and alerts reduce the ability to answer questions quickly — which is the only thing observability is for.

Apply it