28.8. Monitoring Production Systems
Table of Contents
Why Monitoring Matters in Production
When your backend goes to production, it stops being “just code” and becomes a service that real people depend on. Monitoring is how you know:
- Is the system up?
- Is it fast enough?
- Is it working correctly?
- Are users silently failing?
Without monitoring, you only learn about problems when users complain, data is lost, or money is lost. With monitoring, you can:
- Detect issues early.
- Understand what is happening inside your system.
- Measure the impact of changes and releases.
- Plan capacity and scaling.
Think of monitoring as your “nervous system” in production. Logging, metrics and traces are different senses that together tell you how healthy your application is.
Important: A production backend without proper monitoring is not truly “in production,” no matter how well the code is written.
Key Monitoring Concepts
Before looking at tools, it helps to know the common concepts used in monitoring. These will appear in many tools and dashboards.
Black-box vs White-box Monitoring
- Black-box monitoring treats your system like a black box.
You check from the outside: “Can a user log in?”, “Is the API endpoint responding at all?”
Examples:
- Ping a URL every 30 seconds and check HTTP status.
- Simulate a user flow, such as “create account, log in, create order”.
- White-box monitoring looks inside the system.
You check internal metrics: CPU usage, number of database connections, queue sizes, error rates.
Examples:
- Number of requests per second to
/api/orders. - 95th percentile request latency.
- Count of failed DB queries.
In practice, you want both. Black-box tells you “something is wrong for the user.” White-box helps you find out “what exactly is wrong inside.”
Logs, Metrics, Traces
Most monitoring setups are built on three pillars:
| Pillar | What it is | Example questions it answers |
|---|---|---|
| Logs | Text records of events | What error happened for this specific request? |
| Metrics | Numeric time series values | Is error rate increasing? Is CPU usage too high? |
| Traces | End-to-end request paths across services | Which service is slow? Where is time spent in this request? |
You do not need to implement everything at once. A typical path:
- Start with structured logging and basic health checks.
- Add metrics and simple dashboards.
- Add tracing when you have multiple services or complex flows.
SLIs, SLOs, and Error Budgets
Large production systems often use three related concepts:
- Service Level Indicator (SLI)
A measured value, for example: - Availability percentage of an API.
- 95th percentile latency.
- Error rate per minute.
- Service Level Objective (SLO)
A target for the SLI over a period of time.
Examples:
- “API availability must be at least 99.9% per month.”
- “95% of
/checkoutrequests must complete in under 300 ms.”
- Error budget
How much failure is allowed while still respecting the SLO.
Example with availability:
- SLO: 99.9% uptime per month.
- That means at most $0.1\%$ downtime.
- In a 30-day month, there are $30 \times 24 \times 60 = 43{,}200$ minutes.
$0.1\%$ of that is $43{,}200 \times 0.001 = 43.2$ minutes. - So your error budget is about 43 minutes of downtime per month.
Rule: Define a few clear SLOs for your main APIs, and use them to guide alerts and release decisions.
Health Checks
A health check is a simple endpoint or command that returns the health status of your service. It is used by:
- Load balancers and reverse proxies.
- Orchestrators like Docker Compose, Kubernetes.
- External monitoring systems.
Types of Health Checks
You will commonly see at least two levels.
Liveness Checks
Liveness checks answer: “Is the process alive at all?” If this check fails, the system may restart the container or process.
Examples:
- Check if the HTTP server responds on
/healthz. - Check if the main event loop is running.
A simple liveness endpoint in FastAPI:
from fastapi import FastAPI
app = FastAPI()
@app.get("/healthz")
def liveness():
return {"status": "ok"}This only proves that the app is running and can respond. It does not tell you that the database works or that dependencies are healthy.
Readiness Checks
Readiness checks answer: “Can this instance handle real traffic correctly?” A service might be alive but not ready, for example if:
- Database is unavailable.
- Redis or another dependency is down.
- Migrations are still running.
Example FastAPI readiness endpoint:
from fastapi import FastAPI
import asyncpg
app = FastAPI()
@app.get("/readyz")
async def readiness():
try:
conn = await asyncpg.connect("postgres://user:pass@db/app")
await conn.execute("SELECT 1")
await conn.close()
return {"status": "ready"}
except Exception:
# Failure status and code for readiness
return {"status": "not_ready"}A load balancer can be configured to:
- Send traffic only to instances where
/readyzreturns success. - Remove instances from rotation when the readiness check fails.
Designing Health Checks
Good health checks are:
- Fast
They should not be expensive queries. Often they do a minimal check, such asSELECT 1to the database. - Non-destructive
Never modify data or trigger side effects in a health check. - Stable
Avoid checks that are flaky or depend on external networks unless required. You can separate “deep” and “shallow” checks if needed.
Choosing What to Monitor
Monitoring everything is impossible and noisy. You must choose what matters most. For backends, there are common categories.
Infrastructure Metrics
These are about the servers or containers:
| Metric | Why it matters |
|---|---|
| CPU usage | High CPU can cause slowness, thread pool exhaustion. |
| Memory usage | Memory leaks, out-of-memory kills, increased latency. |
| Disk usage | Full disks stop logs, databases and queues. |
| Disk I/O | Slow disks hurt database and file operations. |
| Network I/O | Can reveal DDoS attacks or misconfigured clients. |
Example: You might alert if CPU is above 85% for 5 minutes, or disk usage is over 90%.
Application Metrics
These are tied to your API and business logic:
| Metric | Example |
|---|---|
| Request rate (RPS) | 120 requests per second to /api/orders |
| Latency (p50, p95, p99) | p95 latency = 400 ms for /api/login |
| Error rate | 3% of requests return 5xx in the last 5 minutes |
| Queue length | 200 pending jobs in the email queue |
| Job processing time | Average job takes 2 seconds |
Percentiles:
- p50 is the median. Half of requests are faster, half slower.
- p95 means 95% of requests are faster than this.
- p99 shows worst cases for 99% of traffic.
Rule: Always monitor at least request rate, error rate, and latency for your main APIs.
Business Metrics
Business metrics show if the system is delivering value:
- Number of signups per hour.
- Number of successful orders.
- Payment conversion rate.
- Failed checkouts percentage.
These metrics are often critical, sometimes more than pure technical metrics. An app may be technically “healthy” but broken if, for example, payments always fail due to a new bug.
The “Four Golden Signals”
A common rule from SRE practice is to always monitor:
- Latency
How long each request takes. - Traffic
How many requests you get per time unit. - Errors
How many requests fail. - Saturation
How “full” your system is, for example CPU, memory, or queue length.
If you have these four, you can usually detect and debug most production issues.
Dashboards
Dashboards are visual collections of your important metrics. A good dashboard lets you answer questions quickly without digging into raw logs.
Designing Useful Dashboards
Do not create dashboards with 50 tiny graphs. Focus on clarity.
Useful patterns:
- Top-level service dashboard
- Overall request rate.
- Error rate.
- p50, p95 latency.
- CPU and memory usage.
- Database connection count.
- Per-endpoint dashboard
For critical endpoints like/login,/checkout: - Requests per second.
- Error rate.
- Latency percentiles.
- Rate of specific errors, such as validation errors vs 500s.
- Backend worker dashboard
- Queue length for each queue.
- Job processing rate.
- Job failure rate.
- Job runtime distributions.
Example table of typical graphs:
| Dashboard section | Graph example |
|---|---|
| Traffic | Total requests per second |
| Errors | HTTP 5xx per minute |
| Latency | Overall p95 latency for all endpoints |
| DB | Queries per second, slow queries count |
| Queues | Jobs queued vs processed per minute |
Making Dashboards Actionable
A dashboard is useful only if it helps you make decisions such as:
- “Do we need to roll back the last deployment?”
- “Do we need to scale up the number of replicas?”
- “Is this incident resolved yet?”
To be actionable:
- Group related graphs together. For example, show request rate and latency in the same view.
- Use consistent colors. For example, error rates always in red, latency in blue.
- Provide time range controls to zoom in on an incident period.
Alerts and On-call
Monitoring is not only about graphs. You also need alerts, which notify you when something is wrong.
What Makes a Good Alert
Good alerts:
- Indicate a real problem that requires human action.
- Are connected to user impact or SLO violations.
- Are specific and actionable.
Bad alerts:
- Trigger for minor, transient issues that self-heal.
- Fire often without clear action.
- Use confusing or vague messages.
Example of a good alert:
“Checkout API 5xx error rate > 5% for 5 minutes. Possible user impact. Check database connectivity and recent deployments.”
Example of a bad alert:
“CPU > 70% for 1 minute.”
(Might be normal under load and self-resolving.)
Basic Alert Examples
Some starter alarms for an API backend:
| Condition | Why it matters |
|---|---|
| 5xx error rate > 2% for 10 minutes | Users are hitting server errors |
| p95 latency > 1 second for 10 minutes | System is slow for many users |
| Health check failing on any instance | An instance is unhealthy |
| DB connection failures > X per minute | Database issues or misconfiguration |
| Queue length > N for more than 15 minutes | Background jobs falling behind |
Start with a small set of alerts that directly relate to user impact. Expand slowly, and review alerts after incidents.
On-call Basics
In teams, someone is usually “on-call”, meaning they receive alerts outside normal working hours. Even in small projects, you might:
- Send alerts to email or messaging tools like Slack.
- Use a phone app to get push notifications.
Guidelines:
- Rotate on-call responsibilities fairly.
- Keep runbooks with “What to check when this alert fires.”
- After major incidents, review what went wrong and improve alerts and dashboards.
Monitoring Logs in Production
Logs are a major part of monitoring in production. They help you see detailed events and debug specific failures.
Structured Logging
In production, plain text logs are hard to search. It is better to use structured logging, usually JSON, where each field is a key-value pair.
Example of a structured log entry:
{
"timestamp": "2026-08-28T15:23:10Z",
"level": "INFO",
"message": "Created new order",
"order_id": 12345,
"user_id": 678,
"endpoint": "/api/orders",
"duration_ms": 85,
"status_code": 201
}This type of log can be easily filtered, for example:
- Show all logs with
status_code=500. - Show logs for
order_id=12345. - Show all logs where
duration_ms > 500.
Centralized Logging
In production you usually have:
- Multiple application instances.
- Many containers, servers, or pods.
Reading logs directly from each machine is not practical. You need centralized logging, where:
- Each instance writes logs to stdout or a local file.
- A logging agent or service collects logs.
- Logs are sent to a central system like Elasticsearch, Logstash, Kibana (ELK), or Loki, or a hosted logging platform.
Centralized logging lets you:
- Search across all instances.
- Filter by fields such as endpoint, user id, correlation id.
- Create visualizations of log metrics, such as counts over time.
Correlation IDs
For debugging user requests, it helps to tag all logs from a single request with a correlation id or request id.
Flow:
- A request arrives with no id.
- The gateway or application generates a random id, for example
req-abc123. - This id:
- Is added to the response header.
- Is included in every log line created while handling that request.
- May be passed to downstream services.
This way, when a user reports a problem with a specific request, you can search logs for that id and see the whole story.
Tracing and Distributed Tracing
As systems grow into microservices and multiple backends, a single request might:
- Come through an API gateway.
- Call a user service.
- Call an order service.
- Call a payment service.
If the request is slow, which part is responsible? Logs are helpful but can be complex. Tracing records a flow of sub-operations for each request.
Basic Concepts
- Trace
The whole journey of a single request through the system. - Span
A single unit of work inside the trace, such as: - HTTP request handler in service A.
- Database query in service A.
- HTTP call from service A to service B.
Each span has:
- Start time and end time.
- Name.
- Optional attributes, such as
db.statementorhttp.url.
Distributed Tracing
In distributed tracing, spans from different services are linked using:
- A trace id, shared across all spans.
- Individual span ids with parent-child relationships.
Example:
- Trace id:
1234 - Spans:
api_gateway(root span).user_service(child ofapi_gateway).order_service(child ofapi_gateway).payment_service(child oforder_service).
In dashboards, you see a timeline that shows where time is spent. If the payment service is slow, you see a long bar for that span.
OpenTelemetry and Tools
Modern systems often use OpenTelemetry to:
- Collect traces, metrics and logs in a standard format.
- Export them to tools like Jaeger, Zipkin, or hosted solutions.
Step by step:
- Add OpenTelemetry SDK to your application.
- Instrument your HTTP server and client, database calls, and background workers.
- Export traces to a backend that can visualize them.
This is especially valuable once you have more than one service involved in a request.
Monitoring Databases and Queues
Many production incidents start with the database or background processing. You should monitor these separately.
Database Monitoring
Key metrics:
| Metric | Why it matters |
|---|---|
| Connections in use | Hitting max connections causes failures |
| Slow queries count | Can reveal missing indexes or bad queries |
| Query throughput | Capacity and load on the database |
| Replication lag (if replicas) | Stale reads or risk of inconsistency |
| Cache hit rate (if DB caching) | Efficiency of caching strategies |
PostgreSQL, for example, can expose:
- Slow queries, using logs or
pg_stat_statements. - Lock waits and blocked queries.
- Buffer cache statistics.
Monitoring these metrics can help you detect:
- Need for indexing or query optimization.
- Problems after deployment, such as a new slow query.
Queue and Worker Monitoring
If you use background workers such as Celery with Redis:
Key metrics:
| Metric | Why it matters |
|---|---|
| Queue length | Too long means jobs are delayed |
| Job throughput | Jobs processed per minute |
| Job failures | High failure rate means bugs or external issues |
| Job runtime | Slow jobs may cause backlogs |
You can set alerts when, for example:
- Queue length for
emailexceeds 1,000 for 10 minutes. - Job failure rate is over 5% for 15 minutes.
Continuous Improvement of Monitoring
Monitoring is not something you finish once. It evolves as your application, architecture and user traffic change.
Learn from Incidents
After each incident, ask:
- Did we get alerted early enough?
- Did we have enough data (metrics, logs, traces) to debug quickly?
- Which new metric or log would have helped?
Then:
- Add new metrics or dashboards.
- Improve existing alerts and thresholds.
- Update documentation and runbooks.
Monitor Your Monitoring
Monitoring systems themselves can fail or become overloaded. Consider:
- Checking that your monitoring agent is running on each node.
- Verifying that important metrics are updating, not flatlining.
- Alerting if no logs have been received from an instance for some period.
Start Simple, Grow Gradually
For a small backend, a realistic starting point:
- Health checks for liveness and readiness.
- Structured logging and centralized log storage.
- Basic metrics:
- Request rate.
- Error rate.
- p95 latency.
- CPU and memory.
- A few essential alerts tied to user impact.
As you grow:
- Add tracing, business metrics, and advanced dashboards.
- Define SLOs and error budgets.
- Introduce on-call rotation and incident runbooks.
Key Principle: Monitoring should help you sleep better, not create constant noise. Start with the most important signals and refine them based on real incidents.
With solid monitoring in place, your production backend becomes observable. You can see what is happening, understand why it is happening, and respond quickly when things go wrong.
Views: 6
KAHIBARO