2026
Prometheus: Metrics-Based Monitoring for Modern Infrastructure
Prometheus collects numeric measurements from your infrastructure on a regular schedule, stores them efficiently and gives you a query language to ask questions about what those numbers are doing over time. Here is how the pieces fit together.
Something goes wrong. A service slows down. A job that normally completes in two seconds starts taking twenty. An error rate that sits at zero most of the time quietly climbs to five percent at three in the morning. Without metrics, you find out when a user complains. With metrics, you find out before they notice.
Prometheus is a monitoring system built around time series data. It collects numeric measurements from your infrastructure on a regular schedule, stores them efficiently and gives you a query language to ask questions about what those numbers are doing over time. The combination of a scrape-based collection model, a powerful query language called PromQL and a mature ecosystem of integrations has made it the de facto standard for metrics in most infrastructure built in the last decade.
How Prometheus collects data
Most monitoring systems use a push model: the application sends metrics to a central server. Prometheus inverts this. It polls targets on a schedule. Each target exposes a plain-text HTTP endpoint at /metrics. Prometheus scrapes that endpoint every few seconds and stores what it finds. The target does not need to know where Prometheus is running.
The pull model has practical advantages. You can scrape the same target from multiple Prometheus instances for redundancy. You can tell from a failed scrape that a target is down, which a push model cannot do if the target simply stops sending. Targets do not need Prometheus credentials. Access flows one way: Prometheus reaches out to targets, not the other way around.
Target exposes metrics
The application runs an HTTP server on a /metrics endpoint, formatted in the Prometheus text exposition format
Prometheus scrapes the target
On each scrape interval Prometheus sends an HTTP GET to the /metrics endpoint and stores what comes back
Data stored in TSDB
Metrics land in the local time series database with a timestamp attached. Each sample is a (labels, timestamp, value) tuple
PromQL queries the data
Operators use PromQL to query stored time series, calculate rates, aggregate across instances and feed dashboards or alerts
Metric types
Prometheus defines four core metric types. Each one is designed for a different kind of measurement. Picking the right type matters because it affects how you query the data and what questions you can reasonably answer.
Counter
A value that only ever goes up. Tracks how many times something has happened: requests served, errors thrown, bytes sent. Counters reset to zero when the process restarts. You query the rate of change rather than the raw number, which tells you how fast something is happening right now.
Gauge
A value that can go up or down. Memory usage, active connections, queue depth. Unlike a counter, the current value is meaningful on its own. You are interested in what it is right now, not how fast it is changing.
Histogram
Samples observations and counts them into configurable buckets. Used for things like request duration or response size where you want to know the distribution. From a histogram you can calculate percentiles: what fraction of requests completed in under 200 milliseconds, for example.
Summary
Similar to a histogram but calculates configurable quantiles on the client side before exposing them. Useful when you already know which percentiles matter and want them calculated directly by the application. Histograms are generally preferred in modern setups because they can be aggregated across multiple instances, which summaries cannot.
Labels and time series
Every metric in Prometheus is identified by a name and a set of key-value pairs called labels. Each unique combination of metric name and label values is a separate time series. Labels are what make a single metric name useful across many dimensions.
A counter called http_requests_total on its own tells you the total count of HTTP requests. Add a status label and you can split that count by response code. Add a handler label and you can see request counts per endpoint. The power is in being able to aggregate across labels when you want the big picture, then narrow down to specific label values when something looks wrong.
http_requests_total{method="GET", status="200", handler="/api/users"}
Counter. Each unique combination of label values is a separate time series.
process_resident_memory_bytes{job="api-server", instance="10.0.0.5:8080"}
Gauge. The job and instance labels come from the scrape configuration, not the application.
http_request_duration_seconds_bucket{le="0.1", handler="/checkout"}
Histogram bucket. le is the upper bound. One series per bucket per label set.
Labels are cheap to add but not free to maintain. Each unique label value combination creates another time series. A label with unlimited unique values, like a user ID or a session token, will create as many time series as there are unique values. That number of unique values blows up storage. The practical guidance is: use labels for dimensions you actually query across and keep the set of possible values for each label small.
PromQL
PromQL is the query language for Prometheus. It is built around the idea of operating on time series as first-class values. You select series by name and labels, apply functions to them, aggregate across label dimensions and compute things like rates of change and percentiles. The queries that look intimidating at first tend to follow a small number of patterns once you understand what the functions are doing.
Common PromQL patterns
rate(http_requests_total[5m])
Requests per second, averaged over the last 5 minutes. rate() handles counter resets automatically.
sum(rate(http_requests_total[5m])) by (handler)
Total request rate per endpoint, summed across all instances. The by clause controls which labels survive the aggregation.
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, handler))
99th percentile request latency per handler over the last 5 minutes. The le label is the histogram bucket boundary.
(node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
Available memory as a percentage of total. Arithmetic on time series works element-wise across matching label sets.
The rate() function is one of the first things worth understanding properly. Counters are monotonically increasing numbers. The raw value is not very useful on its own. What you usually want to know is how fast the counter is increasing, which is exactly what rate() gives you. It calculates the per-second rate of increase over a specified time window and handles counter resets gracefully when a process restarts.
Alerting
Prometheus evaluates alerting rules on a regular interval. A rule is a PromQL expression with a threshold. When the expression evaluates to a non-empty result the alert enters a pending state. If it stays pending long enough, it fires. Fired alerts are pushed to Alertmanager, which handles routing, deduplication, grouping and silencing before sending notifications to wherever they need to go.
Example alerting rule
groups:
- name: api
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "Error rate above 5% for 5 minutes"
The for duration prevents transient spikes from generating noise. An alert that fires immediately on a single bad data point will page someone for a blip that resolved itself before they even looked at it. Requiring the condition to persist for several minutes filters out the noise while still catching real problems fast enough to matter.
Service discovery
In a static environment you can list scrape targets by IP address in the Prometheus configuration file. In practice, infrastructure moves around. Containers restart on different nodes. Services scale up and down. Maintaining a static list by hand stops being viable quickly.
Prometheus supports service discovery integrations for most of the common environments: Kubernetes, Consul, EC2, Azure, GCE and more. In a Kubernetes environment, Prometheus can discover pods and services directly from the Kubernetes API. It watches for changes and updates its scrape targets automatically. Annotations on pods tell Prometheus whether to scrape them, on which port and at which path. New deployments become visible to Prometheus without any manual configuration change.
Kubernetes pod annotations for scraping
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
Storage
Prometheus stores data in a custom time series database on local disk. The format is optimised for the specific access patterns of metrics: writes are append-only, reads are typically range queries over recent time windows. Compression is aggressive because the values being stored follow predictable patterns.
Local storage is fast but not designed for long-term retention at large scale. The default retention is fifteen days. For longer retention most teams integrate remote write: Prometheus streams metrics to an external system as they are collected. Thanos, Cortex and VictoriaMetrics are common choices. They add long-term storage, horizontal scalability and global query across multiple Prometheus instances. Prometheus continues handling scraping while the remote system handles retention and aggregation at scale.
Exporters
Not every piece of infrastructure can be instrumented directly. A database, an operating system kernel, a load balancer. Exporters bridge the gap. An exporter is a process that talks to a system using its native interface, translates the metrics it gets back into the Prometheus exposition format and exposes them on an HTTP endpoint ready to be scraped.
Common exporters
node_exporterHardware and OS metrics from Linux hosts. CPU, memory, disk I/O, network interfaces, filesystem usage.
blackbox_exporterProbes endpoints over HTTP, HTTPS, DNS, TCP and ICMP. Useful for measuring external availability and response times from Prometheus's perspective.
mysqld_exporterMySQL server metrics. Query throughput, connection pool state, replication lag, InnoDB internals.
postgres_exporterPostgreSQL metrics. Table sizes, index usage, transaction rates, lock contention, replication status.
redis_exporterRedis metrics. Memory usage, hit rates, connected clients, keyspace size, command latency.
Where it fits
Prometheus does one thing: it collects numeric measurements over time and lets you query them. It does not do log aggregation. It does not do distributed tracing. It does not store events. In practice it sits alongside tools like Loki for logs and Tempo or Jaeger for traces. Grafana is almost always in the picture too, providing dashboards that sit on top of all three.
The combination of a straightforward data model, a pull-based architecture that makes targets easy to reason about and a rich ecosystem of client libraries and exporters has made Prometheus the tool most teams reach for first when they need to understand what their infrastructure is doing. Once you have it collecting data and you have a dashboard that shows you the state of your system, it is genuinely hard to go back to operating without it.
The most common starting point is node_exporter on your hosts alongside application-level instrumentation in whatever language your services are written in. Most modern frameworks either include Prometheus client libraries or have well-maintained third-party ones. Getting those first metrics flowing takes an afternoon. Building alerting rules that actually reflect the health of your system and tuning them until they page on real problems rather than noise, takes longer. That second part is where most of the real work lives.