DocsGuides

Guides

Time-series baselines and anomaly detection

5 min readReviewed July 2026

An anomaly is a value that differs materially from an explicit expectation. It is not simply a point that looks surprising on a chart.

GraphJSON can query historical time series, visualize comparisons, and alert on supported collection visualizations. It does not currently train or evaluate a built-in statistical anomaly model over arbitrary saved SQL.

Use one of two patterns:

GraphJSON SQL → investigate and compare with an explainable baseline

external evaluator → emit anomaly measurement
                   → GraphJSON dashboard and supported threshold alert

Keep urgent paging, acknowledgment, and escalation in the monitoring system that owns the response.

Define the signal before the model#

Record:

Field Example
Metric Completed report generations
Grain 15-minute UTC bucket
Population Production, non-test accounts
Unit Successful workflows
Expected seasonality Time of day and day of week
Settling delay 10 minutes
Minimum denominator 100 attempts
Owner Reporting platform
Response Check ingestion, deployment, queue, and provider health

If no decision or investigation follows an anomaly, do not create an alert.

Distinguish anomaly types#

Type Example
Level shift Conversion falls from 32% to 24% after a release
Spike Webhook failures rise for one 15-minute interval
Trend change Queue latency grows over several days
Seasonal deviation Monday signups are low relative to prior Mondays
Missing signal Evaluator or producer stopped sending
Composition shift Total is stable but one plan or region changes
Data-quality anomaly Event volume rises because of duplicate delivery

The response differs. A missing signal should not be treated as a healthy zero, and a duplicate spike should not trigger a product-growth celebration.

Start with direct thresholds#

Use a fixed threshold when the acceptable boundary is known:

payment failure rate > 2%
oldest queue age > 15 minutes
data delivery lag > 10 minutes
checkout completion rate < 20%

A business or reliability boundary is easier to explain and test than a statistical score.

Use GraphJSON’s native alert surface for supported Single Line collection visualizations. Review the evaluation and notification limitations in Create and operate alerts.

Compare with the previous period#

For a first exploration, compare equivalent buckets:

WITH daily AS (
  SELECT
    toDate(toDateTime(timestamp)) AS day,
    count() AS value
  FROM product_events
  WHERE
    JSONExtractString(json, 'event') = 'report_published'
    AND timestamp >= toUnixTimestamp(now() - INTERVAL 60 DAY)
  GROUP BY day
)
SELECT
  current.day,
  current.value,
  previous.value AS value_7_days_ago,
  current.value - previous.value AS absolute_change,
  if(
    previous.value = 0,
    NULL,
    (current.value - previous.value) / previous.value
  ) AS relative_change
FROM daily AS current
LEFT JOIN daily AS previous
  ON previous.day = current.day - INTERVAL 7 DAY
ORDER BY current.day

Same-weekday comparison accounts for weekly seasonality better than comparing Monday with Sunday. It still fails around holidays, launches, outages, and rapid growth.

Never divide by zero and label the result an infinite anomaly. Return a documented no_baseline state.

Use several comparable periods#

A more stable weekly baseline can use the same weekday from several prior weeks:

expected = median(value 7, 14, 21, and 28 days earlier)

Median resists one abnormal historical period better than a mean. Also retain:

  • number of baseline observations
  • minimum and maximum
  • median absolute deviation or another dispersion measure
  • which comparison dates were used

Do not calculate a baseline from periods that include the value being tested. That reduces the apparent deviation.

Add a robust deviation score#

For a baseline median m and median absolute deviation MAD:

robust_score = 0.6745 × (observed - m) / MAD

This is a screening score, not a probability. Handle:

MAD = 0 and observed = median → score 0
MAD = 0 and observed differs → explicit zero-variance deviation
too few baseline periods → insufficient_baseline

Do not claim “99% confidence” from a score without a statistical model and validated assumptions.

For ratios, retain the numerator and denominator. A conversion-rate swing from one conversion out of two visitors is not comparable to a swing over 100,000 visitors.

Treat missing and zero separately#

Possible bucket states:

observed_zero
no_eligible_events
source_incomplete
evaluator_missing
not_applicable

Generate a complete time spine when you need to reveal missing buckets, then left join the observed values. Do not use coalesce(value, 0) until the metric contract says an absent row means a real zero.

