Gendi Notes

Grafana

Grafana stores no data. It queries other systems and draws the answer — which is why "Grafana is broken" is almost always the datasource or the query.

Level 2 guide grafanadashboardsobservability

What is it?#

A query and visualisation layer over many backends: Prometheus, Loki, Tempo, SQL databases, cloud metrics. One UI, one login, one place to correlate.

┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │Prometheus│ │ Loki │ │ Tempo │ │ Postgres │ ← the data lives here └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ └─────────────┴──────┬──────┴─────────────┘ ↓ queries, on every refresh ┌──────────────┐ │ GRAFANA │ ← stores dashboards and alert rules │ │ in its OWN small database, │ │ NOT your metrics └──────────────┘

What it is made of#

PieceWhat it is
DatasourceA connection to a backend. Each has its own query language
DashboardA grid of panels, stored as JSON
PanelOne visualisation, driven by one or more queries
VariableA dropdown that parametrises queries — $namespace, $pod
Alert ruleA query plus a condition, evaluated on a schedule
Contact pointWhere a firing alert is sent
Notification policyRouting tree: which alerts go to which contact point

How to use it#

Build a dashboard that is worth keeping#

Most dashboards are abandoned because they answer no particular question. Anchor them to a method instead:

MethodPanelsFor
REDRate, Errors, DurationRequest-driven services
USEUtilisation, Saturation, ErrorsResources: nodes, disks, queues
Four Golden SignalsLatency, traffic, errors, saturationGoogle SRE's version of both
A RED dashboard, in four queries
# Rate — requests per second by status
sum(rate(http_requests_total{service="$service"}[5m])) by (status)

# Errors — the ratio, which is what an SLO measures
sum(rate(http_requests_total{service="$service",status=~"5.."}[5m]))
  /
sum(rate(http_requests_total{service="$service"}[5m]))

# Duration — p99. rate() first, then sum by le, THEN the quantile
histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket{service="$service"}[5m])) by (le))

# Saturation — how close to the limit
sum(rate(container_cpu_usage_seconds_total{pod=~"$service.*"}[5m]))
  /
sum(kube_pod_container_resource_limits{pod=~"$service.*",resource="cpu"})

Variables make one dashboard serve every service#

Name:   service
Type:   Query
Query:  label_values(http_requests_total, service)
Multi:  yes        Include All: yes

Then use $service in every panel. One dashboard, a dropdown, no copies to maintain.

Dashboards as code#

A dashboard edited in the browser exists only in Grafana's database. Provision them from files instead:

/etc/grafana/provisioning/dashboards/main.yaml
apiVersion: 1
providers:
  - name: 'platform'
    folder: 'Platform'
    type: file
    disableDeletion: true
    allowUiUpdates: false        # the UI becomes read-only for these
    options:
      path: /var/lib/grafana/dashboards
Or in Kubernetes — the sidecar picks up any labelled ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: web-dashboard
  labels:
    grafana_dashboard: "1"        # the sidecar watches for this label
data:
  web.json: |
    { "title": "Web — RED", "panels": [ ... ] }

Alerting#

Grafana's unified alerting can evaluate rules against any datasource, which is the reason to use it over Alertmanager alone.

Alert rule query + condition + "for: 5m" │ (the `for` is what stops flapping) ↓ Notification policy routing tree, matched on labels │ severity=critical → PagerDuty │ severity=warning → Slack ↓ Contact point the actual destination │ └─ grouping · deduplication · silences · mute timings

What goes wrong#

SymptomCauseFix
"No data"Query returns nothing, or wrong time rangeRun it in Explore; widen the range
Panel empty only for some values= instead of =~ with a multi-value variableUse =~
Dashboard slowQueries over months at high resolutionShorter ranges; recording rules
too many outstanding requestsPanels hammering PrometheusFewer panels; increase min interval
Edits keep disappearingProvisioning with allowUiUpdates: falseWorking as configured — commit the JSON
Alerts flapNo for: durationAdd for: 5m
Alert storm in one incidentNo grouping in the notification policygroup_by on cluster and alertname
Dashboards lost after restartSQLite on ephemeral storageUse a PVC, or an external database

Interview corner · الأسئلة اللي بتتسأل#

A Grafana panel shows "No data". How do you debug it?

Grafana stores nothing, so the fault is in the datasource, the query, or the time range — check them in that order, cheapest first.

1. Time range      — is there data in this window at all? Widen to 24h
2. Datasource      — Settings → Save & test. Is it reachable?
3. The query       — run it in Explore, stripped of variables
4. Variables       — do they resolve? Try a literal value first
5. The metric      — does it exist upstream?
                     In Prometheus: does the series appear at all?
FindingCause
Works in Explore, fails in the panelA variable is unresolved or wrongly interpolated
Works with a literal, fails with $varMulti-value with = instead of =~
No data anywhere, including PrometheusThe target is not being scraped
Data exists but stops at a point in timeThe exporter or target died then
How do you design a dashboard people will actually use?

Start from the question, not the data. Most dashboards fail because they show everything available rather than what someone needs during an incident.

A structure that works:

RowContainsAnswers
TopSLO / error budget burnAre we breaking our promise right now?
2ndRED: rate, errors, p99Is this service the problem?
3rdDependencies: downstream latency and errorsOr is it something we call?
4thSaturation: CPU/memory vs limits, queue depthAre we running out of something?
BottomLinks to logs and traces, pre-filteredWhere do I go next?

Rules that keep it useful:

  • One dashboard per service, parametrised by variables — not one per environment.
  • Percentiles, never averages. An average hides exactly the users who are leaving.
  • Annotate deploys. "What changed?" is the first incident question, and an overlay answers it instantly.
  • Delete unused dashboards. A directory of 200 is the same as none, because nobody can find the right one.

Key takeaways#

  • Grafana stores no metrics. "Grafana is broken" is the datasource, the query, or the time range.
  • Anchor dashboards to RED or USE, and start from the question someone asks during an incident.
  • Use =~ with multi-value variables, or the panel breaks on "All".
  • Percentiles, not averages, and annotate deploys.
  • Provision dashboards from Git. The UI is for experimenting; the repo is the source of truth.
  • for: on every alert rule, or you train people to ignore the channel.
  • Grafana needs persistent storage — or it loses every dashboard on restart.

Last updated 2026-09-13