“Finance wants Q3's checkout latency for the quarterly review. Prometheus keeps 15 days. And last week's restart left a 20-minute hole.”
case name: The data we no longer had
- Service
- metrics storage
- Impact
- No historical data for capacity planning or SLO reporting; gaps in every graph after restarts
- Detected by
- A request from Finance
- Time to resolve
- An afternoon to set up remote storage
Skills you'll use on this case
remote_writeGrafana MimirHA pairs and deduplicationrecording rules for long-term KPIs01 The investigation
- Mon
REPORT
'Can we get p99 checkout latency per month for July, August, September?'
- Mon
QUERY
How far back does Prometheus go?
PromQL· Prometheus(time() - prometheus_tsdb_lowest_timestamp_seconds) / 86400result{instance="localhost:9090", job="prometheus"} 15.0 - Mon
DEAD END
'Just set --storage.tsdb.retention.time=400d'
It works until the disk fills, the one server dies (and takes 13 months of history with it), or queries over months of raw data time out. Prometheus's local TSDB is designed to be fast and simple for recent data on one node. It isn't durable, clustered storage.
- Mon
FINDING
The gap: one Prometheus, one restart, 20 minutes of nothing
checkout req/s — note the holereq/s - Tue
RESOLVED
Two Prometheus replicas remote-write to Mimir with 13-month retention; Grafana queries Mimir
Root cause
All metrics lived only on a single Prometheus server's local disk, with 15-day retention and no redundancy. That's the right design for alerting on recent data, and the wrong one for history, durability, or availability.
02 The concepts behind it
Remote write
Prometheus can stream every sample it ingests to a remote endpoint (remote_write) while still keeping local data for fast queries and alerting. The remote system (Grafana Mimir, Thanos Receive, Cortex, VictoriaMetrics, Amazon Managed Prometheus, Grafana Cloud) stores data durably in object storage, scales horizontally, keeps it for months or years, and serves PromQL.
High availability: two identical scrapers
Run two Prometheus replicas with identical configs, differing only in an external label (replica="a" / "b"). Both scrape and both evaluate alerts, and Alertmanager deduplicates the alerts. Both remote-write; Mimir's HA tracker (or Thanos's query deduplication) keeps one replica's data and fails over when it stops. Restarting either replica no longer leaves a hole.
What to keep for how long
Raw, high-cardinality data is expensive to keep for a year and slow to query at that range. Record the KPIs you'll want long-term (SLI ratios, p99 per route per 5 minutes) as recording rules and keep those for 13 months; keep raw data shorter. Thanos can downsample old data to 5-minute and 1-hour resolution; with Mimir, recording rules or per-tenant retention serve the same purpose.
03 The fix
01Add Mimir to the lab (monolithic mode, local filesystem)
Monolithic mode runs every Mimir component in one process: perfect for a lab. In production it runs as separate, scaled components backed by S3 or GCS.
obs-lab/mimir/mimir.ymlwhole fileyaml # compose: # mimir: # image: grafana/mimir:2.16.0 # command: ["-config.file=/etc/mimir.yml"] # volumes: ["./mimir/mimir.yml:/etc/mimir.yml:ro"] # ports: ["9009:9009"] multitenancy_enabled: false server: http_listen_port: 9009 log_level: warn common: storage: backend: filesystem filesystem: { dir: /data/mimir } blocks_storage: storage_prefix: blocks tsdb: { dir: /data/ingester } ingester: ring: replication_factor: 1 kvstore: { store: inmemory } instance_addr: 127.0.0.1 distributor: ha_tracker: enable_ha_tracker: false # enable with a KV store (consul/etcd) for real HA pairs limits: compactor_blocks_retention_period: 400d accept_ha_samples: true02Remote-write from Prometheus, with replica labels
external_labelsidentify this Prometheus. Thecluster/__replica__pair is what Mimir's HA tracker deduplicates on. Then add a Grafana data source of type Prometheus pointing athttp://mimir:9009/prometheusand use it for long-range dashboards.obs-lab/prometheus/prometheus.ymladd to fileyaml global: scrape_interval: 15s external_labels: cluster: obs-lab __replica__: prometheus-a remote_write: - url: http://mimir:9009/api/v1/push queue_config: max_samples_per_send: 5000terminal$ curl -s -X POST localhost:9090/-/reloadcurl -s -G localhost:9009/prometheus/api/v1/query --data-urlencode 'query=count(up)' | jq -r '.data.result[0].value[1]'── expected output ──4
04 Make sure it never surprises you again
01Watch remote_write health
If remote write falls behind (network, a slow backend), samples queue in memory and are eventually dropped. Alert on the lag between the newest sample ingested and the newest sample sent, and on failed samples.
PromQL· Prometheus( prometheus_remote_storage_highest_timestamp_in_seconds - ignoring(remote_name, url) group_right prometheus_remote_storage_queue_highest_sent_timestamp_seconds ) > 120 or rate(prometheus_remote_storage_samples_failed_total[5m]) > 0
05 Your turn: write the query
Run each one against your lab before revealing the answer. Share your own version in the comments; there's usually more than one correct query.
Write a recording rule for p99 checkout latency at 5-minute resolution, suitable for keeping 13 months.
Two Prometheus replicas both evaluate ShopLiteDown. Why don't you get two pages?
06 Interview questions from this case
How do you scale Prometheus for long-term storage and high availability?
Thanos vs Mimir in one sentence each?