Query performance and caching

Grafana · Expert · 12 min

A Grafana that is slow is almost always a data source being asked too much, too often; the fixes are in the panel options and dashboard design.

Start with measurement. The Query inspector (panel menu, Inspect, Query) shows every request a panel made, its duration, the number of series and rows, and the exact query sent after variable substitution. The Stats tab gives total request time and data size; the JSON tab the panel model. Prometheus itself logs slow queries and exposes prometheus_engine_query_duration_seconds.

Levers inside a panel's Query options:

  • Max data points (default panel width) and Min interval (1m or more for long ranges) raise the step and shrink the response. Set a dashboard-wide minimum on the data source (timeInterval).
  • Relative time and Cache timeout; Query caching (Enterprise and Cloud) stores responses per data source for a TTL so ten viewers of a wall board hit Prometheus once.
  • The Prometheus data source's incremental querying re-fetches only the new slice on refresh and merges it in the browser, with an overlap window for late samples.

Levers in the dashboard:

  • Fewer panels, collapsed rows (their queries don't run), lazy loading (panels off-screen are not queried until scrolled into view), and a sane auto-refresh backed by min_refresh_interval.
  • Recording rules in Prometheus for expensive aggregations, especially anything over [1d] or histogram_quantile across many series.
  • Variables: label_values(metric, label) with a metric selector is far cheaper than label_values(label) on a busy server, and refresh on time range change re-runs every variable query on each zoom.
  • Avoid regex matchers with leading .* and queries without a job filter.

Server side, [dataproxy] timeouts and max_idle_connections, a remote cache for sessions, and watching Grafana's own /metrics (grafana_datasource_request_duration_seconds) complete the picture. Rendering of images for reports and alert screenshots runs in a separate image renderer service that should be sized on its own.

Example

# Panel query options
# Max data points: 500     Min interval: 1m     Relative time: (none)
# Cache timeout: 60        (query caching, Enterprise/Cloud)

# Expensive on a dashboard (every panel, every refresh):
histogram_quantile(0.99, sum by (le, route) (
  rate(http_request_duration_seconds_bucket[$__rate_interval])))

# Cheap: a Prometheus recording rule evaluated once, used everywhere
# groups:
#   - name: api
#     rules:
#       - record: route:http_request_duration_seconds:p99_5m
#         expr: histogram_quantile(0.99, sum by (le, route) (rate(http_request_duration_seconds_bucket[5m])))
route:http_request_duration_seconds:p99_5m

# Variable queries: narrow the metric instead of scanning all series
label_values(up{job=~"api.*"}, instance)     # good
label_values(instance)                       # scans every series

Remember

  • Inspect first: the query inspector shows requests, timing and series counts
  • Min interval, max data points, caching and incremental querying cut data source load
  • Collapsed rows, lazy loading, recording rules and narrow variable queries fix slow dashboards

Watch out for

  • Variables set to refresh on time range change combined with heavy label_values() queries, so every zoom fires a dozen metadata queries.

Check yourself

  1. Fifty people watch the same dashboard that refreshes every 30s. Which measure reduces Prometheus load most directly?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/