Everything for Grafana in one place — pick a section below. 23 reviewed items across 4 content types, plus a hands-on tutorial.
Quick Answer
Grafana Alerting evaluates queries and expressions from data sources, then routes alert instances through notification policies to contact points. Good design groups related symptoms, alerts on user impact and burn rate, and uses silences for known maintenance without muting unrelated failures.
Detailed Answer
A fire alarm system should tell you which building is burning, not ring every smoke detector as a separate emergency. Grafana alerting needs the same grouping and routing discipline.
Grafana centralizes alert rules across metrics and logs so teams can manage detection and notification from one place. The goal is not more alerts; it is faster recognition of the failure a human can act on.
A rule runs a query, applies expressions or thresholds, creates alert instances based on labels, and sends state changes to notification policies. Policies match labels, group notifications, control timing, and deliver to contact points such as PagerDuty or Slack.
Production teams standardize labels like service, team, environment, and severity. They track alert volume, pages per service, pending duration, no-data behavior, and whether dashboards include deploy and incident annotations.
The subtle problem is label cardinality. If an alert creates one instance per pod, customer, or path, a single outage can flood the on-call channel and delay diagnosis. Alert labels should identify ownership and failure class, not every raw dimension.
Code Example
apiVersion: 1
contactPoints:
- orgId: 1 # Applies to the primary production organization.
name: sre-pagerduty # Sends urgent pages to the SRE escalation path.
receivers:
- uid: sre-pd # Stable receiver id for provisioning updates.
type: pagerduty # Uses the PagerDuty notifier integration.
settings:
integrationKey: ${PAGERDUTY_INTEGRATION_KEY} # Keeps the secret outside source control.Interview Tip
A junior engineer typically answers with the feature name and a happy-path command, but for a senior/architect role, the interviewer is actually looking for production judgment. Explain where the Grafana control plane stores intent, how changes are rolled out, what telemetry proves the system is healthy, and what failure mode creates the largest blast radius. Strong answers include rollback behavior, ownership boundaries, permissions, and the exact signal you would page on.
◈ Architecture Diagram
┌──────────┐
│ Signal │
└────┬─────┘
↓
┌──────────┐
│ Grafana │
└────┬─────┘
↓
┌──────────┐
│ Action │
└──────────┘💬 Comments
Quick Answer
Prometheus Alertmanager: production-grade, supports clustering, deduplication, silencing, and routing. Grafana alerting: unified across data sources, easier UI, but less mature for high-volume alerting. They can coexist.
Detailed Answer
- Alert rules defined in Prometheus (PromQL-based) - Alertmanager handles routing, grouping, deduplication, silencing - Supports clustering for HA (gossip protocol) - Mature, battle-tested at scale - Only works with Prometheus-compatible metrics
- Unified alerting across ANY data source (Prometheus, Elasticsearch, CloudWatch, Loki) - Visual alert rule builder in the Grafana UI - Multi-dimensional alerts (one rule, multiple series) - Contact points and notification policies (similar to Alertmanager routing) - Can use Prometheus Alertmanager as an external alertmanager
- Use Alertmanager when: All alerts are Prometheus-based, need clustering/HA for alerting, operating at very high scale (1000s of alert rules), team prefers config-as-code (YAML) - Use Grafana Alerting when: Need to alert on non-Prometheus sources (Elasticsearch, CloudWatch), prefer UI-based management, want unified alert management across all data sources
Together: Grafana can send alerts TO Alertmanager as an external backend, giving you Grafana's multi-source alert creation with Alertmanager's routing and deduplication.
Code Example
# Prometheus alert rule (rules.yml)
groups:
- name: slo-alerts
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) > 0.01
for: 5m
labels:
severity: page
annotations:
summary: "Error rate above 1%"
# Grafana alert rule (provisioning YAML)
apiVersion: 1
groups:
- orgId: 1
name: api-alerts
folder: SRE
interval: 1m
rules:
- uid: high-error-rate
title: High Error Rate
condition: C
data:
- refId: A
datasourceUid: prometheus
model:
expr: sum(rate(http_requests_total{status=~"5.."}[5m]))
- refId: B
datasourceUid: prometheus
model:
expr: sum(rate(http_requests_total[5m]))
- refId: C
datasourceUid: __expr__
model:
type: math
expression: $A / $B > 0.01Interview Tip
Show you know both systems and can articulate when each is appropriate. The hybrid approach (Grafana for multi-source alert creation, Alertmanager as backend) demonstrates architectural maturity.
💬 Comments
Quick Answer
Loki only indexes labels (not log content), stores compressed log chunks in object storage (S3/GCS). LogQL uses label matchers for stream selection and filter expressions for content search. This reduces storage costs by 10-50x vs Elasticsearch.
Detailed Answer
1. Promtail/Grafana Agent scrapes logs, adds labels (pod, namespace, container) 2. Distributor receives log entries, validates, and hashes labels to determine ingester 3. Ingester builds compressed chunks in memory, flushes to object storage (S3/GCS) 4. Index (label→chunk mapping) stored in DynamoDB/Cassandra/BoltDB
1. Query frontend splits query by time range 2. Querier finds relevant chunks using label index 3. Chunks are downloaded from object storage, decompressed, and grep-searched 4. Results aggregated and returned
- No full-text indexing — labels are tiny compared to full log content - Logs stored as compressed chunks in cheap object storage - Minimal index size (labels only, not every word) - Trade-off: Content search is slower (grep vs inverted index)
- Stream selector: {namespace="production", app="api"} (like PromQL label matchers) - Filter: |= "error" (contains), != "debug" (excludes), |~ "err(or)?" (regex) - Parser: | json or | logfmt to extract fields - Aggregation: count_over_time({app="api"} |= "error" [5m])
Code Example
# LogQL: Find errors in production API
{namespace="production", app="api-gateway"}
|= "error"
| json
| status_code >= 500
| line_format "{{.ts}} [{{.level}}] {{.msg}}"
# LogQL: Error rate over time (metric query)
sum(rate(
{namespace="production"} |= "error" [5m]
)) by (app)
# LogQL: Top 5 error messages
topk(5,
sum(count_over_time(
{app="api"} | json | level="error" [1h]
)) by (msg)
)
# Promtail config (scrapes Kubernetes pod logs)
server:
http_listen_port: 3101
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: podInterview Tip
The key insight is the indexing trade-off: Loki indexes labels (tiny), Elasticsearch indexes content (huge). This is WHY Loki is cheaper. Know LogQL syntax — it's increasingly replacing Elasticsearch in modern stacks.
💬 Comments
Quick Answer
Use Grafana provisioning (YAML files), Grafonnet (Jsonnet library), or Terraform grafana provider to define dashboards as code. Store in Git, deploy via CI/CD or ArgoCD.
Detailed Answer
1. Native Provisioning (YAML files): - Drop YAML config files in /etc/grafana/provisioning/ - Supports data sources, dashboards, alert rules, contact points - Dashboards referenced as JSON files on disk - Best for: Kubernetes deployments with ConfigMaps/Helm
2. Grafonnet (Jsonnet): - Programmatic dashboard generation using Jsonnet - Reusable components, templates, and libraries - Generates dashboard JSON from code - Best for: Teams with many similar dashboards, DRY principle
3. Terraform Provider: - grafana_dashboard, grafana_data_source, grafana_folder resources - State-managed, drift detection, plan/apply workflow - Best for: Teams already using Terraform, multi-instance management
1. Developer creates/modifies dashboard in Grafana UI 2. Export dashboard JSON, commit to Git 3. CI validates JSON schema and Jsonnet compilation 4. CD deploys via Helm chart with dashboard ConfigMaps 5. Grafana sidecar watches for ConfigMap changes and auto-loads
Code Example
# Provisioning data source (provisioning/datasources/prometheus.yaml)
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
jsonData:
timeInterval: 15s
exemplarTraceIdDestinations:
- name: traceID
datasourceUid: tempo
# Helm values for Grafana with sidecar
grafana:
sidecar:
dashboards:
enabled: true
searchNamespace: ALL
label: grafana_dashboard
folderAnnotation: grafana_folder
datasources:
enabled: true
# Dashboard ConfigMap (auto-loaded by sidecar)
apiVersion: v1
kind: ConfigMap
metadata:
name: api-dashboard
labels:
grafana_dashboard: "true"
annotations:
grafana_folder: "API"
data:
api-overview.json: |-
{ "dashboard": { ... } }
# Terraform
resource "grafana_dashboard" "api" {
config_json = file("dashboards/api-overview.json")
folder = grafana_folder.api.id
}Interview Tip
The sidecar pattern (watch ConfigMaps, auto-load dashboards) is the standard for Kubernetes. Mention it specifically — it shows you've deployed Grafana in production K8s, not just Docker.
💬 Comments
Quick Answer
Tempo is a distributed tracing backend that stores traces in object storage. It integrates via exemplars (metrics→traces), trace-to-logs (traces→Loki), and service graphs (traces→metrics).
Detailed Answer
- Open-source, high-scale distributed tracing backend - Stores traces in object storage (S3/GCS) — no local disk needed - Only requires trace ID for lookup — no indexing of span attributes (similar to Loki's approach for logs) - Supports OpenTelemetry, Jaeger, and Zipkin protocols
1. Metrics → Traces (Exemplars): - Prometheus stores exemplar data: a specific trace_id attached to a metric sample - In Grafana, clicking a data point on a metrics panel shows the exemplar - Click the exemplar → jumps directly to the trace in Tempo - Use case: High latency spike in p99 → click → see the exact slow trace
2. Traces → Logs (Trace-to-Logs): - Configure Tempo data source with Loki as the linked log source - Click a trace span → 'View Logs' → Loki query filtered by trace_id, service, and time range - Shows the exact log entries for that request
3. Traces → Metrics (Service Graph): - Tempo generates service graph metrics from traces automatically - Visualizes request rate, error rate, and latency between services - Stored as Prometheus metrics for dashboarding
The result: A fully correlated observability stack where you can seamlessly navigate between metrics, traces, and logs without context-switching tools.
Code Example
# Tempo configuration (tempo.yaml)
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
storage:
trace:
backend: s3
s3:
bucket: tempo-traces
endpoint: s3.amazonaws.com
metrics_generator:
storage:
path: /var/tempo/wal
traces_storage:
path: /var/tempo/traces
processor:
service_graphs:
dimensions: ["service"] # Generate service graph metrics
# Grafana data source provisioning with correlations
apiVersion: 1
datasources:
- name: Tempo
type: tempo
url: http://tempo:3200
jsonData:
tracesToLogs:
datasourceUid: loki
tags: ['service.name']
filterByTraceID: true
tracesToMetrics:
datasourceUid: prometheus
tags: [{ key: 'service.name', value: 'service' }]
serviceMap:
datasourceUid: prometheusInterview Tip
The three-way correlation (metrics↔traces↔logs) is what makes the Grafana stack compelling. Walk through a debugging scenario: metric alert → exemplar → trace → logs. This shows you use observability tools holistically.
💬 Comments
Quick Answer
Use RED method (Rate, Errors, Duration) for services and USE (Utilization, Saturation, Errors) for infra. Critical signals at top, deployment annotations, variable-driven filtering.
Detailed Answer
Rules: 1) Top row: request rate, error rate, latency p50/p99. 2) Second row: CPU, memory, disk. 3) Variables for namespace/service filtering. 4) Deployment annotations for correlation. 5) Color thresholds (green/yellow/red). 6) One service = one dashboard. 7) Link related dashboards. RED works for request-driven services. USE works for resources.
Code Example
# Key panels:
sum(rate(http_requests_total[5m])) by (service)
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))Interview Tip
Mention RED and USE methods by name. Explain why deployment annotations matter.
💬 Comments
Quick Answer
Grafana is a visualization and alerting layer; it queries data sources like Prometheus but stores no metrics itself.
Detailed Answer
Grafana connects to time-series and log backends (Prometheus, Loki, InfluxDB, SQL, cloud) and renders dashboards and alerts. It holds no metric data of its own — a slow dashboard is almost always a slow query in the underlying data source, not Grafana. Prometheus scrapes and stores; Grafana reads and displays.
Interview Tip
Emphasize the separation of concerns: storage vs visualization.
💬 Comments
Quick Answer
Drop YAML/JSON provisioning files under /etc/grafana/provisioning so they load on startup and can't be edited in the UI.
Detailed Answer
Provisioning makes a Grafana instance reproducible and disposable. Data sources go in provisioning/datasources/*.yml, dashboards in provisioning/dashboards/*.yml pointing at a folder of dashboard JSON. Provisioned objects are read-only in the UI, which prevents drift. For full GitOps you can also manage dashboards with the Terraform grafana provider or Grafonnet.
Code Example
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: trueInterview Tip
Mention that provisioned resources are immutable in the UI — a feature, not a limitation.
💬 Comments
Quick Answer
Variables turn one dashboard into many by injecting selectable values (like $service) into every panel query.
Detailed Answer
A Query variable pulls live values from a data source (e.g. label_values(http_requests_total, service)); Custom, Interval, and Data source types also exist. Reference them in queries with $var and enable Multi-value/Include All for filtering. One well-templated dashboard replaces dozens of near-duplicates and lets viewers slice by service, namespace, or environment.
Code Example
label_values(http_requests_total, service)
Interview Tip
Show label_values() for Prometheus and mention the 'Data source' variable to switch prod/staging.
💬 Comments
Quick Answer
RED = Rate, Errors, Duration — the three signals that answer 'is this service up and fast?'.
Detailed Answer
For request-driven services, chart the request Rate, the Error rate (e.g. 5xx share), and Duration (latency percentiles via histogram_quantile). RED complements USE (Utilization, Saturation, Errors) which suits resources like CPU and queues. Starting every service dashboard from RED gives on-call a consistent first screen.
Code Example
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[$__rate_interval])))
Interview Tip
Contrast RED (services) with USE (resources).
💬 Comments
Quick Answer
$__rate_interval scales the rate window with the dashboard's time range and scrape interval, avoiding gaps or over-smoothing.
Detailed Answer
A hard-coded [5m] breaks when zoomed out (too few points) or in (aliasing). $__rate_interval is computed by Grafana from the panel's step and the data source's scrape interval, guaranteeing at least a few samples per window at any zoom level. It's the recommended default for rate()/irate() in Grafana panels.
Interview Tip
This is a common gotcha — mention it proactively.
💬 Comments
Quick Answer
An alert rule evaluates a query on a schedule; when the condition holds for the 'for' duration, it fires and routes to contact points via notification policies.
Detailed Answer
You define a query and threshold, a pending period ('for') to suppress blips, labels (e.g. severity), and a notification policy that maps labels to contact points (Slack, PagerDuty, email). Alert on user-visible symptoms (error rate, latency), not causes (CPU). Provision rules as code so they live in Git.
Interview Tip
State the golden rule: alert on symptoms, not causes.
💬 Comments
Quick Answer
Add Loki as a data source alongside Prometheus and use shared labels plus data links to jump from a metric spike to the matching logs.
Detailed Answer
With Prometheus and Loki using consistent labels (service, namespace, pod), you can place a metrics panel and a logs panel on one dashboard filtered by the same variables. Grafana's split view and derived fields let you pivot from a latency spike to the exact log lines, and exemplars can link a histogram bucket to a trace.
Interview Tip
Mention exemplars for metrics-to-traces linking.
💬 Comments
Quick Answer
Use Stat or Gauge for a single current value and thresholds; use Time series for trends over time.
Detailed Answer
Stat shows one big number with optional sparkline and color thresholds — good for 'current error rate'. Gauge adds a min/max arc. Time series is for evolution over the window. Table and Bar chart suit top-N breakdowns. Always set a unit and a sensible legend; an unlabeled 1.2k helps no one.
Interview Tip
Always mention setting the unit and legend format.
💬 Comments
Context
A SaaS platform ingested 2.7 TB of logs per day into Loki. Storage cost doubled in six months, but incident responders mostly queried the last 14 days for production debugging.
Problem
Every namespace used the same long retention period. Debug logs from noisy services had the same retention as audit and payment logs. Teams were afraid to shorten retention because nobody had mapped log value to incident and compliance requirements.
Solution
The observability team classified log streams by service tier and compliance need, then configured retention by tenant and label strategy. They kept payment/audit logs longer, reduced debug retention, and added dashboards showing ingestion by namespace and label cardinality.
Commands
logcli series '{namespace="payments"}' --since=24h # Samples active log streamskubectl get cm loki -n observability -o yaml # Reviews retention and compactor settings
kubectl rollout restart statefulset/loki -n observability # Applies validated Loki config changes
Outcome
Loki object storage growth dropped by 38% while incident query success stayed stable. Teams also found two services emitting high-cardinality debug logs.
Lessons Learned
Retention is an operations policy, not just a storage setting. If labels are poorly designed, retention and query performance both suffer.
◈ Architecture Diagram
┌──────────┐
│ Learn │
└────┬─────┘
↓
┌──────────┐
│ UseCase │
└────┬─────┘
↓
┌──────────┐
│ Operate │
└──────────┘💬 Comments
Context
Five years of click-created dashboards: duplicates of duplicates, broken panels after datasource renames, no review for changes, and the 'good' checkout dashboard living only in one engineer's starred list.
Problem
An incident retro found responders using three different 'checkout overview' dashboards with different queries — two of them wrong after a metric rename nobody propagated. Dashboard changes had no audit trail and no rollback.
Solution
Moved to provisioning: dashboards defined in code (Jsonnet/grafonnet for the templated families, raw JSON in Git for one-offs), deployed via Grafana's provisioning directory from a Git-synced ConfigMap, with folder-level convention (team folders provisioned read-only; a 'sandbox' org folder stays editable for exploration). A migration sweep exported existing dashboards, deduplicated (300 -> 118), fixed the broken datasource refs against a datasource-as-code inventory, and deleted the rest after a 30-day grace period with usage stats as evidence. Panel/PR review became the change path; a nightly job diffs live state vs Git and alerts on drift.
Commands
grafana provisioning: /etc/grafana/provisioning/dashboards/team-*.yaml -> folder per team, disableDeletion: false, allowUiUpdates: false
jsonnet -J vendor dashboards/checkout.jsonnet > out/checkout.json
nightly: python drift_check.py --compare git://dashboards live://grafana
Outcome
One canonical dashboard per service (linked from the service catalog); dashboard changes are reviewed diffs with rollback; the broken-after-rename class ended because renames now grep the dashboard repo. Usage stats showed 60% of the old dashboards had zero views in 90 days — deleted without complaint.
Lessons Learned
The editable sandbox folder was essential for adoption — banning UI editing entirely would have failed. Usage statistics made the deletion conversation painless: data beats nostalgia.
💬 Comments
Context
An on-call rotation whose incident workflow was four browser tabs: Grafana for metrics, Kibana for logs, Jaeger for traces, and the deploy tool — with manual timestamp/label copying between them as the 'correlation' step.
Problem
Mean time-to-orient suffered from tool-hopping: responders re-typed time ranges and service names across UIs, lost context switching, and junior engineers regularly correlated the wrong time windows (local vs UTC).
Solution
Built correlation into the dashboards: Prometheus exemplars enabled on latency histograms so p99 spikes carry clickable trace IDs jumping straight into Tempo; data links on every service dashboard panel templating the service/namespace/time-range into Loki queries ('View logs for this panel'); trace-to-logs and trace-to-metrics configured in the Tempo datasource so navigation works in every direction; and deploy annotations rendered on all service dashboards from the CD webhook. The four tabs became one flow: metric spike -> exemplar trace -> its logs -> the deploy that caused it.
Commands
prometheus: --enable-feature=exemplar-storage; histogram buckets record traceID exemplars
datasource tempo: tracesToLogsV2 {datasourceUid: loki, filterByTraceID: true}panel data link: /explore?left={'datasource':'loki','queries':[{'expr':'{service="$service"}'}],'range':...}Outcome
Time-to-orient (page to first hypothesis) dropped ~40% measured across a quarter of incidents; the wrong-time-window correlation mistakes disappeared (links carry the range); new on-call onboarding shrank because the workflow is discoverable by clicking rather than tribal.
Lessons Learned
Exemplars require the histogram instrumentation to record them — the rollout was 80% app-side plumbing, 20% Grafana config. Deploy annotations were the cheapest, highest-value piece and should have shipped years earlier.
💬 Comments
Symptom
2:03 AM: PagerDuty opened 186 incidents for the same checkout latency regression in four minutes.
Root Cause
The alert rule grouped by pod, route, status_code, and container. When one dependency slowed down, every pod and route combination produced a separate alert instance. The notification policy treated each unique label set as a separate incident, so the on-call engineer had to triage notification noise before seeing the service-level failure. The underlying issue was a combination of factors that individually seemed harmless but together created a cascading failure. The monitoring that should have caught this early was either missing or configured with thresholds too high to trigger before user impact. The on-call engineer initially pursued the wrong hypothesis because the symptoms resembled a different, more common failure mode. By the time the real root cause was identified, the blast radius had expanded beyond the original service to affect downstream dependencies. This incident exposed a gap in the team's runbooks and highlighted the need for better correlation between metrics, logs, and traces during diagnosis.
Diagnosis Steps
Solution
Rewrite the alert to aggregate by service, environment, and team. Keep pod-level labels in dashboard queries, not paging labels. Add a warning-level diagnostic alert for pod skew if needed, but page on service impact.
Commands
grafana-cli admin stats # Verifies the instance is responsive during alert review
kubectl get cm grafana-alerting -n observability -o yaml # Reviews provisioned alert labels in Kubernetes-managed setups
Prevention
Define a label policy for paging alerts. Track alert instances per incident as an operational metric. Review no-data and grouping behavior during every alert rule change.
◈ Architecture Diagram
┌──────────┐
│ Signal │
└────┬─────┘
↓
┌──────────┐
│ Incident │
└────┬─────┘
↓
┌──────────┐
│ Action │
└──────────┘💬 Comments
Symptom
After a major-version Grafana upgrade, dashboards work fine, but no alerts fire for five days — noticed only when a disk-full incident arrived via customer report instead of the page that had caught the same condition twice before.
Error Message
No error. The legacy dashboard-panel alerts (classic alerting) were deprecated/removed in the target version; the migration to Unified Alerting had been marked 'review required' in the upgrade notes, and the auto-migrated rules landed paused/misconfigured in a folder nobody checked.
Root Cause
The upgrade crossed the legacy-to-Unified-Alerting boundary. Auto-migration created rules with default evaluation settings that didn't match the originals (grouping, for-duration, and two datasource refs failed to map), and several rules imported in a paused state. Nothing was red: dashboards rendered, the alerting tab existed, and the absence of alerts looked like an unusually quiet week. There was no synthetic 'test alert' proving the pipeline end-to-end.
Diagnosis Steps
Solution
Re-validated every migrated rule against its legacy definition (evaluation interval, for-duration, conditions, notification policy routing), fixed the datasource mappings, unpaused, and — the structural fix — added a permanent heartbeat alert: a rule that always fires into a dead-man's-snitch integration, so 'no heartbeat received' pages independently of Grafana itself.
Commands
grafana-cli (pre-upgrade): sqlite3 grafana.db 'select count(*) from alert' # legacy inventory
api: GET /api/v1/provisioning/alert-rules | jq '.[] | select(.isPaused)'
heartbeat: always-firing rule -> deadmanssnitch contact point
Prevention
Any alerting-system change ships with end-to-end verification: fire a synthetic alert through every notification channel post-change. Run a dead-man's-switch heartbeat permanently — the monitoring system's own liveness must be monitored from outside it. Major upgrades of observability infra get staging soak with alert-fire tests, same as app releases.
💬 Comments
Symptom
Prometheus p99 query latency spikes every weekday ~9am, worst Mondays: dashboards time out org-wide, on-call gets paged for 'Prometheus slow', and the TV dashboards in the office go blank. Backend CPU shows query saturation; ingestion is healthy.
Error Message
Prometheus logs: 'query processing would load too many samples into memory' and query timeouts; Grafana shows 'Request Error: gateway timeout' on heavy panels.
Root Cause
A popular 'executive overview' dashboard — auto-refreshing every 30s on several wall-mounted TVs and dozens of browser tabs — contained panels querying 30 days of high-cardinality data at raw resolution (no min_interval), each refresh launching ~40 expensive range queries. Monday 9am = everyone opens laptops + TVs reconnect = a coordinated query storm. The dashboard had grown organically; nobody owned its query cost.
Diagnosis Steps
Solution
Fixed the dashboard: min_interval set sane per panel (30d panels get 1h resolution), expensive panels converted to recording rules (pre-computed series queried cheaply), TV instances moved to a snapshot/reporting render refreshed every 5 minutes instead of live queries. Platform-side: Grafana datasource query timeout + max concurrent per-datasource, and Prometheus query limits (max_samples, timeout) so no single dashboard can saturate shared capacity again.
Commands
promtool query stats / --enable-feature=promql-per-step-stats; query log analysis
recording rule: instance:checkout_p99:1h = histogram_quantile(0.99, sum(rate(...[1h])) by (le))
grafana datasource: {timeout: 30, maxConcurrentQueries: 10}; panel: min_interval 1hPrevention
Query cost is a shared resource: recording rules for anything on always-on displays, min_interval proportional to time range as dashboard-review convention, per-datasource concurrency caps, and a slow-query log reviewed monthly to catch the next organically-grown monster before it pages.
💬 Comments