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.

Contents

  1. Requirements & Scope
  2. Back-of-the-Envelope Estimates
  3. Data Model, Schema & Validation
  4. API
  5. High-Level Architecture
  6. The Durable Buffer: Kafka
  7. Stream Processing & Guarantees
  8. Dual Sinks: Operational, Analytical, Semantic
  9. Bottlenecks & Tradeoffs
  10. Failure Modes
  11. 45-Minute Interview Walkthrough
  12. Common Mistakes
  13. Summary
  14. Check Yourself
  15. Key Terms
  16. Further Reading
  17. Concepts to understand first
  18. Worked example
  19. Runnable Python model

Concepts to understand first

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

The pipeline sits between producers of telemetry and the stores that consume it. Pin down what it must guarantee.

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.

QuantityEstimateVolume math
Ingest throughput~10 GB/s10M records/s × 1 KB = 1010 B/s.
Daily raw volume~860 TB/day10 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 PB860 TB/day × 3 replicas × 3 days ÷ ~3× compression ≈ 2.6 PB; sized to absorb a multi-hour outage + replay.
Vector path~1% embeddedOnly a routed subset (e.g. errors, key events) is embedded — embedding all 10M/s is neither useful nor affordable.

Worked estimate, step by step

StepMathResult
Ingest10M records/s × 1 KB10 GB/s
Per day (raw)10 GB/s × 86,400 s≈ 864 TB/day
Partitions10 GB/s ÷ 10 MB/s per partition≈ 1,000
Cluster write traffic10 GB/s × 3 replicas30 GB/s (before compression)
Records to embed10M/s × 1%100K/s
Kafka storage, 3 days864 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.

Sources to agents to Kafka to stream processing, fanning out to operational, analytical, and vector stores, with a dead-letter queue
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.

SinkServesStoreAccess pattern
OperationalReal-time dashboards & alertsTSDB / hot log indexLow-latency point & range reads on recent data.
AnalyticalBatch analytics, ML trainingColumnar (Parquet) / warehouseLarge scans over historical data.
SemanticLLM / RAG queryingVector store + embeddingsNearest-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

10. Failure Modes

FailureWhat happensDefense
Sink down for hoursConsumer lag growsKafka retention covers it; consumers catch up; alert on lag
Poison recordProcessor crashes in a loop on one partitionTry/catch per record; send to DLQ after N retries
Broker lossPartitions lose a leaderRF=3, acks=all, min.insync.replicas=2; leader election
Hot partitionOne partition lags while others idleBetter key, or salt the key for hot tenants
Overload beyond bufferRetention would expire unread dataShed 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.

Worked example, in orderWrite search successfully: It stores the stable record ID under its durable acceptance policy.. 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.. Replay: Search receives the record again and must idempotently reuse it; archive retries. Broker transactions do not magically cover these external sinks.. 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.1Write search successfully2Archive call fails3Replay4Handle poison/sensitive data
Read top to bottom. The table below explains the state and guarantee at each step.
StepState, calculation, and reasoning
1. Write search successfullyIt stores the stable record ID under its durable acceptance policy.
2. Archive call failsA shared worker that commits the input now would lose archive delivery. Retain required per-sink status/positions or independent consumer groups.
3. ReplaySearch receives the record again and must idempotently reuse it; archive retries. Broker transactions do not magically cover these external sinks.
4. Handle poison/sensitive dataA 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.

Primary references for these concepts

11. 45-Minute Interview Walkthrough

MinutesWhat to do
0–5Clarify rate, formats, outage tolerance, PII, sinks.
5–10Estimate: 10 GB/s, ~1,000 partitions, ~2.6 PB buffer.
10–18Draw agents → Kafka → stream processor → three sinks + DLQ.
18–28Kafka deep dive: keys, replication, retention, replay.
28–38Delivery semantics. Draw the commit-after-write flow.
38–45PII scrubbing, vector path subset, failure modes.

12. Common Mistakes

Watch out

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.

ConcernMechanism
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

  1. 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.

  2. How many partitions for 10 GB/s at 10 MB/s each?
    Answer

    10 GB/s ÷ 10 MB/s = 1,000 partitions, plus headroom.

  3. 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.

  4. 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.

  5. 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.

  6. 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.
acks=all
Ack only after in-sync replicas write.
Idempotent sink
Writing twice has the same effect as once.
DLQ
Dead-letter queue for bad records.
Schema registry
Shared, versioned record schemas.
Back-pressure
Slowing producers when consumers fall behind.

16. Further Reading


⌂