Scaling Prometheus
The first scaling move is a bigger machine, and it goes further than most teams believe: one well-provisioned Prometheus carries millions of active series and hundreds of thousands of samples per second. Harborline's tens of thousands of series occupy a rounding error of that ceiling. So this topic is a survey in escalation order — sharding, federation, remote_write, the clustered rebuilds — and an argument for recognizing, honestly, which rung you are actually standing on.
The survey matters even at Harborline's size, because one early choice decides whether escalation stays cheap later. external_labels set today, before the first sample ever leaves the server, keep every rung on the ladder reachable; history without them cannot be retro-labeled.
Vertical Limits
Memory scales with active series — the head keeps recent chunks and index entries for every live one — and CPU scales with samples per second plus query load. The number to watch is prometheus_tsdb_head_series against RAM, and the ceiling is high enough that most teams retire the server before they reach it.
The common failure at this rung is not outgrowing the machine. It is detonating cardinality — a user_id label, coming in Chapter 5 — and mistaking the explosion for organic growth. Per-job scrape_samples_scraped separates the two: real traffic grows across the fleet, a label accident grows in one job. The fix for the second is relabeling from topic 14, not architecture.
Functional Sharding
Before any clustering, run more Prometheus servers: split by team, environment, or failure domain, each one small, independent, and blast-radius-contained. A shard's death blinds only its own patch, and each shard alerts on what it scrapes. The cost is the global picture — no single server can answer a query spanning shards — and the arrangement is acceptable exactly as long as nobody needs one.
Federation
Federation is a parent Prometheus scraping /federate on the leaves, pulling only series that match explicit match[] selectors. It is built for hierarchies of aggregates: the leaves keep raw detail and evaluate recording rules (Chapter 4), and the parent scrapes the resulting roll-ups to serve the global dashboard.
Used to copy everything, it fails on schedule: the parent re-ingests the fleet's entire output, the /federate response grows until the scrape exceeds its timeout, and you have built a slower, lossy imitation of what remote_write does properly. Federation is a roll-up channel, not replication.
remote_write and Agent Mode
remote_write streams every sample to an external system as it is ingested, buffered through the WAL, so a receiver outage is survivable for hours rather than seconds. Remote-Write 2.0 extends the protocol with metadata, exemplars, and native-histogram support. Agent mode — the --agent flag, stable in the 3.x line — strips the server down to scrape, relabel, forward: no local queries, no rule evaluation, no alerting, built for edge collectors feeding one central store.
# prometheus.yml — samples stream out as they are ingested
global:
external_labels:
env: prod
replica: A
remote_write:
- url: "https://metrics-store.example/api/v1/push"
The two external labels are load-bearing. env keys every aggregation the backend does, and replica is how an HA pair avoids double counting — the receiver deduplicates on the one label that distinguishes the two replicas. The buffer has a limit, too: WAL segments are truncated as the head flushes, so the prometheus_remote_storage_* lag metrics are worth an alert before any receiver ever goes down.
The Clustered Rebuilds
When one dataset must outgrow one node, three architectures wait. Thanos bolts onto the Prometheus servers you already run: a sidecar ships their immutable blocks to object storage, a global query layer fans out across all of them, and old data finally gets the downsampling topic 15 refused. Mimir — Cortex lineage — inverts the model: a horizontally scalable, multi-tenant backend fed purely by remote_write, with Prometheus reduced to an agent at the edge.
VictoriaMetrics is the pragmatic third path: a Prometheus-compatible engine with its own storage format, run as a single binary or a cluster, with a markedly lower resource appetite. Three tools, three verbs — extend, centralize, substitute — and the comparison box pins down which requirement justifies each.
When One Server Is Enough
Most of the time, and specifically: series count in the low millions or less, retention needs measured in weeks, no hard multi-region or multi-tenant query requirement. Harborline meets every criterion, so the honest architecture is the one already running — one server on obs-01, sized with topic 15's arithmetic, external labels set so the ladder stays climbable. The chapter ends where it began, with one binary; the difference is that it now watches six hosts and three stores, and the next Saturday-morning complaint will land on a system that finally has data.
Thanos — extends the Prometheus servers you already run: sidecars, object storage, a global query layer, downsampling for old data. Choose it to add long-term storage and a global view without re-platforming.
Mimir — centralizes: everything arrives via remote_write into one horizontally scalable, multi-tenant backend. Choose it for many teams or clusters feeding one governed store.
VictoriaMetrics — substitutes: a compatible, resource-frugal engine, single binary or clustered. Choose it when the driver is cost and operational simplicity rather than ecosystem purity.
- Federating all series into a "global Prometheus" — the parent re-ingests the whole fleet's output, the
/federatescrape grows past its timeout, and you have built a slower, lossy copy of what remote_write does properly. Federate aggregates only. - Adopting Thanos or Mimir at 100,000 series — five-plus new components, object storage, and a query-path rewrite to solve a problem a $200-a-month VM doesn't have. The clustered systems earn their operational cost at a scale most teams never reach.
- Running an HA pair that remote-writes from both replicas with identical labels — every sample arrives twice and every
rate()in the backend reads double. Each replica needs a distinguishingreplicaexternal label, and the backend needs deduplication configured. - Letting a remote_write outage outlive the WAL — the send queue rides the WAL, which is truncated as the head flushes, so a receiver down for many hours leaves a permanent gap in the remote store. Alert on
prometheus_remote_storage_*lag from day one. - Treating agent mode as a lightweight full Prometheus — an agent evaluates no rules and fires no alerts, so a site whose only collector is an agent has outsourced its paging to the far end of a WAN link. Keep rule evaluation where the data is born, or accept that link into the paging path.
- Measure before scaling —
prometheus_tsdb_head_seriesand per-jobscrape_samples_scrapedtell you whether growth is real traffic or one team's label accident, and the fix for the second is relabeling, not architecture. - Shard by failure domain first — one Prometheus per environment or region, each alerting on its own patch, so no single server's death blinds the whole fleet.
- Set
external_labels(env,region,replica) on every server before the first sample leaves via federation or remote_write — every aggregation layer keys on them, and history cannot be retro-labeled. - Choose the escalation rung by the requirement that forces it — weeks-long retention means remote storage, global query means a Thanos-style layer over stores, multi-tenancy means Mimir — and stop at the first rung that satisfies it.
Knowledge Check
prometheus_tsdb_head_series triples in a week while customer traffic is flat. What is the first move?
- Provision a bigger VM before the head exhausts RAM
- Begin a Thanos rollout for horizontal headroom
- Find the offending job — this is a cardinality accident
- Lower retention.time so the disk survives the growth and the head shrinks
What is federation actually built to carry between leaves and the parent?
- Every series, as a disaster-recovery replica
- Pre-aggregated roll-ups selected by
match[] - Alert notifications from leaf to parent
- Scrape configs, so leaves stay in sync
A remote site runs only a Prometheus agent shipping to a central Mimir. What has the site given up?
- Local alerting — its pages now depend on the WAN link
- The ability to scrape and relabel local targets
- All samples during any receiver outage, instantly
- Its external labels, which agents cannot set
An HA pair remote-writes from both replicas, and every rate() in the backend reads exactly double. What is missing?
- A recording rule dividing every result by two
- A relabel rule dropping one replica's samples
- A
replicaexternal label plus backend deduplication - A TCP load balancer in front of the receiver to drop duplicate samples
A team wants long-term storage and one global query view, but refuses to re-platform its existing per-region Prometheus servers. Which system fits?
- Mimir
- Thanos
- VictoriaMetrics
- A federation parent scraping all series
You got correct