An anomaly detection job that is too noisy to be useful
You deploy an ML anomaly detection job over logs-* (150M docs/day) with a date_histogram-style datafeed on @timestamp, bucket_span: 5m, and a single high_mean detector on response_time_ms partitioned by service.name. There are 40 services, and service.name was also added as an influencer along with host.name (1,200 hosts) and url.path (30,000 distinct paths). The job's model memory limit is 512 MB. After 48 hours: job memory status is hard_limit, results are dominated by url.path anomalies on rarely seen paths, and the team wants to abandon the feature. /api/checkout has steady traffic; /api/search has a hard daily traffic pattern and a 20-minute outage each Sunday.
1. Why is model memory exhausted, and what would you change about the job configuration before touching the memory limit?
Read the worked answer
Model memory is driven by how many entities get their own statistical model, not by data volume. Memory is allocated per partition_field_value (and per detector), times the models needed for each detector function. Here it is 40 services × 1 detector - that part is fine. The damage comes from having url.path as an influencer with 30,000 distinct values plus 1,200 hosts: influencers are tracked in each bucket's model, and a very high-cardinality influencer bloats every model plus the per-bucket aggregation state.
Order of operations:
- Fix the configuration first. Raise the memory limit only after the model count is sane; otherwise you are buying time on a design that still produces noise.
- Drop
url.pathas an influencer. 30,000 values over a 5-minute bucket is mostly singletons; rare-seen values generate spurious anomalies by construction. If path-level behaviour matters, run a separate job keyed by apartition_fieldover a filtered set of paths (e.g. aquery_delay-safe filter or an ingest-timepath_groupkeyword with a few dozen values). - Keep
service.nameas thepartition_field(it already is), and demote it from the influencer list - a partition field does not need to also be an influencer, and duplicating it inflates state. - Drop
host.nameas an influencer unless hosts are the unit of interest; 1,200 values in every model is expensive for marginal signal. - Then check
GET _ml/anomaly_detectors/<job>/_statsformodel_size_statsand memory status; setmodel_memory_limitfrom observedpeak_model_byteswith headroom, not from a round number.
Finally, note that a 5-minute bucket over 150M docs/day is cheap for search but yields 288 buckets/day - modelling daily seasonality in response_time_ms needs far more than 48 hours of history before it can distinguish "normal Sunday" from "anomaly".
2. `/api/search` has a 20-minute Sunday outage. What detector configuration detects it, and what will it wrongly flag?
Read the worked answer
A single high_mean detector on response time will smear a 20-minute event across 5-minute buckets and, worse, cannot tell "traffic stopped" from "everything is fine", because a throughput drop does not raise the mean response time.
Design:
- Two detectors, not one. Keep
high_mean(response_time_ms)for latency, and addlow_count(orlow_non_zero_countif empty buckets should be ignored) on the event rate so the outage is caught as a drop in volume. Alow_non_zero_countavoids treating a quiet-but-alive service as an outage. bucket_span. At 5m a 20-minute outage spans 4 buckets - detectable, but the model needs the bucket to be long enough that normal traffic produces a stable count. 5m is defensible for 150M docs/day; do not go below it just to sharpen the outage edge.detector_descriptionshould state the unit and direction so operators read it correctly.- Daily seasonality requires the job to have seen multiple full weeks. Until
daily/weeklycomponents are populated (analysis_config.detectors[].detector_descriptionplus_statsmodel_size_stats), the job will flag the ordinary daily peak.
What it will wrongly flag:
- The Sunday outage window itself creates a step change at both edges; expect an anomaly at the recovery bucket too, since the model must re-learn after a gap.
- Services with sparse traffic will produce
low_countanomalies continuously - this is the same cardinality trap asurl.path. Only enablelow_countfor services whose baseline count is comfortably above the model's minimum, or partition out the sparse ones. - Any deliberate deploy that shifts latency upward by a small, sustained amount will be flagged once and then absorbed - that is correct behaviour, but the team should be told so they do not chase it.
3. The datafeed produces "no data" warnings and some buckets are consistently shifted. What would you check, and how do you validate the whole job before re-enabling it in production?
Read the worked answer
"No data" / shifted buckets almost always come from the gap between event time and index time:
- Check
datafeed_config.query_delay. If documents arrive later than the job assumes, the datafeed looks at a window with no documents yet, logsNo datafeed data received, and later re-processes. Setquery_delayfrom observed ingest lag (p99 ofindex_time - @timestamp), not from a guess. - Check
frequencyandchunking_config. Default chunking is fine at 150M docs/day; amode: manualchunk size that is too small produces many small searches and can time out. - Check that the datafeed's index pattern and the analyzer's
time_fieldpoint at the same field and that it is adatemapped as an epoch/ISO value. A keyword-typed timestamp silently yields no buckets. - Confirm the cluster timezone assumptions are irrelevant - ML bucketing is on UTC epoch, so a dashboard in Europe/Berlin will look shifted if it re-buckets by local day. Verify by comparing
bucket.timestamp(epoch ms) with the raw documents.
Validation sequence before production:
- Run with
"analysis_limits": {"model_memory_limit": ...}set from a historical replay over 2–4 weeks, not live tail only:datafeed_configwithstart/endon a task,wait_for_completion. - Inspect
_stats:model_size_stats.model_bytesvs limit,memory_status, anddata_counts.invalid_dates_count/skipped_empty_buckets. - Review
_results/recordssorted byrecord_scoreand confirm the top records correspond to events operations actually recognises. If the top 50 records are all rare-path noise, the config is still wrong - fix the influencers rather than loweringanomaly_score_threshold. - Establish a baseline period (
delete/re-create the job) so the model has not been contaminated by the noisy 48 hours, then promote it and set acustom_rules/job_idalert onrecord_score > thresholdfor the on-call channel. - Only then raise
model_memory_limitif the replay shows genuine peak usage near the cap.