Prometheus
Pull metrics, PromQL, alerting rules, exporters.
Theory
Prometheus is a pull-based monitoring system: it scrapes HTTP /metrics endpoints on a configured interval (default 15s). Each target exposes metrics in the Prometheus text format — one metric per line with optional labels in {curly braces}.
Four metric types: Counter (monotonically increasing, e.g. http_requests_total), Gauge (can go up or down, e.g. memory_usage_bytes), Histogram (bucketed observations for latency, e.g. http_request_duration_seconds_bucket), Summary (precomputed quantiles on client side).
PromQL is Prometheus's query language. rate(http_requests_total[5m]) computes per-second rate over a 5-minute window. irate() uses only the last two samples — more reactive but noisier. histogram_quantile(0.99, ...) computes the p99 latency from histogram buckets.
Recording rules pre-aggregate expensive queries and store results as new time series. Alert rules evaluate PromQL expressions and fire to Alertmanager when the condition is true for a specified duration (for: 5m prevents flapping alerts).
Alertmanager handles routing, grouping, silencing, and inhibition of alerts. Routes match alert labels (e.g. severity=critical → PagerDuty, severity=warning → Slack). Group_by merges related alerts into one notification to reduce noise.
Exporters translate third-party metrics into Prometheus format. node_exporter exposes host OS metrics (CPU, memory, disk, network). blackbox_exporter probes HTTP/TCP endpoints for availability. Custom apps should expose /metrics natively using client libraries (Python, Go, Java).
Service discovery via kubernetes_sd_config, ec2_sd_config, or static_configs. Relabeling (relabel_configs) filters or rewrites target labels before scraping — drop dev namespaces, rename label keys, add env labels from instance metadata.
Prometheus stores data locally in a TSDB with 2-hour blocks compacted into larger chunks. Default retention is 15 days. For long-term storage, remote_write to Thanos, Cortex, or Mimir — these also enable multi-cluster federation and HA.
Architecture Diagram
Exporters / app /metrics
| scrape (pull)
Prometheus TSDB
|
+----+----+
v v
Grafana Alertmanager
(viz) (route/notify)Examples
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alerts.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
scrape_configs:
- job_name: "app"
static_configs:
- targets: ["app:8000"]
metrics_path: /metrics
- job_name: "kubernetes-pods"
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: "true"
groups:
- name: app_alerts
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) /
rate(http_requests_total[5m]) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "Error rate above 5% for {{ $labels.job }}"
- record: job:http_request_duration_seconds:p99
expr: histogram_quantile(0.99, sum(rate(
http_request_duration_seconds_bucket[5m])) by (le, job))
# Request rate per endpoint
rate(http_requests_total[5m])
# p99 latency
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, handler))
# CPU usage %
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# Memory available %
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100
# 5xx error rate
sum(rate(http_requests_total{status=~"5.."}[5m])) /
sum(rate(http_requests_total[5m]))
Interview Questions
What is the difference between a Counter and a Gauge?
Counter is monotonically increasing (resets on restart) — use rate() to get per-second rate. Gauge is a current value that goes up or down — use directly or avg(). Never use rate() on a Gauge. Common mistake: using a Gauge for request count instead of a Counter.
When would you use irate() vs rate()?
rate() averages over the full window (e.g. [5m]) — smoothed, good for alerting. irate() uses only the last two samples — more sensitive to spikes, better for dashboards showing real-time behavior. Use rate() for alert rules to avoid flapping on momentary spikes.
How does histogram_quantile() work?
Histogram buckets count observations below each threshold (le label). histogram_quantile(0.99, ...) interpolates within the bucket containing the 99th percentile. Accuracy depends on bucket boundaries — pre-choose buckets around expected p50/p90/p99 values. Don't use client-side Summary for aggregated quantiles across instances.
What happens when a scrape target is down?
Prometheus records the scrape failure in up == 0. Alert on up == 0 for >= 5m to catch dead targets. Prometheus does not backfill — missed scrapes create gaps in time series.
Explain recording rules and why they matter.
Recording rules evaluate a PromQL expression on a schedule and save the result as a new metric. This pre-computes expensive aggregations (e.g. per-job error rate) so dashboards load fast instead of recomputing across millions of series. Also normalizes metric naming for SLO alerting.
How does Alertmanager prevent alert storms?
group_by clusters related alerts into one notification. group_wait delays the first notification to collect related alerts. group_interval controls re-notification timing. inhibit_rules suppress low-severity alerts when a matching critical alert is firing (suppress disk-full warnings while the host is down).
How do you handle high-cardinality labels?
High-cardinality labels (user_id, request_id) create one time series per value — can grow to millions and OOM Prometheus. Never put unbounded values in labels. Use logs for per-request tracing and Prometheus for aggregate metrics. Drop high-cardinality labels with relabel_configs at scrape time.
FAANG: How does Prometheus scale beyond a single instance?
Prometheus is designed as a single-node system. Scale via: (1) sharding by target (multiple Prometheus instances scraping different service groups), (2) remote_write to Thanos/Cortex/Mimir for global query and long-term storage, (3) Thanos sidecar uploads TSDB blocks to object storage. For read path, Thanos Querier federates across shards with deduplication.
Best Practices
- Use Counters for requests/errors/bytes — never a Gauge. Use rate() on Counters, never on Gauges.
- Choose histogram bucket boundaries that bracket your expected p50/p90/p99 — default buckets rarely fit your latency distribution.
- Add
for: 5mto every alert rule to prevent flapping alerts on momentary spikes. - Alert on symptoms (high error rate, high latency) not causes (CPU 80%) — fewer, more actionable pages.
- Use recording rules to pre-aggregate expensive PromQL — dashboards load in milliseconds instead of scanning millions of series.
- Never put high-cardinality values (user_id, request_id) in label sets — one label value = one time series = potential OOM.
Common Mistakes
- Using rate() on a Gauge — Gauges go up and down, rate() gives nonsensical results. Use avg() or max() on Gauges directly.
- Setting scrape_interval too short (e.g. 1s) — creates storage pressure and makes irate() meaningless. 15–30s is standard.
- Alerting on CPU/memory thresholds without context — 90% CPU is fine under expected load; alert on SLO burn instead.
- Using client-side Summary for aggregated quantiles — quantiles cannot be re-aggregated across instances. Use Histogram instead.
- No
up == 0alert — when a scrape target disappears, Prometheus records no data (no alert fires). You need an explicit absent/up check.
Cheat Sheet
Practical Exercises
Add prometheus_client to a Flask app. Expose /metrics with a Counter for request count (labeled by endpoint and status_code) and a Histogram for request duration. Run prometheus locally and visualize rate() in the built-in graph UI.
Create alerts.yml with: (1) an alert for error rate > 5% sustained 2 minutes, (2) an alert when up == 0 for 5 minutes. Start Alertmanager with a simple Slack webhook config. Trigger the alert by stopping your app and verify the notification fires.
In Grafana, build a 4-panel dashboard: (1) request rate, (2) error rate %, (3) p99 latency via histogram_quantile, (4) total requests per endpoint. Use the sum by() and rate() patterns from the cheat sheet.