Metadata
| Field | Value |
|---|---|
| Slug | monitoring-alerting |
| Difficulty | Beginner |
| Estimated Reading Time | 12 min |
| Estimated Coding Time | 15 min |
| Tier | 5 — Production Infrastructure |
| Implementation Language | Go |
| SEO Description | Learn Monitoring and Alerting for microservices. Understand Prometheus, Grafana, Metrics (Counters, Gauges, Histograms), and the Four Golden Signals. See a Go implementation. |
1. Overview
What problem does it solve?
If you launch an e-commerce website and go to sleep, how do you know if it's actually working? If the database runs out of disk space, how do you know? If a bad code deployment causes checkout errors to spike from 1% to 15%, who notices?
Monitoring is the process of collecting quantitative data (Metrics) about your system (e.g., CPU usage, HTTP requests per second, error rates) in real-time. Alerting is the automated process of paging a human (e.g., via PagerDuty or Slack) when those metrics cross a dangerous threshold.
What breaks without it?
- Silent Outages: Your users become your monitoring system. You only find out your site is down when angry customers yell at you on Twitter.
- Resource Exhaustion: A memory leak slowly consumes RAM over 3 days until the server crashes. With monitoring, you see the trend line going up and fix it on day 1.
2. Motivation
In the early days, monitoring meant writing a shell script that pinged a server every 5 minutes and emailed you if it didn't respond (e.g., Nagios).
As systems moved to microservices and Kubernetes, servers became ephemeral. You couldn't just ping "Server A" because Server A might be destroyed and replaced by Server B dynamically based on load. The industry needed a dynamic, time-series based monitoring system. Prometheus (created at SoundCloud, inspired by Google's internal 'Borgmon') became the open-source standard for cloud-native metrics collection.
3. Real-World Usage
| System | Use Case |
|---|---|
| Prometheus | The industry standard open-source Time Series Database (TSDB) for metrics. |
| Grafana | The industry standard visualization dashboard that queries Prometheus. |
| Datadog / New Relic | Enterprise SaaS alternatives that handle Logging, Monitoring, and Tracing all in one expensive platform. |
| PagerDuty | Connects to alerting rules and physically calls an engineer's phone at 3 AM if the site goes down. |
4. Prerequisites
| Concept | Block |
|---|---|
| Logging | 032 Logging & Structured Logs |
5. Visual Explanation
The Push vs Pull Model
There are two ways to collect metrics:
-
Push Model (e.g., StatsD / Datadog): The application actively sends UDP packets containing metrics to a centralized server. Problem: If the metric server goes down, the application might block or lose data.
-
Pull Model (e.g., Prometheus): The application simply maintains a
/metricsHTTP endpoint. Prometheus runs a background loop that scrapes (HTTP GET) that endpoint every 15 seconds. Benefit: If Prometheus goes down, the application doesn't care. It just keeps serving its/metricsendpoint.
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ffcc00', 'edgeLabelBackground':'#ffffff'}}}%%
graph TD
classDef app fill:#f9f9f9,stroke:#333,stroke-width:2px;
classDef prom fill:#d4edda,stroke:#28a745,stroke-width:2px;
classDef dash fill:#cce5ff,stroke:#007bff,stroke-width:2px;
subgraph Microservices
A[Order Service<br/>:8080/metrics]:::app
B[Payment Service<br/>:8081/metrics]:::app
end
P[(Prometheus TSDB)]:::prom -->|"Scrapes every 15s"| A
P -->|"Scrapes every 15s"| B
G[Grafana Dashboards]:::dash -->|"Query (PromQL)"| P
Alert[Alertmanager]:::prom -->|"Error Rate > 5%"| Pager[PagerDuty / Slack]
P -->|"Fires Alert"| Alert
6. Internal Working
Types of Metrics
- Counter: A number that only goes up (or resets to 0 on restart).
- E.g.,
http_requests_total. - You use math (
rate()) in Grafana to figure out the Requests Per Second.
- E.g.,
- Gauge: A number that goes up and down.
- E.g.,
active_connections,cpu_usage_percent,queue_depth.
- E.g.,
- Histogram: Samples observations into "Buckets" to calculate percentiles (p99, p95).
- E.g.,
http_request_duration_seconds. If you want to know "99% of my users experience a page load faster than 200ms", you use a Histogram.
- E.g.,
The Four Golden Signals
Google's SRE (Site Reliability Engineering) handbook states that if you can only measure four things, measure these:
- Latency: How long requests take (e.g., p99 latency is 300ms).
- Traffic: How much demand is on the system (e.g., 500 requests per second).
- Errors: The rate of failed requests (e.g., 2% of requests return HTTP 500).
- Saturation: How "full" your system is (e.g., CPU is at 90%, Database connection pool is 100% full).
7. Implementation
Why Go? Go is the language Prometheus is written in. It has the best, most native support for exposing a /metrics endpoint using the official prometheus/client_golang library.
/*
033 - Monitoring & Alerting
Demonstrates how to instrument a web server to expose
Prometheus metrics (Counters, Gauges, and Histograms).
*/
package main
import (
"fmt"
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// ── 1. Define Metrics ──
var (
// COUNTER: Only goes up. Good for total requests.
requestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests processed",
},
[]string{"path", "status"}, // Labels (tags) to slice the data
)
// GAUGE: Goes up and down. Good for current state.
activeRequests = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "http_active_requests",
Help: "Number of requests currently being processed",
},
)
// HISTOGRAM: Buckets data. Good for latency percentiles (p99).
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Latency of HTTP requests",
Buckets: []float64{0.1, 0.5, 1.0, 2.0, 5.0}, // Define buckets in seconds
},
[]string{"path"},
)
)
func init() {
// Register metrics with Prometheus's default registry
prometheus.MustRegister(requestsTotal)
prometheus.MustRegister(activeRequests)
prometheus.MustRegister(requestDuration)
}
// ── 2. The Application Logic ──
func checkoutHandler(w http.ResponseWriter, r *http.Request) {
// 1. Increment Active Requests Gauge
activeRequests.Inc()
defer activeRequests.Dec()
// 2. Start a timer for the Histogram
start := time.Now()
// Simulate work (Random sleep between 0 and 2 seconds)
sleepTime := time.Duration(rand.Intn(2000)) * time.Millisecond
time.Sleep(sleepTime)
// Simulate occasional 500 Errors
status := "200"
if rand.Float32() < 0.2 { // 20% failure rate
status = "500"
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
} else {
fmt.Fprintf(w, "Checkout Successful!")
}
// 3. Record Duration (Histogram)
duration := time.Since(start).Seconds()
requestDuration.WithLabelValues("/checkout").Observe(duration)
// 4. Record Total Requests (Counter)
requestsTotal.WithLabelValues("/checkout", status).Inc()
}
// ── 3. Test Harness ──
func main() {
// Route 1: The actual application logic
http.HandleFunc("/checkout", checkoutHandler)
// Route 2: The Prometheus Metrics Exporter (Pull Model)
http.Handle("/metrics", promhttp.Handler())
fmt.Println("🚀 Server running on :8080")
fmt.Println("👉 Hit http://localhost:8080/checkout to generate traffic")
fmt.Println("📈 View metrics at http://localhost:8080/metrics")
// Start server
log.Fatal(http.ListenAndServe(":8080", nil))
}
Sample Output (from /metrics)
If you visit http://localhost:8080/metrics after hitting the checkout endpoint a few times, you'll see the raw data Prometheus scrapes:
# HELP http_active_requests Number of requests currently being processed
# TYPE http_active_requests gauge
http_active_requests 0
# HELP http_request_duration_seconds Latency of HTTP requests
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{path="/checkout",le="0.1"} 0
http_request_duration_seconds_bucket{path="/checkout",le="0.5"} 1
http_request_duration_seconds_bucket{path="/checkout",le="1"} 3
http_request_duration_seconds_bucket{path="/checkout",le="2"} 5
http_request_duration_seconds_bucket{path="/checkout",le="+Inf"} 5
http_request_duration_seconds_sum{path="/checkout"} 5.432
http_request_duration_seconds_count{path="/checkout"} 5
# HELP http_requests_total Total number of HTTP requests processed
# TYPE http_requests_total counter
http_requests_total{path="/checkout",status="200"} 4
http_requests_total{path="/checkout",status="500"} 1
Prometheus reads this text, stores it, and Grafana translates it into beautiful line charts.
8. Complexity
| Metric | Details |
|---|---|
| Memory Overhead | Low. The app only stores a few integers in RAM (the current counters). |
| Storage (Prometheus) | High. Storing every data point every 15 seconds for a massive cluster requires large, fast disks. (Often solved by down-sampling historical data). |
9. Trade-offs
| Strategy | Pros | Cons |
|---|---|---|
| Logs | High cardinality (You can log specific User IDs). | Expensive to store. Slow to query for aggregations (e.g., "What was the average latency over 30 days?"). |
| Metrics | Extremely cheap to store over long periods. Blazing fast to query aggregations. | Low cardinality. You cannot put user_id as a label on a Prometheus metric. It will create millions of time series and crash the database (Cardinality Explosion). |
10. Production Evolution
| Feature | This Implementation | Production |
|---|---|---|
| Scraping | Manual /metrics | Kubernetes ServiceMonitors automatically discover pods and configure Prometheus to scrape them without human intervention. |
| Alerting | None | PromQL Alerts. E.g., rate(http_requests_total{status="500"}[5m]) > 0.05. If the error rate over 5 minutes exceeds 5%, fire an alert to PagerDuty. |
| SLIs and SLOs | None | Service Level Objectives. Instead of alerting on CPU (which users don't care about), production alerts on SLOs (e.g., "99% of requests must complete in < 200ms"). If the Error Budget drops, it pages someone. |
11. Common Bugs
| Bug | What happens | Fix |
|---|---|---|
| Cardinality Explosion | A junior engineer adds label: { user_id: 123 } to a metric. Prometheus creates a new time series for every single user. The Prometheus server runs out of RAM and crashes instantly. | NEVER use unbounded values (like User IDs, Session IDs, or IP Addresses) as metric labels. Labels should be finite (e.g., HTTP Status Code, Region, Method). |
| Alert Fatigue | You create an alert that fires every time CPU hits 80%. It fires 10 times a day. Engineers get annoyed and mute the channel. A real outage happens, and no one notices. | Only alert on user-facing symptoms (Latency, Errors). High CPU is not an emergency if latency is fine. |
| Restarting Counters | The app crashes and restarts. The http_requests_total counter drops from 10,000 back to 0. Grafana shows a negative spike. | Always use the rate() or increase() functions in PromQL; they automatically handle counter resets gracefully. |
12. Interview Questions
-
What is the difference between Logs and Metrics? Hint: Logs are discrete events (e.g., User 5 bought Item X). Metrics are aggregations over time (e.g., The system processed 500 requests per second with a 2% error rate).
-
Why shouldn't you put
user_idas a label on a Prometheus metric? Hint: Cardinality explosion. Prometheus stores a unique time series for every unique combination of labels. Millions of users = millions of series in RAM, which will crash the TSDB. -
What are the Four Golden Signals? Hint: Latency, Traffic, Errors, and Saturation. If you monitor these four, you have a solid grasp on the health of any system.
-
Explain the difference between a Push model (StatsD) and a Pull model (Prometheus). Hint: Push sends data to a central server; Pull exposes an endpoint for the central server to scrape. Pull is generally preferred in cloud-native environments because it's easier to scale and avoids overwhelming the app if the metrics server goes down.
13. Used By (Downstream Blocks)
- 010 Rate Limiter — Rate limiters rely heavily on monitoring to visualize how much traffic is being dropped.
- 028 Circuit Breaker — Circuit breaker state changes (OPEN/CLOSED) must be exported as metrics to trigger alerts.
14. Used In (Case Studies)
| System | Use Case |
|---|---|
| Invented Borgmon, the internal predecessor to Prometheus, defining modern SRE practices (SLAs, SLOs). | |
| Uber / Netflix | Both run massive internal metrics platforms (M3 at Uber, Atlas at Netflix) capable of handling billions of data points per second. |
15. Related Blocks
| Relationship | Block |
|---|---|
| Parallel | 032 Logging & Structured Logs |
| Next | 034 Distributed Tracing |
16. Try It Yourself
Exercise 1: Gauge vs Counter
Add a new metric: a Counter tracking payments_failed_total. Then add a Gauge tracking inventory_items_remaining (start it at 100). When a checkout succeeds, decrement the gauge. Notice how the Counter only goes up, but the Gauge goes down.
Exercise 2: PromQL Basics
If you have Docker installed, run a local Prometheus container pointing to your Go app. Write a PromQL query to calculate the percentage of errors over the last 1 minute:
rate(http_requests_total{status="500"}[1m]) / sum(rate(http_requests_total[1m]))
Website Metadata
| Field | Value |
|---|---|
| Hero Title | Monitoring & Alerting |
| Hero Subtitle | How to know your system is broken before your customers tell you on Twitter. |
| Breadcrumb | System Design → Building Blocks → Monitoring |
| Sidebar Category | Tier 5 — Production Infrastructure |
| Search Keywords | prometheus, grafana, metrics, observability, tsdb, counter, gauge, histogram, golden signals, sre |
| Internal Links | ← 032 Logging · → 034 Distributed Tracing |
| Suggested Illustration | An airplane cockpit with massive, glowing dials (Gauges) and a bright red flashing light (Alerting) while the pilot sleeps. |
| Suggested Animation | A Go app serves requests. A counter increments. A robot (Prometheus) walks by every 15s with a clipboard, writes down the number, and feeds it into a machine that draws a line chart. |