ObservabilityV · Prometheus ArchitecturePromArchitecture
Remote Write and Remote Receive
What you'll learn
- Configure remote_write to a receiver such as Thanos Receive, Mimir, Cortex or VictoriaMetrics
- Tune queue_config from first principles and explain the shard model
- Monitor delivery health with the prometheus_remote_storage_* metrics
- Predict what happens to samples during a remote-storage outage, including the WAL horizon
Prerequisites
Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13
A single Prometheus with fifteen days of local disk is a complete monitoring system — right up until compliance asks for thirteen months, or three teams need one query across every cluster. Remote write is how a 2026-era Prometheus answers that: every sample is also streamed to a remote storage system whose whole job is long-term, global, multi-tenant retention. The local TSDB keeps doing what it does; the remote system does what the TSDB never will.
What it is
remote_write streams samples — after optional
write_relabel_configs filtering — to an HTTP endpoint as
snappy-compressed protobuf WriteRequest messages. The receivers
you will actually meet in production: Thanos Receive, Grafana
Mimir, Cortex, VictoriaMetrics (vminsert), and the hosted
endpoints (Grafana Cloud and friends).
Two things it is not:
- Not replication. Your local TSDB is untouched; remote write is a one-way stream. There is no synchronisation back.
- Not query. Reading remote data back is
remote_read, a separate, optional configuration that most people skip in favour of querying the receiver’s own query layer (Mimir, Thanos Querier) directly from Grafana.
WAL-backed delivery — the guarantee and its edge
The remote-write queue manager tails the same write-ahead log the head block uses, and it checkpoints its position as it goes. Restart Prometheus and it resumes where it stopped — as long as the WAL segment still exists.
That qualifier is the entire delivery guarantee. The WAL is truncated when the head is compacted (lesson 03), roughly every couple of hours. A receiver outage shorter than that horizon is fully replayed; an outage longer than that leaves a permanent gap in the remote store, even though your local retention is fine. Remote write buys you at-least-once delivery over hours, not days. Design alerts and expectations accordingly.
The queue model
Samples read from the WAL fan out into shards — in-memory
queues, each with its own sender goroutine. The number of shards
scales dynamically between min_shards and max_shards based on
the rate of samples coming in versus going out. Each shard batches
up to max_samples_per_send samples and flushes when the batch is
full or batch_send_deadline expires, whichever comes first.
Failures retry with exponential backoff between min_backoff and
max_backoff.
How to configure it
remote_write:
- url: 'https://mimir.example.internal/api/v1/push'
remote_timeout: 30s
headers:
X-Scope-OrgID: platform # tenant id for Mimir/Cortex-style receivers
basic_auth:
username: prometheus
password_file: /etc/prometheus/secrets/mimir.pass
tls_config:
ca_file: /etc/prometheus/tls/ca.crt
write_relabel_configs:
# do not pay to ship runtime trivia
- source_labels: [__name__]
regex: 'go_.*'
action: drop
queue_config:
capacity: 10000 # samples buffered per shard (default 2500)
max_shards: 50 # ceiling on parallel senders (default 200)
min_shards: 1 # idle floor (default 1)
max_samples_per_send: 2000 # batch size (default 500; 2000 with the v2 protocol)
batch_send_deadline: 5s # flush a partial batch after this
min_backoff: 30ms # first retry delay after a failure
max_backoff: 5s # worst-case retry delay
retry_on_http_429: true # v1 protocol treats 429 as permanent otherwise
# protobuf_message: io.prometheus.write.v2.Request # opt into RW 2.0 in 2.55
Tuning from first principles. Sustained throughput is roughly
shards × max_samples_per_send ÷ actual round-trip time. If
shards_desired pins at max_shards and lag grows: raise
max_samples_per_send first (fewer, bigger requests), then
max_shards, then capacity. Do not just max everything: queue
memory is capacity × max_shards samples held in RAM, and your
receiver pays for every oversized burst. write_relabel_configs
is the cheapest lever of all — every dropped series is egress and
receiver spend saved forever.
How to validate it
# Config sanity
promtool check config /etc/prometheus/prometheus.yml
# The two numbers that matter most: how far behind are we?
curl -s 'localhost:9090/api/v1/query?query=prometheus_remote_storage_highest_timestamp_in_seconds'
curl -s 'localhost:9090/api/v1/query?query=prometheus_remote_storage_queue_highest_sent_timestamp_seconds'
# Reachability and auth from the Prometheus host
# (a POST-only endpoint answering 405 still proves TLS, DNS and credentials path)
curl -sv https://mimir.example.internal/api/v1/push -o /dev/null
The delivery-health metrics, all under prometheus_remote_storage_:
# lag: the single best "are we keeping up" signal — alert on this
prometheus_remote_storage_highest_timestamp_in_seconds
- on() prometheus_remote_storage_queue_highest_sent_timestamp_seconds
prometheus_remote_storage_samples_total # accepted
prometheus_remote_storage_samples_retried_total # transient failures
prometheus_remote_storage_samples_failed_total # permanent rejections (4xx)
prometheus_remote_storage_samples_dropped_total # dropped before send (queue full)
prometheus_remote_storage_shards # current shard count
prometheus_remote_storage_shards_desired # what the autoscaler wants
prometheus_remote_storage_bytes_total # your egress bill
Then prove the round trip: query the receiver (Mimir, Thanos
Querier) for a recent metric and confirm your external_labels
arrived attached.
Outage semantics, precisely
Receiver down → sends fail → exponential backoff between
min_backoff and max_backoff → shards fill → new samples beyond
capacity × max_shards are dropped (samples_dropped_total) → on
recovery, everything still in the WAL is resent → anything WAL
truncation already reclaimed is gone for good.
Failure classes the metrics distinguish:
- 429 (rate limited): with the v1 protocol this is a
permanent failure unless
retry_on_http_429: true; the v2 protocol retries it by default. A 429 storm during receiver autoscaling with v1 defaults silently loses data. - 400 (bad request): never retried.
samples_failed_totalgrows until you fix the offending series or relabel it away. Classic cause: the sender’s clock skew puts samples outside the receiver’s acceptance window. - Everything else (5xx, network): retried within the WAL horizon.
How it can fail
- Receiver outage beyond the WAL horizon. Symptom: lag grows,
then
dropped_totalgrows; the long-term store has a permanent hole while local Grafana looks fine. Invisible unless you alert on the lag metric. - 429s with v1 defaults. Symptom:
failed_totalspikes that correlate exactly with receiver scaling events. - 400 rejections from bad samples. Symptom:
failed_totalgrows steadily, and the Prometheus logs name the offending labels. - Undersized queue. Symptom:
shards_desiredpinned atmax_shards, lag growing, CPU churning in small batches. Remote write “works” but is perpetually behind. - Credential or TLS expiry. Symptom: identical to an outage in
the metrics, but
curl -vshows 401s or handshake failures. - Queue memory during a long outage. capacity × max_shards samples held in RAM while the receiver is down; sized generously, the buffers themselves OOM the server mid-incident.
How to troubleshoot it
- Classify first: keeping up, behind, or dropping? The lag
expression plus
failedvsretriedvsdroppedanswers this in one dashboard row. - Read the logs. “remote storage” send errors include HTTP status codes and label hints.
- Prove the path. Curl the URL from the Prometheus host; check certificate dates; check the receiver’s own ingestion metrics.
- Check the WAL. A bloated
wal/directory during an outage tells you how much resend is pending. - Fix in order: restore the receiver, then let the queue drain. Do not restart Prometheus mid-drain unless forced — the resend survives restarts, but cold caches make it slower.
Security implications
The remote_write credentials can write metrics into your
long-term store — metric injection poisons dashboards, SLOs and
capacity models, and it does not show up anywhere local. Protect
password_file (0640), mandate TLS across any WAN boundary, and
scope tenant credentials (X-Scope-OrgID or equivalent) per team
so one leaked password cannot write as everyone. A receiver
deployed without authentication is an open write path into your
history.
Performance implications
Egress bandwidth ≈ your ingestion rate, snappy-compressed.
Marshalling CPU scales with samples per second. Queue memory is
capacity × max_shards × sample size — at the tuned example above
(10000 × 50) that is half a million samples held in RAM per remote
endpoint. The receiver side pays ingestion cost for everything you
send, which is why write_relabel_configs belongs in the cost
conversation, not just the hygiene one.
Verification
You should now be able to answer:
- Where does remote-write durability come from, and exactly where does it end?
- What do
capacity,max_shards,max_samples_per_sendandbatch_send_deadlineeach control? - Which metric pair shows remote-write lag, and why is it the one to alert on?
- What is the delivery difference between an HTTP 400 and an HTTP 429 under the v1 protocol?
- Why does a six-hour receiver outage leave a permanent remote gap even with thirty days of local retention?
Quiz
Knowledge check · 8 questions
Q1. Where does remote-write delivery durability come from?
Q2. What is the best single signal that remote write is falling behind?
Q3. A receiver outage of a week is fully recovered from the WAL once the receiver returns.
Q4. The receiver answers HTTP 400 to a batch. What happens to those samples?
Q5. Remote write cannot keep up with ingestion. Which are legitimate levers?
Q6. With the v2 remote-write protocol, HTTP 429 responses are retried by default.
Q7. capacity multiplied by max_shards roughly bounds what?
Q8. Name one open-source storage system that accepts Prometheus remote write.
Passing score: 75%. Answers are checked in this browser.