By day 79 of this series, you've learned individual tools and techniques. Now it's time to connect them into a coherent monitoring and observability roadmap: a practical guide that takes your team from raw metrics to confident, data-driven on-call decisions. Without a clear path forward, teams often build monitoring that looks impressive but solves no real problems.
Metric types, labels and PromQL
Before you can scale monitoring, you need to understand what you're actually measuring. The foundation starts with Prometheus' four metric types: counters, gauges, histograms, and summaries. Each solves a different observability need, and choosing the wrong one creates confusion downstream.
Counters track things that only go up: requests processed, errors logged, bytes transmitted. They're simple and monotonic, which makes them predictable for alerting. Gauges measure point-in-time snapshots: memory usage, active connections, queue depth. Histograms bucket observations into ranges, letting you calculate percentiles without needing to store every raw value. Summaries do similar work but calculate percentiles at the client side, which matters when you can't afford high cardinality.
Labels are where most teams stumble. A label should be a dimension you actually filter or group by. Adding cardinality carelessly—like customer IDs or request UUIDs as labels—will exhaust your storage and Prometheus' scrape capacity. A solid rule: if you wouldn't put it in a GROUP BY clause, it doesn't belong as a label.
Here's a practical example. Instead of this:
http_requests_total{method="GET",user_id="12345",path="/api/users"}
Do this:
http_requests_total{method="GET",endpoint="/api/users"}
http_request_duration_seconds_bucket{le="0.1",endpoint="/api/users"}
http_request_duration_seconds_bucket{le="0.5",endpoint="/api/users"}
Once you have clean metrics, PromQL becomes your language for asking questions. Learn the basics: rate() for calculating per-second changes, histogram_quantile() for latency percentiles, and group modifiers for multi-dimensional aggregation. Most teams need only 10–15 query patterns to cover 80% of their troubleshooting work. Focus on those first, then expand your PromQL toolkit as new problems surface.
Exporters, dashboards and alerts
With solid metrics in place, the next step is getting data into Prometheus and visualizing it so humans can act on it. This is where exporters enter the picture. An exporter is a bridge between your application or infrastructure and Prometheus. Node Exporter gives you CPU, memory, and disk. The Postgres exporter exposes table sizes, slow queries, and replication lag. Redis Exporter tells you eviction rates and command latency.
The key is choosing exporters that align with what you actually operate. If you run Kubernetes, Prometheus service discovery handles most of your scrape targets automatically. If you run VMs, you'll manage static scrape configs or use Consul for discovery. Either way, the goal is coverage without sprawl: you want visibility into every service tier without drowning in noise.
Dashboards are where metrics become visible to the whole team. A good dashboard isn't pretty; it's useful. It should answer the question someone would ask during an incident. For a payment service, that might be: Is the gateway responding? What's the success rate? How long are payouts taking? Are there database connection errors?
Build dashboards around business outcomes first, then drill into subsystems. Use variable dropdowns to let on-call engineers filter by region, service, or customer cohort. Keep dashboards simple enough that a person new to the service can understand the health at a glance.
Alerts are how you prevent problems from becoming incidents. A good alert has three properties: it's actionable, it happens rarely enough to trust, and the runbook is written before the alert fires. Avoid alerts that page you every week for non-critical issues; that trains people to ignore alerts entirely.
A simple alert rule:
groups:
- name: payment_service
interval: 30s
rules:
- alert: HighErrorRate
expr: rate(payment_errors_total[5m]) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "Payment service error rate above 5%"
runbook: "https://docs.internal.com/payment-runbook#high-error-rate"
Notice the for: 2m clause. This prevents flaky alerts that spike for 10 seconds. The runbook URL is mandatory; whoever is on call needs to know what to do without hunting through wikis.
SLOs, burn rates and on-call
Once you're collecting metrics and alerting on symptoms, you're ready to think about reliability targets. This is where SLOs (service level objectives) transform monitoring from reactive firefighting into proactive engineering.
An SLO is a promise to your users. "99% of API requests will complete within 500ms." That promise, measured over 30 days, gives you a budget of downtime or slowness you can spend. If you have 99% availability as your SLO, you can afford 7.2 hours of downtime per month. The difference between your SLO and 100% is your error budget.
When you're consuming your error budget slowly—say, 10% per week—you can take risks: deploy new features, do maintenance, refactor. When you're burning your budget quickly—80% in the first week—you stop, fix the root cause, and focus on stability.
Burn rates quantify how fast you're consuming the budget. A 10x burn rate means you're failing 10 times faster than your SLO allows. If you hit a 10x burn rate for one hour, you've used a week's budget. That's a page-worthy event. A 2x burn rate sustained over a day? That needs investigation but not an emergency page.
Here's how to set SLO-based alert rules:
- alert: HighBurnRate
expr: |
(
(1 - (rate(requests_success_total[1h]) / rate(requests_total[1h])))
/ (1 - 0.99)
) > 10
for: 5m
labels:
severity: critical
annotations:
summary: "Error budget burning at 10x rate"
This alerts when the observed failure rate is 10 times what the SLO permits. Pair this with slower burn rate alerts (2x over 6 hours) to catch degradation early.
On-call rotations work best when they're tied to SLOs and error budgets. Engineers on call know the SLO, understand which metrics matter, and have runbooks for the most common failures. Rotate on-call weekly or bi-weekly; that cadence lets people prepare and recover without burnout.
Scaling, tracing and cost control
As your systems grow, monitoring becomes more complex. A single Prometheus instance can scrape hundreds of targets, but once you're handling thousands of metrics across dozens of services, you need a scaling strategy.
Sharding is one approach: run multiple Prometheus instances, each responsible for a subset of targets, then query them all through a router like Thanos or Cortex. Recording rules pre-compute expensive aggregations so on-call dashboards stay responsive. Retention policies keep disk usage predictable: keep 15 days of high-resolution data locally, then archive older data to cheaper storage.
Metrics alone won't answer "why is latency high on this one request?" That's where tracing enters. A trace follows a single request through your entire stack: API gateway → service A → database → service B → cache. You see where time is spent, which services are slow, and whether a timeout in one service cascaded into failures elsewhere.
Tracing libraries like Jaeger or Datadog inject trace IDs into requests, and instrumenting your code takes minutes:
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('payment-service');
async function processPayment(orderId) {
const span = tracer.startSpan('processPayment');
try {
const span2 = tracer.startSpan('validateOrder');
// ... validation logic
span2.end();
} finally {
span.end();
}
}
Start with tracing the critical path: payment flows, user registration, or whatever touches your most important customers. Don't trace everything initially; that's storage cost you don't need.
Cost control is the often-overlooked pillar. High-cardinality labels, unbounded retention, and trace sampling at 100% will bankrupt you quickly. Set budgets: "We spend $500/month on monitoring." Then work within that. Use sampling for traces (10% is often enough), set retention policies based on business need (not indefinitely), and audit which metrics actually get queried in Grafana dashboards.
Many teams find that 70% of metric storage comes from 5% of their labels. Use cardinality-exporter to identify and eliminate the worst offenders.
Common mistakes
- Alerting without runbooks. An alert that pages someone at 3am with no runbook is worse than no alert at all. Always write the runbook first.
- Mixing business metrics and technical metrics. Track revenue, sign-ups, and subscription cancellations separately from CPU and disk. They have different audiences and urgency levels.
- Setting SLOs too tight. 99.99% sounds impressive but is expensive to achieve and maintain. Start with 99% or 99.5% and raise it only if you can afford the operational cost.
- Ignoring cardinality. A single label with 10,000 unique values across 1,000 services will create 10 million time series. Your storage will grow exponentially and Prometheus will become sluggish.
- Dashboards with no context. A graph showing "requests/sec" means nothing if viewers don't know what "normal" looks like. Add baselines, expected ranges, and annotations.
Key takeaways
- Start with the four metric types and clean label design; they're the foundation everything else rests on.
- Exporters and dashboards are how you make metrics visible; pair them with simple, actionable alerts backed by runbooks.
- SLOs and error budgets shift your team from reacting to incidents toward proactive, sustainable engineering.
- As you scale, use sharding, recording rules, and tracing to keep latency low and costs predictable.
- Audit cardinality, retention, and trace sampling regularly; left unchecked, monitoring can become your most expensive infrastructure.






Comments
Loading comments…