Designing a High-Volume Telemetry Ingestion Pipeline
A system design interview guide to the pipeline that swallows a firehose of telemetry from heterogeneous sources and lands it, reliably and losslessly, into stores that serve both real-time analytics and LLM/RAG querying.
Student edition · start with the guarantee
Accept heterogeneous signals into bounded durable pipelines with clear per-signal delivery and privacy policies.
Correctness contract: Every accepted record has stable identity/schema/time/scope; source and sink durability, dedup horizons, and quarantine behavior are explicit.
Basic HTTP, databases, and programming are assumed. Specialized terms are explained below. Named products motivate the problem; this is an educational design, not a claim about their current internals. Capacity numbers are illustrative decimal payload estimates, not benchmarks. Add indexes, protocol overhead, replicas, headroom, and failure capacity before provisioning.
Behind every observability platform is an ingestion pipeline doing the unglamorous, load-bearing work: accepting telemetry from tens of thousands of sources in a dozen formats, buffering it durably so nothing is lost when a downstream store hiccups, transforming and validating it in flight, and routing it to whichever stores need it. The modern twist is that there are now two very different consumers of that data — the traditional real-time analytics path (dashboards, alerts, ad-hoc queries) and a newer semantic path that embeds telemetry into a vector store so an LLM can answer questions over it via retrieval-augmented generation. A good design serves both from one pipeline while keeping the operational and analytical stores cleanly separated. This guide builds it: the durable buffer at the center, stream processing for parse/enrich/route, the delivery guarantees (exactly-once, dead-letter, PII scrubbing), and the dual-sink fan-out.
Each definition includes a concrete example and the condition under which the mechanism is useful. You can read this guide without opening another guide.
Offsets and checkpoints
An offset is a position in one log partition. A checkpoint is a recoverable, consistent record of input positions and processing state. On restart, state must correspond exactly to the input that is considered processed.
Example. If a batch increments a total and crashes before saving its offset, replay increments twice. Commit the total, dedup state, and partition position together, or use a compatible transactional/idempotent sink.
Limit. Saving an offset after a write avoids that specific loss window but can still duplicate; framework checkpoints alone do not make arbitrary external effects transactional.
Backpressure and load shedding
Backpressure tells an upstream producer that the downstream stage cannot accept more work. Buffering delays that signal; shedding deliberately rejects or drops lower-priority work when a bound is reached.
Example. A sink accepts 100 events/s while input is 150/s. A 1000-event queue fills in 20 seconds. The system must then block, reject, or apply its documented drop policy.
Limit. A finite queue cannot absorb an infinite outage. Prioritized messages still need hard quotas, deadlines, and bounded client queues; otherwise an exemption can exhaust the system.
Pub/sub and durable queues
Publish/subscribe routes an event to interested subscribers; it may be ephemeral or durable. A queue/log with retained records and offsets allows replay after a consumer is disconnected.
Example. An online gateway misses a comment while restarting. Ephemeral Redis Pub/Sub cannot replay it; a durable comment log can fetch after the gateway's last sequence.
Limit. Pub/sub alone does not promise persistence, global order, or exactly-once effects. Broker choice, acknowledgements, retry horizon, and client catch-up protocol define those guarantees.
Cardinality
The number of distinct identities a store must track. A metric series is its metric name plus the full label combination. The product of label-value counts is an upper bound when not every combination occurs.
Example. 100 hosts × 20 routes × 5 status codes can produce up to 10,000 series. Adding a unique request_id can make every request create a new series.
Limit. Hashing every ID to a distinct digest does not reduce cardinality or guarantee anonymity. Use bounded categories, explicit limits, or intentional fixed-bucket aggregation.
Transactional outbox
A business transaction writes its state change and an event row to the same database. A relay publishes committed event rows later, retrying until the broker durably accepts them.
Example. An order and its OrderCreated outbox row commit together. A relay crash after publish but before marking sent publishes the same event ID again on restart.
Limit. The relay is at-least-once, not magically exactly-once. Consumers dedup transactionally; preserve per-entity sequence when order matters. CDC is change data capture: reading committed database changes.
Spans and trace context
A span records one operation with a trace ID, span ID, parent or links, timing, and attributes. Context propagation sends the current trace/span identity across HTTP, RPC, thread, and queue boundaries so related work can be assembled.
Example. A gateway starts a trace, injects W3C traceparent into a service call, and the service links its queue consumer work to the producer span.
Limit. Cross-host wall clocks can skew timelines. A parent-child graph is not always a tree because asynchronous work can have multiple links. Instrumentation must be configured; IDs are not automatically present in every log.
1. Requirements & Scope
Intuition
Think of a post office sorting hall. Trucks drop off mail all day, far faster than any one street can take it. The hall has a huge storeroom, so nothing is lost when a van breaks down. Kafka is that storeroom. Stream processors are the sorters, and each sink is a delivery route.
Clarifying questions to ask
Peak records per second and average size?
Which signal types, and which formats must we accept?
Longest downstream outage we must survive without loss?
Which sinks truly need no duplicates?
Any PII or compliance rules on the data?
What share of data needs the semantic (vector) path?
The pipeline sits between producers of telemetry and the stores that consume it. Pin down what it must guarantee.
Heterogeneous ingest. Accept logs, metrics, traces, and events from many source types (apps, hosts, network devices, third parties) in many formats, and normalize them.
Durability & no loss. Once accepted, a record must not be lost even if every downstream store is temporarily unavailable. The buffer decouples producers from consumers.
Throughput & backpressure. Sustain millions of records per second and degrade gracefully under spikes rather than dropping data or stalling producers.
Transform in flight. Parse, validate against a schema, enrich with context, scrub PII, and route each record to the right sink(s).
Dual consumers. Feed a real-time analytical path and a semantic (embedding/vector) path for LLM/RAG querying, keeping operational and analytical stores separated.
Delivery semantics. At-least-once by default, exactly-once where correctness demands it, with a dead-letter path for records that cannot be processed.
Interview tip: Lead with the durable buffer. The single most important decision in an ingestion pipeline is putting a durable, replayable log (Kafka) between producers and consumers — it is what turns “a fragile chain that loses data when anything downstream fails” into “a pipeline where a store can be down for an hour and you just catch up.”
2. Back-of-the-Envelope Estimates
Assume 10M records/second at peak, averaging 1 KB each.
Quantity
Estimate
Volume math
Ingest throughput
~10 GB/s
10M records/s × 1 KB = 1010 B/s.
Daily raw volume
~860 TB/day
10 GB/s × 86,400 s ≈ 8.6×1014 B.
Kafka partitions
~1,000+
10 GB/s ÷ ~10 MB/s per partition ≈ 1,000 partitions for parallelism + headroom.
Buffer retention
~3 days = ~2.6 PB
860 TB/day × 3 replicas × 3 days ÷ ~3× compression ≈ 2.6 PB; sized to absorb a multi-hour outage + replay.
Vector path
~1% embedded
Only a routed subset (e.g. errors, key events) is embedded — embedding all 10M/s is neither useful nor affordable.
Worked estimate, step by step
Step
Math
Result
Ingest
10M records/s × 1 KB
10 GB/s
Per day (raw)
10 GB/s × 86,400 s
≈ 864 TB/day
Partitions
10 GB/s ÷ 10 MB/s per partition
≈ 1,000
Cluster write traffic
10 GB/s × 3 replicas
30 GB/s (before compression)
Records to embed
10M/s × 1%
100K/s
Kafka storage, 3 days
864 TB × 3 replicas × 3 days ÷ 3
≈ 2.6 PB
Read the table
At about 20 TB of disk per broker, 2.6 PB needs 130 or more brokers. Retention length is a direct cost dial.
The numbers force two conclusions: the buffer must be partitioned into the low thousands to get the parallelism to keep up, and you cannot embed everything — the semantic path is fed a deliberately routed, filtered subset, which is why routing at processing time matters.
3. Data Model, Schema & Validation
Heterogeneous sources produce a mess of formats, so the first job is to normalize everything into a common envelope while validating structure. A typical normalized record carries a timestamp, a source/type, a set of resource attributes, the payload, and correlation IDs.
# normalized telemetry record (envelope + payload)
{
"ts": 1751779200000, # event time, ms
"source": "svc-checkout", # who produced it
"type": "log" | "metric" | "trace" | "event",
"resource": { "host": ..., "region": ..., "env": ... },
"trace_id": "4bf92f...", # correlation, if in a request
"body": { ... }, # the actual payload
"schema_ver": 3 # for evolution
}
Schema & validation. Use a schema registry (Avro/Protobuf/JSON Schema) so producers and consumers agree on structure and it can evolve compatibly. Validate at the edge: a record that fails schema validation is not silently dropped and not allowed to poison the pipeline — it is routed to a dead-letter queue for later inspection. Schema-on-write (validate at ingest) gives clean downstream data; schema-on-read (store raw, interpret later) gives flexibility. High-volume telemetry usually does light validation on write plus schema-on-read for the flexible payload, so a new field from a source does not break ingestion.
4. API
# edge ingest (agents batch + compress)
POST /v1/ingest Content-Encoding: zstd
body: [ Record, Record, ... ] # up to ~1 MB per batch
202 Accepted # only after Kafka acks=all
429 Too Many Requests Retry-After: 2 # per-tenant quota: back-pressure
400 Bad Request # fails schema, with per-record errors# topics
telemetry.raw.{logs|metrics|traces|events} key = trace_id or source, RF=3, min.insync.replicas=2
telemetry.dlq original bytes + error + stage
telemetry.clean.* scrubbed, enriched; fed to sinks
Return 202 only after Kafka acknowledges. That is the durability promise to the producer.
5. High-Level Architecture
The pipeline is a fan-in to a durable buffer, then stream processing, then a fan-out to separated sinks.
Heterogeneous sources ship through collection agents into Kafka, the durable partitioned buffer. Stream processing parses, enriches, routes, and scrubs PII, then fans out to three cleanly separated sinks — an operational store for real-time analytics, an analytical store for batch, and a vector store for LLM/RAG — while invalid records go to a dead-letter queue.
The shape is deliberate. Agents at the edge batch and compress so producers are cheap and the network is efficient. Kafka in the middle decouples everything: producers write as fast as they like, consumers read at their own pace, and a slow or dead sink never backs pressure onto the producers. Stream processing is where all the transformation and routing happens. And the fan-out separates concerns: the operational store is tuned for low-latency reads, the analytical store for cheap batch scans, and the vector store for semantic retrieval — three different access patterns, three different stores, one pipeline.
6. The Durable Buffer: Kafka
Kafka partitions provide append order, not necessarily event-time/causal order across concurrent producers. One active group member owns a partition at a time; asynchronous processing still needs ordered completion/checkpointing. Stable related keys help routing, but trace spans from different hosts can arrive in any order.
For a common durable broker-failure contract, use suitable replication placement, acks=all, min.insync.replicas, producer idempotence, and safe leader election settings. These are configuration guarantees under a fault model, not universal power-loss protection for every storage mode. acks=all waits for the current in-sync set, whose minimum matters.
Retention can be age or size constrained; downstream delay is safe only while unread input remains available and recovery throughput drains it. Agents/SDKs/brokers have finite buffers. Define explicit per-signal drop/backpressure and archive/replay policy.
7. Stream Processing & Guarantees
At-most-once may be acceptable for sampled debug data, not for accepted audit/billing records. At-least-once allows retries; exactly-once logical effects require stable identities plus transactional/idempotent sink state under a specified horizon. Kafka transactions cover supported Kafka records/offsets, not arbitrary external stores.
Use independent sink consumer groups or durable per-sink status to isolate failures. Required durable sink/quarantine writes precede input-position advancement; committing after one of several required writes can lose the rest. Replays reuse IDs and immutable payloads, and mismatch is an error. Checkpoints and contributions must agree.
Redact/minimize before any broker/archive/DLQ whose policy forbids retaining raw sensitive data. Hashing alone is not anonymity. Embedding/RAG is optional asynchronous derived work with tenant filters and source citations; approximate retrieval and an LLM response are not proof of a causal incident explanation. The compact pipeline snippets are pseudocode, not runnable OTel configuration.
8. Dual Sinks: Operational, Analytical, Semantic
The routing step fans each record to the sinks that need it, and the sinks are kept cleanly separated because they serve fundamentally different access patterns.
Sink
Serves
Store
Access pattern
Operational
Real-time dashboards & alerts
TSDB / hot log index
Low-latency point & range reads on recent data.
Analytical
Batch analytics, ML training
Columnar (Parquet) / warehouse
Large scans over historical data.
Semantic
LLM / RAG querying
Vector store + embeddings
Nearest-neighbor search on meaning.
Operational vs analytical separation is the classic move: the operational store is optimized for freshness and low-latency reads and kept small, while the analytical store holds cheap, long-retained columnar data for scans and ML. Keeping them apart means a heavy analytical scan never contends with the low-latency operational path.
The semantic / RAG path
The newer sink supports natural-language querying of telemetry (“why did checkout error rates spike last night?”). A routed subset of records — typically errors, anomalies, and notable events, not the full firehose — is passed through an embedding model that turns each into a vector capturing its meaning, and the vectors are stored in a vector store (with an ANN index like HNSW). At query time, the user’s question is embedded, the vector store returns the nearest telemetry, and that context is fed to an LLM to generate a grounded answer — retrieval-augmented generation. Crucially this is a separate sink: embedding is expensive, so only a filtered subset flows here, and it never sits on the operational hot path.
Interview tip: When RAG comes up, resist embedding everything. The senior answer is “route a filtered subset to the vector store” — embedding 10M records/second is neither affordable nor useful, and the value of RAG comes from retrieving the relevant few, not indexing the whole firehose. Keep the semantic path off the operational critical path.
9. Bottlenecks & Tradeoffs
Partition key skew. The most common real bottleneck — a poor key funnels a hot tenant or hot service onto one partition, capping throughput. Key for even spread while preserving needed ordering.
Exactly-once vs throughput. Transactional exactly-once adds coordination overhead and latency. Use it only where duplicates are a correctness bug; prefer at-least-once + idempotent sinks elsewhere.
Buffer retention vs cost. Longer Kafka retention means surviving longer outages and more replay flexibility, but more storage. Size it to your worst tolerable downstream outage.
Schema-on-write vs on-read. Strict validation gives clean data but rejects anything unexpected and couples producers tightly; loose validation is flexible but pushes correctness downstream. Most pipelines do light write validation + schema-on-read.
Backpressure vs loss. Under sustained overload beyond buffer capacity you must eventually choose: shed low-value telemetry (sampling) or risk falling permanently behind. Decide the shedding policy deliberately, by signal priority.
10. Failure Modes
Failure
What happens
Defense
Sink down for hours
Consumer lag grows
Kafka retention covers it; consumers catch up; alert on lag
Shed low-priority signals first; per-tenant quotas at the edge
Worked example: one sink fails after another succeeds
One record is routed to both search and archive.
Read top to bottom. The table below explains the state and guarantee at each step.
Step
State, calculation, and reasoning
1. Write search successfully
It stores the stable record ID under its durable acceptance policy.
2. Archive call fails
A shared worker that commits the input now would lose archive delivery. Retain required per-sink status/positions or independent consumer groups.
3. Replay
Search receives the record again and must idempotently reuse it; archive retries. Broker transactions do not magically cover these external sinks.
4. Handle poison/sensitive data
A quarantine write itself needs durable success before advancing its required position. Redact before any retained broker/archive/DLQ where policy requires it.
Check your understanding: Does hashing PII guarantee anonymity?
No. Stable hashes can be linked or dictionary-reversed. Apply a deliberate minimization/redaction/keyed-pseudonym policy and access/retention controls.
Run a small, testable model
Download concepts.py. It uses only the Python 3.10+ standard library. From the directory where you saved it, run:
python3 concepts.py dedup
python3 concepts.py test
The local contribution model demonstrates transactional replay; each external sink needs its own supported durability/idempotency contract.
The program contains deterministic assertions and failure traces. It is a learning model, not a production service: it does not implement network consensus, distributed durability, authentication, or third-party delivery. Read its contracts and try changing an input before extending it.
Kafka deep dive: keys, replication, retention, replay.
28–38
Delivery semantics. Draw the commit-after-write flow.
38–45
PII scrubbing, vector path subset, failure modes.
12. Common Mistakes
Watch out
Writing straight from producers to stores with no durable buffer.
Claiming Kafka transactions make a database write exactly-once.
Committing offsets before the sink write. That is at-most-once.
Letting one bad record block a partition.
Embedding every record into the vector store.
13. Summary
A high-volume telemetry pipeline is organized around a durable, replayable buffer that decouples producers from consumers, with in-stream processing that validates, enriches, scrubs, and routes to cleanly separated sinks — including a semantic path for LLM/RAG.
Concern
Mechanism
How do we accept many sources?
Edge agents normalize formats into a common envelope; schema registry for structure.
How do we never lose data?
Kafka: replicated, partitioned, retained, replayable log between producers and consumers.
How do we get throughput?
Partition for parallelism with a key that spreads load and preserves needed ordering.
How do we transform reliably?
Stream processor parses, enriches, scrubs PII, and routes; poison records go to a DLQ.
How do we avoid dupes/loss?
At-least-once + idempotent sinks by default; transactional exactly-once where correctness needs it.
How do we serve both consumers?
Separate operational (hot, low-latency) and analytical (columnar, batch) sinks off one buffer.
How do we support LLM/RAG?
Embed a routed subset into a vector store; retrieve nearest context at query time for the LLM.
The recurring theme: put a durable log in the middle and everything else gets easier — producers decouple from consumers, outages become catch-up instead of loss, and new sinks (analytical, semantic) are just new consumers replaying the same stream. Specialize the sinks; unify on the buffer.
14. Check Yourself
Why put Kafka between producers and stores?Answer
It decouples them. A slow or dead store only makes consumers fall behind. Data waits in Kafka and is replayed later.
How many partitions for 10 GB/s at 10 MB/s each?Answer
10 GB/s ÷ 10 MB/s = 1,000 partitions, plus headroom.
Do Kafka transactions give exactly-once writes to Postgres?Answer
No. They are atomic only across Kafka topics and offsets. For Postgres use an idempotent upsert, or store the offset in the same DB transaction.
What delivery guarantee do you get if you commit the offset before writing the sink?Answer
At-most-once. A crash after the commit loses that batch.
Where should PII scrubbing happen, and why?Answer
In the stream processor, before any queryable store. Doing it once in-stream beats redacting every store later.
Why embed only ~1% of records?Answer
Embedding 10M/s costs too much and adds little. RAG needs the relevant few, such as errors and anomalies.
15. Key Terms
Topic / partition
Named log, split into ordered shards.
Consumer group
Consumers sharing a topic’s partitions.
Offset
An offset is a position in one log partition. A checkpoint is a recoverable, consistent record of input positions and processing state. On restart, state must correspond exactly to the input that is considered processed. Example and limits above.