Every alert you write leans on a baseline. Maybe you set a threshold at 200ms p99 because that's what your latency looked like last quarter. Maybe you used a tool that 'automatically' picks a baseline from the last two weeks of data. Either way, you're betting that the numbers from yesterday tell you something useful about tomorrow.
But baselines have a dirty secret: they're never really stable. Code changes, traffic shifts, infrastructure mutations—all of them tug at the baseline. And most teams don't notice until the alerts start firing at 3 AM for reasons nobody can explain. This article is about how to think about baselines without the marketing fluff. We'll look at where they show up, what people get wrong, patterns that hold up, and when you should just skip the math and use your gut.
Where Baselines Actually Show Up in Your Work
Alert thresholds in production
You're staring at PagerDuty at 3 AM. Again. The alert fired because your 99th-percentile latency crossed 500 ms — but that threshold came from a baseline computed six months ago. Your traffic has since doubled and shifted to a different cloud region. The baseline is wrong. The catch is, nobody remembers who set it, or whether that 500 ms number was aspirational or empirical. I have seen teams waste weeks debating numbers that were essentially pulled from a single Slack poll. That's where baselines show up first: inside alert conditions that nobody questions until they wake up at 3 AM.
Prometheus rules, Datadog monitors, and Grafana alert conditions all rely on some concept of "normal." The problem is that most baselines are static snapshots — last month's p95, a hand-wavy 80% CPU, a fixed error budget of 0.1%. Static baselines are cheap to configure but expensive to maintain. They drift silently. And when they break, they break at night.
“We tuned our alerts on Monday. By Thursday the dashboards were screaming false positives. Nobody touched code. The traffic pattern just changed.”
— Staff engineer, e-commerce observability team
That's the real trap: the baseline works until it doesn't. Then you burn a sprint re-calculating it.
Dashboard reference lines
Dashboards are the second place baselines creep in. A horizontal line at "2,000 requests/sec" looks authoritative. But where did it come from? Was it the peak of last Black Friday, or a theoretical capacity limit from a load test that never matched production? Wrong order matters here. A baseline that's too optimistic hides slow degradation. One that's too pessimistic trains your team to ignore it. The tricky bit is that dashboard baselines often outlast the engineers who chose them. You inherit a red line, a green zone, and no explanation. That hurts.
Most teams skip this: annotating the *why* behind a baseline. I have fixed this by adding a single comment field in the dashboard JSON — "set from peak load test 2023-11-29, 60% headroom assumed." Without that, the next person will guess. Or worse, they will leave it untouched for two years.
SLO burn-rate targets
Service level objectives add a new kind of baseline: the burn rate. You set a target of 99.9% availability over 30 days. That implies a specific error budget consumption per hour. The baseline here is a calculated slope — not a static number, but a rate. What usually breaks first is the window length. A 30-day SLO smooths out spikes, so you miss fast degradation. A 1-hour window catches everything but alerts too often. The trade-off is brutal: short windows produce noise; long windows hide fires. The baseline you choose determines which failure mode you accept.
One concrete anecdote: a team I worked with used a 5-minute burn-rate alarm based on a 99.99% SLO. The math was correct. But their baseline assumed a Poisson arrival pattern — and their traffic arrived in bursts every 90 seconds. The alarm fired every third burst. They reverted to a static threshold within a week.
Capacity planning models
Capacity planning is where baselines become predictions. You look at last month's peak CPU, multiply by 1.5, and call it the ceiling for next quarter. That works until a marketing campaign doubles traffic overnight. The baseline you used for scaling decisions is suddenly a constraint. I have seen teams overprovision because their baseline included a single anomalous spike from a deployment rollback. And I have seen teams underprovision because their baseline excluded weekend patterns. Capacity baselines need to separate *sustained* growth from *transient* blips — most tools don't do this automatically. You have to decide: is that 2x CPU burst noise or a new normal? The answer changes your infrastructure cost by thousands.
Honestly — most data posts skip this.
The Foundations People Confuse
Median vs. Percentile Baselines
The median is seductive. It gives you a single number, easy to plot, easy to explain. But a median baseline treats every spike as noise and every dip as a glitch to ignore. I have watched teams set p50 latency thresholds and then wonder why their p99 users are screaming. The catch is—medians hide everything that matters in tail latency, error bursts, or memory exhaustion that only hits 5% of requests. Percentile baselines (p90, p95, p99) cost more compute, require more data, and still trick you if you pick the wrong percentile for the signal. A p99 baseline on CPU usage? That catches the scheduler hiccup. A p99 baseline on a login endpoint? You just committed to alerting on every user's flaky coffee-shop wifi. Wrong order.
Static Thresholds vs. Dynamic Baselines
Static thresholds feel safe. You hard-code 80% disk, 200ms latency, 5% error rate. They never drift, never surprise you—until the Tuesday your team ships a new feature that shifts normal latency from 150ms to 190ms. Now every page load triggers a page at 2am. The team reverts the feature. That hurts. Dynamic baselines—rolling averages, exponential smoothing, seasonal decomposition—adapt for free. But they also adapt to problems. If your errors slowly climb from 0.5% to 2.3% over four weeks, a rolling 7-day baseline treats that new normal as healthy.
“A dynamic baseline that chases degradation is just static ignorance in slow motion.”
— engineer post-mortem, internal ops review
The trade-off is brutal: static thresholds miss slow drifts; dynamic baselines absorb them. Most teams skip the step where you pin the baseline to a known-good window (last Tuesday, not the last seven days including Tuesday's outage). Worth flagging—a hybrid approach (static floor + dynamic ceiling) often works better than either alone, because it catches both a sudden 300ms jump and a slow crawl past 1000ms.
Seasonal vs. Rolling Windows
Rolling windows are the default—last 4 hours, last 7 days, last 30 minutes. They work fine for flat traffic. Then comes Black Friday. Or a marketing tweet at 3pm. Or cron jobs that run every hour on the hour. A rolling window that spans only the last 4 hours will miss the pattern that repeats every Monday at 8am. Seasonal baselines (same hour yesterday, same day last week) catch those cycles. The pitfall: seasonality assumes the pattern holds. When your user base shifts timezones, or daylight savings hits, or you deploy a new cache layer that flattens the afternoon peak, the seasonal baseline screams false alarm for three days. That's three days of alert fatigue. The fix is boring but real—use a rolling window as the fallback and let the seasonal model decay faster than you think you need. And never, ever set both to trigger independently. The seam blows out: you get duplicate alerts for the same event, once from the seasonal model, once from the rolling model, and your on-call ignores both.
Patterns That Usually Work
Use multiple time windows
A single baseline window—say, the past seven days—will fail you. I have watched teams set a rolling 24-hour mean for latency, then hit a holiday weekend where traffic drops 40%, and every alert fires because Monday morning looks like an anomaly against Sunday. The fix is boring but effective: run three concurrent windows. A short window (last 4 hours) catches sudden spikes. A medium window (last 7 days) absorbs weekly cycles. A long window (last 30 days) holds the historical norm. The trade-off is complexity—you now have three thresholds to tune, and when they disagree, which one wins? Most teams pick a majority vote or take the most conservative bound. The catch: if your data has sharp step-changes (deploy, feature flag flip), all three windows lag behind. You get false negatives for a few hours until the short window catches up. Worth flagging—this pattern works best on stable, mature services. Early-stage products change too fast; three windows just give you three contradictory opinions.
Layer seasonal adjustments
Hour-of-day patterns kill naive baselines. CPU usage at 2 AM versus 2 PM is not noise—it's structure. The standard move is to decompose time-series into trend, seasonality, and residual components. You model last Tuesday's 3 PM behavior against the same slot in prior weeks, not against Tuesday midnight. That sounds fine until you hit daylight saving shifts or a regional holiday that moves the pattern two hours left. I have seen a team's p99 latency baseline snap in half because their seasonal model assumed Monday always looks like Monday—but the first workday after a long weekend is a different beast. The trick is to layer holiday calendars and exception windows on top of the seasonal model. Most observability platforms let you tag dates; use them. The trade-off is maintenance—every time you add a new timezone or a new business hour rule, the seasonal layer needs updating. Skip that, and your baselines drift silently. A rhetorical question worth sitting with: would you rather update a YAML file once a quarter or chase false alarms every Monday morning?
Combine with anomaly detection sparingly
Anomaly detection sounds like the magic glue—let the machine figure out what's weird. In practice, pairing a statistical baseline with an unsupervised model often doubles your noise. The model flags a shape that the baseline already caught, or worse, they cancel each other out: the baseline says "normal," the model says "anomaly," and nobody trusts either. The pattern that actually survives production is simple: use the baseline as the gatekeeper, then let anomaly detection annotate—not alert. Show an anomaly score beside the baseline threshold in your dashboard. Engineers can glance and say, "Oh, the baseline is green but the model sees a weird pattern—maybe a slow leak." That's a conversation starter, not a pager event.
'We stopped using anomaly detection to fire alerts. We use it to explain why the baseline might be wrong.'
— Senior SRE, after reverting three different ML alert models in six months
The trade-off is human attention: you now have two signals to interpret instead of one. But the false-positive rate drops because you stop alerting on every statistical outlier—only on those that both the baseline and the anomaly model agree are worth waking someone up. A final edge: if your data has long flat periods (nighttime batch jobs, weekends in B2B apps), anomaly detection will see patterns where none exist. Clip those windows out before you feed data into the model. Otherwise you spend Monday mornings explaining why Tuesday's 3 AM flatline was "unusual."
Anti-Patterns That Make Teams Revert
One-size-fits-all windows
Teams default to a 7-day rolling window because it feels safe. That feels reasonable until your application has a weekly batch job that spikes every Tuesday at 2 p.m. — the baseline swallows the spike whole. One engineer I worked with spent three weeks tuning anomaly detectors only to discover the window was averaging legitimate bursts into the threshold. The alert never fired, until a real outage looked identical to the batch job, and nobody noticed for hours. Seven days works for a SaaS dashboard with flat traffic. For anything cyclical, seasonal, or event-driven? It blinds you.
The fix is not a longer window. The fix is multiple windows — a short one for fast-moving signals (latency p99, error count) and a longer one for gradual shifts (memory, disk). Even then, you must check whether the window aligns with your deployment cadence. Deploy every two days? A 7-day window might mix three different code states into one baseline. That's not a baseline. That's a blur.
'A single window size is a promise that your traffic pattern never changes. That promise is almost always wrong.'
— SRE lead, after reverting a full baseline system
Flag this for data: shortcuts cost a day.
Ignoring code deploys in baseline calculations
Most tools let you filter out deploy-related data. Most teams don't. The result: your baseline includes the 5-minute spike when a bad config pushed error rates to 12%, then the recovery, then the follow-up hotfix. Your threshold now sits at 8% — which means the next deploy that pushes error rates to 6% looks normal. It's not normal. It's the beginning of the same pattern. I have watched teams chase phantom degradations for two weeks before someone realized the baseline had been poisoned by the last incident.
The fix requires tagging. Tag every deploy with a version label, then exclude deploy windows from the baseline calculation. That sounds obvious. Almost nobody does it on the first pass. The catch is that tagging adds pipeline work — and when the pipeline breaks, the tags stop flowing. Then you're back to the poisoned baseline, wondering why your pager never goes off.
Worth flagging—if you exclude deploy windows, don't exclude them forever. The new code *is* the new normal after a few hours. The trick is separating the rollout instability from the steady state. Rollback the deploy? Then exclude the whole window. Keep it? Let the baseline adapt after one full deploy cycle of clean data. That means you need a window that resets, not a window that forgets.
Setting it and forgetting it
The most common anti-pattern: configure baselines once, then never touch them. Teams treat baseline tuning like setting a thermostat — pick a number, walk away. But signals drift. Code changes. User behavior shifts when you release a feature that doubles page load time. The baseline doesn't know it should shift too. It just keeps averaging yesterday’s numbers, and soon your pager is silent while your customers are staring at a spinner.
Drift is especially nasty for percentile-based baselines (p99 latency, for example). A 1% slow-cohort creep can raise the p99 by 200ms over three months without triggering any alert — because the baseline drifted with it. That's the quiet killer. No incident, no rollback, just a slow erosion of user experience that nobody notices until the NPS survey arrives.
What usually breaks first is the weekly review. Teams schedule a baseline recalibration every Monday, then skip it for three weeks, then forget. Instead, automate the recalibration check: compare last week’s baseline to the week before. If they differ by more than 15%, flag it for human review. That forces a conversation. “Why did our p99 move 20ms? Did we ship something? Did a dependency degrade?” That conversation stops the drift before it becomes the new normal.
Rhetorical question: how many alerts have you missed because a baseline silently accepted worse performance as the new standard? Exactly. Set a calendar reminder to review baseline windows every two weeks. Not to change them — to ask whether they still match reality. If they don't, tweak the window size, exclude a stale deploy tag, or reset the calculation entirely. That ten-minute check saves you from the three-hour revert next month.
Maintenance, Drift, and Long-Term Costs
Baseline recalibration cycles
You set your latency baseline in January. By March it's useless. That's not sloppy work—it's entropy. I have watched teams pour hours into a perfect seasonal model, only to see it degrade within six weeks because a microservice deployment shifted response times by 30 milliseconds. The math is brutal: every parameter you estimate (mean, variance, daily peaks) decays at a different speed. Most teams recalibrate quarterly and regret not doing it monthly. Worth flagging—the tools that automate re-estimation often hide the cost. They run at 2 a.m., consume credits, and nobody notices until the alert firehose returns. Then you recalibrate again, chasing your own tail. The catch is that shorter cycles mean more noise in the training window. One bad data day can corrupt an entire month's baseline. So you end up babysitting the recalibration logic itself, which defeats the point of automating it.
Storage and compute overhead
Every baseline you keep eats disk. Every recalculation eats CPU. That sounds trivial until you have 400 services each with 6 metrics, each storing 90 days of granular histogram data for seasonal decomposition. I have seen observability bills double after a team “just turned on dynamic baselines.” The hidden tax is the join cost: your alerting system fetches the baseline from one store, the live metric from another, compares them, and all of that must finish before the evaluation window closes. That hurts. Most teams I talk to discover the storage problem only after their retention policy starts silently truncating history—then the baseline recalculates using 14 days instead of 90, and false positives explode.
Team cognitive load from false positives
A bad baseline doesn't just waste compute. It wastes attention. One pager at 3 a.m. that turns out to be a stale baseline—that erodes trust. Three more in a week and your on-call starts muting the alert. Six weeks later, the real outage arrives, and nobody noticed because the signal was buried under baseline noise. That's the real maintenance cost: not the cloud bill, but the human habit of ignoring your monitoring.
“We spent three months tuning baselines perfectly. Then we spent six months un-tuning them because nobody believed the alerts anymore.”
— Senior platform engineer, post-mortem retrospective
Reality check: name the quality owner or stop.
The tricky bit is that cognitive load compounds. Each false positive trains your team to ask “is this real or is it the baseline again?” That second-guessing burns seconds that compound into hours. You can measure it—track how many alerts get acknowledged without investigation, then watch that number climb after a drift event. The fix isn't more automation; it's ruthless pruning. Delete baselines that haven't fired a valid alert in 30 days. Freeze models during known deployment windows. Accept that a baseline that needs weekly human oversight isn't a baseline—it's a part-time job. Our next section will tell you exactly when to skip the formal baseline altogether and save yourself the headache.
When You Shouldn't Use a Formal Baseline
Low-traffic services with sparse data
Formal baselines need signal mass. A service that processes twenty requests an hour — or five hundred on a weird Tuesday, then zero for two days — can't produce a statistically meaningful profile. The math simply won't converge. I have seen teams spend three weeks tuning a baseline for a cron job that runs once daily, only to watch it fire alerts every single morning. The model had no pattern to lock onto, so it declared every execution anomalous. The fix was not a smarter algorithm. The fix was turning off the baseline entirely and using a static threshold: "If this job runs longer than ten seconds, page someone." Simple. Cheap. Correct.
The catch is that many monitoring platforms default to dynamic baselines for every metric they ingest. You might not even know a formal baseline is being computed against your sparse data. That silent computation generates a false sense of precision — a pretty chart with a shaded band that means nothing. What usually breaks first is operator trust: after the third night of waking up for a phantom anomaly, engineers start silencing the alert. Then they ignore the dashboard. Then the actual incident happens, and nobody looks.
One-off experiments
Experiments are, by design, not steady state. A load test, a canary deploy, a chaos engineering exercise — these produce signals that look like failure to a baseline trained on normal operation. But they aren't failure. They're deliberate stress. Formal baselines can't distinguish "we broke the system on purpose" from "the system is breaking." That sounds fine until your baseline triggers a page during a Friday afternoon experiment, and your on-call engineer spends forty minutes debugging a test. You lose a day. The seam blows out.
Better approach: run experiments in a separate environment, or tag the metrics so the baseline can be suspended. Most teams skip this step. They deploy the experiment, the baseline screams, and someone mutters "we'll fix the tuning later." Later never comes. A baseline that can't be paused for planned disruption is not a safety net — it's a false alarm machine that burns goodwill every time it fires.
— field observation, SRE team at mid-scale SaaS provider
Systems undergoing rapid change
Your baseline is a photograph of yesterday. If your system looks nothing like yesterday — if you're migrating databases, rolling out a new protocol, or doubling your traffic — that photograph is useless. Worse, it's misleading. The baseline will see the new normal as an anomaly and generate alert after alert, each one a false positive. The team then faces a choice: silence the alerts (and lose visibility) or retrain the baseline daily (and lose the week). Neither option works.
I have watched a team burn three sprints trying to make baselines behave during a major Kubernetes migration. The cluster scaled up, scaled down, failed, retried — every pattern was new. The baseline never settled. Eventually they ripped it out, dropped in percentiles from the last six hours of raw data, and moved on. That's not defeat. That's recognizing that a formal baseline is a tool, not a religion. When the system is flux, the tool fails. Use sliding windows, use fixed thresholds, use your gut. Just don't use a formal baseline until the dust settles. Then — and only then — you can let it learn again.
Open Questions and FAQ
How long should a baseline window be?
Short answer: long enough to cover three full service cycles, short enough that last quarter's weirdness doesn't pollute today. I have seen teams pick seven days because it's a neat number — Monday through Sunday, clean calendar boundaries. Then their pager goes off every Saturday morning because the window still carries residual load from the previous weekend's deploy. The trick is picking a period that repeats naturally: one business week for SaaS endpoints, 28 days for monthly batch jobs, 72 hours for continuous streaming pipelines. Anything under 24 hours will chase noise. Anything over 90 days will smooth real change into a flat line that never alerts.
Most teams skip this: your baseline window should be sliding, not static. A fixed two-week window from March 1–14 will look great until April 15, when seasonal traffic shifts by 40%. Sliding windows — recalculated every hour, weighted toward recent data — keep the baseline honest. The cost is compute. The payoff is fewer 2 AM false alarms.
Should baselines include incident periods?
Include them and your anomaly detector learns that 500 errors are normal. Exclude them and you lose context about how the system behaves under stress. The pragmatic middle: keep incident data in the window but apply a decay factor so old outage spikes fade faster than normal traffic. Worth flagging — this means you can't simply grab the last four weeks of metrics and call it done. You need to tag incident intervals and let the baseline forget them on a curve. I once watched a team exclude all failure data for six months. Their baseline became a perfect, useless portrait of a system that never broke. False negatives spiked. The seam blows out when your pager stays silent during an actual degradation because the baseline never learned what "degradation" looks like.
'A baseline that doesn't know your worst days will lie to you on your best days.'
— field note from a production engineer, after three weeks of silent queue backpressure
When do you need multi-dimensional baselines?
Single-metric baselines work until your service fans out across regions, instance types, or customer tiers. If you monitor average latency across all hosts, a noisy neighbor in us-east-2 will shift the mean just enough to suppress alerts for a real degradation in eu-west-1. That hurts. Multi-dimensional baselines — per host, per region, per API endpoint — catch the small fires before they merge. The trade-off is cardinality explosion. Baseline per customer? You will drown in maintenance. Baseline per availability zone? Manageable. Baseline per deployment canary? Absolutely.
What usually breaks first is the team that builds twenty dimensions because they can, then discovers each dimension needs its own window tuning, its own decay policy, its own gap-filling logic. Not yet. Start with two dimensions: service and region. Add a third — endpoint group — only after you catch yourself saying "I wish I could separate read from write paths." That sentence is your signal. Not before.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!