Topic 27

Architecture and Data Sources

Grafana

Grafana owns no telemetry. Every panel on every dashboard is a live query sent to a backing store — Prometheus for metrics, Loki for logs, Tempo for traces — rendered in the browser and thrown away. Grafana's own database, SQLite by default, holds dashboards, users, and data source definitions; not one sample of Harborline's data lives inside it.

That makes Grafana the query-and-render layer of the stack, and the consequences cut both ways. Swap the storage underneath and the dashboards keep working; delete the Grafana container and no telemetry is lost. It also means every question about what a dashboard can show, and how fast, is really a question about the store behind it.

The Query-and-Render Layer

A dashboard refresh fans out one query per panel to its data source, written in that source's native language: PromQL to Prometheus, LogQL to Loki, TraceQL to Tempo. Grafana translates nothing and caches almost nothing, so a slow panel is a slow query, and the fix lives in the query, not in Grafana settings. The arithmetic matters: a 20-panel dashboard on a 30-second auto-refresh fires 40 queries per minute at Prometheus from a single open browser tab.

Grafana amplifies query load onto the store; it never absorbs it. When the checkout dashboard feels sluggish, Mara's first stop is the panel's query inspector, and her second is Prometheus itself — because that is where the work actually happens.

One panel refresh — query out, pixels back, nothing kept
Panelone query each
Data sourcenamed connection
Query to storePromQL · LogQL · TraceQL
Renderbrowser · discarded

Data Sources

A data source is a named connection to one backend: a type, a URL, and credentials if the store wants them. Prometheus, Loki, and Tempo are the Harborline three, but the same panel model speaks to dozens of backends through core and plugin data sources — PostgreSQL, Elasticsearch, CloudWatch, MySQL, Jaeger, and the rest of the catalog.

One dashboard can mix sources panel by panel: a PromQL time series in the top row, a Loki log panel beside it, both on the same time axis. That is exactly how the checkout dashboard will end up — metrics saying something changed, logs two panels over saying what the service printed while it did.

Explore Mode

Explore is the scratchpad: pick a data source, iterate on a query, and see the result without building a panel around it. Split view puts two queries side by side on the same time range — a PromQL expression on the left, a Loki query on the right — which is the cheapest correlation tool in the stack. Queries get born in Explore and only then get pinned into dashboards; editing PromQL inside a half-built panel is the slow way to do both jobs.

Provisioning Data Sources from Files

Data sources clicked together in the UI live only in Grafana's database, which means a rebuilt container comes up empty. The alternative is file provisioning: one YAML file per source in /etc/grafana/provisioning/datasources/, read at startup, so the container image plus the files fully determine the running state. Here is Harborline's Prometheus entry.

# /etc/grafana/provisioning/datasources/harborline.yaml
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    uid: prom
    url: http://prometheus:9090
    isDefault: true
    jsonData:
      timeInterval: 15s

Field by field: name is the label humans see in menus; type selects the plugin; uid is the identifier dashboards reference, and fixing it to prom means every dashboard JSON that points at it keeps working after any rebuild; url is where Grafana sends the queries; isDefault makes this the source every new panel and Explore session starts on; and timeInterval declares the real 15 s scrape interval so $__rate_interval — covered in the next topic — computes correct windows.

The Harborline Wiring

Grafana runs in the obs-01 Compose stack and reaches its sources by service name over the Compose network: http://prometheus:9090, http://loki:3100, http://tempo:3200. The browser talks only to Grafana on :3000; Grafana queries the stores server-side and ships rendered results back. With three provisioned sources and an empty dashboard list, Mara has the plumbing done — the next topic starts drawing on it.

Common Mistakes
  • Treating Grafana as storage — expecting a panel to show data older than Prometheus's retention window. The graph goes blank at the retention boundary because nothing exists behind it, and no Grafana setting brings it back.
  • Clicking data sources together in the UI on a container you provision from files — the next image rebuild wipes them, and every panel errors with "data source not found" until someone re-clicks them at 3 a.m.
  • Provisioning without a fixed uid — dashboards reference sources by uid, so a source recreated with a fresh random uid orphans every panel that pointed at the old one.
  • Pointing the Prometheus URL at localhost:9090 from inside the Grafana container — localhost is the Grafana container itself, not the host. Inside Compose the address is the service name, and the symptom is connection-refused on every panel while Prometheus is demonstrably up.
  • Setting a 5 s auto-refresh on a 30-panel dashboard — that is 6 queries per second against Prometheus from one browser tab, multiplied by every screen in the office that leaves the page open.
Best Practices
  • Provision every data source from a file with an explicit uid, and treat the UI's data source page as read-only — the files are the source of truth and survive rebuilds.
  • Set the Prometheus source's timeInterval to the real 15 s scrape interval so $__rate_interval computes correct windows on every panel that uses it.
  • Develop every query in Explore first — split view, iterate, then pin it to a panel — instead of editing PromQL inside a half-built dashboard.
  • Mark Prometheus isDefault: true in provisioning so every new panel and Explore session starts on the source 90% of queries target.
Comparable toolsKibana the same render layer, married to Elasticsearch (OpenSearch forked its own Dashboards, not Kibana)SaaS Datadog / New Relic — storage and dashboards bundled, the layer boundary invisibleCNCF Perses — dashboard project built code-firstInfluxDB Chronograf — this role for the Influx stack

Knowledge Check

The Grafana container on obs-01 is deleted and rebuilt from its image, with provisioning files intact. What telemetry is lost?

  • All metrics that were shown on dashboards
  • None
  • The rendered history of each panel, back to the retention window
  • The last 15 days of Loki log data

Why is a dashboard's auto-refresh rate a capacity decision about Prometheus rather than about Grafana?

  • Grafana caches results, so only the first refresh costs anything
  • Rendering panels is the dominant cost and happens inside Grafana
  • Each refresh sends every panel's query to the store
  • Prometheus limits Grafana to one query per second by default

What breaks if a provisioned data source is recreated with a new random uid?

  • Grafana can no longer reach the backend URL
  • Every dashboard panel that referenced the old uid errors
  • Prometheus stops scraping until the uid is restored
  • All user accounts are reset

Inside the obs-01 Compose stack, Grafana's Prometheus URL is set to http://localhost:9090 and every panel shows connection refused — while Prometheus answers fine from the host. Why?

  • Prometheus is down and the host check is hitting a cached page
  • Grafana lacks credentials for Prometheus
  • Compose networking blocks HTTP between containers by default
  • localhost inside the Grafana container is the Grafana container

You got correct