A stopped heartbeat, rollup job, or ingestion pipeline is often more urgent than an unusual value. Monitor freshness independently:

{
  "event": "metric_evaluation_completed",
  "metric": "checkout_completion_rate",
  "window_end": 1785081600,
  "computed_at": 1785081900,
  "data_complete_through": 1785081600
}

Account for seasonality and change#

Potential baseline dimensions:

  • hour of day
  • day of week
  • business day vs weekend
  • holiday or planned closure
  • plan, region, platform, or release
  • product maturity and sustained growth

Do not create a separate model for every high-cardinality segment. Require enough observations and a clear response owner.

When the product grows rapidly, a four-week fixed baseline can lag the current level. Compare rates, use shorter recent periods, or model a trend explicitly. Document when the baseline was reset.

Add annotations to the investigation#

Correlate anomalies with:

  • releases and feature rollouts
  • provider incidents
  • schema and instrumentation changes
  • campaigns
  • holidays
  • billing or pricing changes
  • backfills and replays

Send bounded change events such as:

{
  "event": "analytics_annotation",
  "annotation_type": "release",
  "release": "2026.07.26.3",
  "service": "checkout",
  "change_id": "deploy_91"
}

Do not use free-form annotation text as a chart split. Keep details in the deployment or change-management system.

Investigate in a fixed order#

When a signal is unusual:

  1. verify freshness and completeness
  2. check duplicates, schema changes, and collection routing
  3. inspect numerator and denominator
  4. compare the same segment and time definition
  5. check release and provider annotations
  6. drill down by a few approved dimensions
  7. sample stable event IDs
  8. compare with an independent source
  9. decide whether the product, pipeline, or expectation changed

Do not tune the threshold before explaining the event. Repeated unexplained alerts are evidence that the signal, baseline, or response is poorly designed.

Emit evaluated anomalies#

When a scheduled evaluator owns the baseline:

{
  "event": "metric_anomaly_evaluated",
  "measurement_id": "checkout_rate:15m:1785081600:v3",
  "metric": "checkout_completion_rate",
  "window_start": 1785080700,
  "window_end": 1785081600,
  "observed": 0.182,
  "expected": 0.311,
  "lower_bound": 0.264,
  "upper_bound": 0.356,
  "deviation_score": -4.2,
  "numerator": 182,
  "denominator": 1001,
  "baseline_version": 3,
  "baseline_periods": 8,
  "status": "anomalous",
  "data_complete_through": 1785081600,
  "computed_at": 1785081900
}

Use a deterministic measurement ID and select one result per window. Keep model artifacts, scheduling, and recovery state outside GraphJSON.

A supported Single Line visualization can alert on deviation_score, observed, or another numeric field. Also monitor whether the evaluator continues to emit results.

Follow Derived events and scheduled rollups for versioning and restatement.

Evaluate the detector#

Before relying on it:

  • replay known incidents
  • inject missing and zero buckets
  • test low denominators
  • test a holiday and a planned release
  • measure false positives and missed incidents
  • verify the investigation is actionable
  • exercise evaluator failure
  • confirm alert recovery behavior

Review detectors after product growth, instrumentation changes, and seasonal shifts. A baseline is a versioned operational definition.

Launch checklist#

  • The metric, grain, population, unit, and owner are explicit.
  • Fixed thresholds are preferred where a real boundary exists.
  • Baseline periods are comparable and exclude the observed point.
  • Missing, zero, and incomplete states remain distinct.
  • Ratios retain numerator and denominator.
  • Seasonality, holidays, releases, and backfills are visible.
  • Statistical scores are not mislabeled as probabilities.
  • The evaluator has a deterministic version and freshness monitor.
  • Alerts have an actionable response and independent paging where needed.
  • Detector quality is reviewed against known incidents.

Use Targets, forecasts, and plan-versus-actual reporting when the question is whether performance met an approved plan or forward estimate. A target miss and an anomaly are different analytical states.

After a detector finds a meaningful movement, use Metric-change decomposition and root-cause analysis to reproduce the top line, rule out measurement defects, quantify segment contributions, separate mix shifts, and connect the result to evidence.

Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support