Back to portfolio
Case study · Observability

Modernizing a Grafana and Prometheus observability stack

The stack had drifted behind: Grafana past end-of-life, Prometheus eight minor versions old, and the heavy dashboards taking tens of seconds to render. Here is how I brought both to LTS and made those dashboards about 30 times faster, with no data loss and no alerts touched.

By Vitor Schiavo Updated October 2026 ~7 min read

Observability stacks rot quietly. The upgrades feel risky, so they get deferred, and one day you are on an end-of-life Grafana with dashboards nobody trusts and queries that take half a minute. This is a write-up of pulling one back to current: two major Grafana versions, a Prometheus LTS bump, and the recording rules that were the real win.

Grafana: end-of-life to 11.6 LTS

Grafana was on an end-of-life release, one major version short of the Angular plugin runtime being removed entirely. I took it to 11.6 LTS in a deliberate, reversible way:

The real win: Prometheus recording rules

The heavy ingress dashboards were painful: 8 to 33 seconds per render. The cause was histogram_quantile() being recomputed from tens of thousands of bucket series (about 57k) on every single refresh. The fix is a classic one that is easy to forget to reach for: recording rules.

I added three recording rules per environment that pre-aggregate the P50, P90 and P99 of the request-duration histogram, and pointed the dashboards at the pre-computed series instead of the raw buckets.

EnvironmentBeforeAfterSpeedup
Production (higher cardinality)8 to 33 s~0.66 s~30×
Sandbox (lower cardinality)3.5 to 9 s~0.66 s~5 to 13×
~30×faster dashboards
0.66 sconsistent query time
LTSGrafana 11.6 + Prom 2.53
0data lost or alerts changed

Prometheus: 2.45 to 2.53 LTS

I patched Prometheus up eight minor versions to 2.53 LTS, staged safely: the sandbox cluster first, then production after a 24-hour soak. One real-world constraint worth naming: the stack runs the managed (GMP) fork of Prometheus because the environment's Cloud Monitoring alerts and dashboards depend on its built-in exporter. Upstream Prometheus is already on 3.x, but the managed fork tops out at 2.53 LTS, so that was the ceiling. Knowing which fork you are on, and why, is half the upgrade.

A config refactor worth doing

The per-environment Prometheus configs had drifted into different section orders, which made diffing one environment against another a chore. I reordered them to a single canonical structure (persistent volumes, config, RBAC, StatefulSet, services, ingress). No functional change, but now a side-by-side diff shows only the differences that matter.

A latent bug, found in passing

While validating the recording rules I found a panel rendering "No data" over a query that clearly had data. The cause was a Grafana thresholds config (thresholdsStyle: line+area) with a trailing threshold step missing its value. The kind of bug that hides for months until someone looks closely at the right panel.

What I would tell someone starting this