Designing a Petabyte-Scale Data Pipeline with Strong SLAs

A system design interview guide to moving petabytes through batch and streaming stages that land correct, complete, and on time — where the hard requirement is not throughput but the freshness and quality guarantee attached to the output.

Moving a petabyte is, by now, a solved problem: throw a distributed engine and enough machines at it and the bytes will flow. What makes a petabyte pipeline hard is the promise wrapped around it — that a dataset will be ready by a deadline, complete, correct, and reproducible, every single day, even when an upstream source is late or a schema changes underneath you. That promise is the SLA, and it is what turns a data pipeline from a script into a system. This guide builds such a pipeline: the batch-versus-streaming split (lambda and kappa), ingestion and partitioning, columnar storage with Parquet, a compute engine (Spark or Flink), orchestration and DAGs, and the reliability machinery — data quality gates, schema evolution, lineage, idempotent reprocessing, backfills, and freshness monitoring — that keeps the SLA honest at petabyte scale.

Contents

  1. Requirements & Scope
  2. Back-of-the-Envelope
  3. Key Definitions
  4. Architecture: Lambda & Kappa
  5. Storage, Partitioning & Parquet
  6. Compute: Spark vs Flink
  7. Quality, SLAs & Reprocessing
  8. Bottlenecks & Tradeoffs
  9. Summary

1. Requirements & Scope

At this scale the requirements that matter are the guarantees, not the volume. Anyone can process a petabyte; the design is judged on whether the output can be trusted and whether it arrives on time.

2. Back-of-the-Envelope

Numbers show which choices are forced by scale — particularly why columnar storage and partition pruning are not optional.

QuantityAssumptionResult
Daily volume~1 PB/day ingested~12 GB/s sustained average
Event rate~1 KB events~10M+ events/sec on the stream
Full-scan costread 1 PB at ~10 GB/s aggregate~28 hours — scanning everything is a non-starter
Partition pruningquery one day of a yearread ~1/365 — minutes, not hours
Columnar projectionread 5 of 100 columns~20× less I/O than row storage
Compressioncolumnar + zstd on typed data~3–10× smaller on disk

The lesson is stark: a full scan of a petabyte takes over a day, so the entire storage design exists to avoid reading data. Partitioning skips irrelevant files, columnar layout skips irrelevant columns, and statistics skip irrelevant row groups. Every query should touch a tiny fraction of the petabyte — that is the whole game.

3. Key Definitions

A few terms recur throughout and are worth fixing precisely.

TermMeaning
BatchProcessing bounded chunks (e.g. a day) on a schedule — high throughput, higher latency.
StreamingProcessing unbounded events continuously — low latency, harder correctness.
LambdaTwo paths: a fast approximate speed layer plus an accurate batch layer, merged at read.
KappaOne streaming path; reprocess history by replaying the log — no separate batch code.
IdempotentRe-running produces the same result — safe to retry and backfill.
BackfillReprocessing historical partitions, e.g. after a bug fix or a new column.
LineageThe recorded map of which inputs produced which outputs.

4. Architecture: Lambda & Kappa

The first architectural decision is how to reconcile the need for both low latency and full accuracy. Two canonical patterns answer it.

Petabyte pipeline: sources into an ingest log, splitting to a Flink speed layer and a Spark batch layer, both writing to a Parquet data lake served to consumers
Sources land in a durable ingest log (Kafka). A speed layer (Flink) produces low-latency results; a batch layer (Spark) produces accurate, complete results; both write to a Parquet data lake served to consumers. Kappa drops the batch layer and reprocesses by replaying the log.

The modern lean, especially with an engine that unifies batch and streaming, is toward kappa or a single-engine hybrid: one codebase, replay for history. The durable ingest log (Kafka or equivalent) is the linchpin either way — it decouples producers from consumers, buffers bursts, and, by being replayable, is what makes reprocessing possible at all.

5. Storage, Partitioning & Parquet

Since scanning the whole dataset is impossible, storage is designed entirely around reading as little as possible. Two ideas do the heavy lifting: partitioning and columnar format.

Partitioning lays data out in a directory hierarchy by a coarse key — almost always date, often plus region or another high-selectivity dimension. A query filtered to one day then reads only that day’s files (partition pruning), touching a tiny slice of the lake. The art is choosing a partition granularity that prunes well without creating the small-files problem — millions of tiny files that overwhelm the metadata layer and kill throughput. Daily partitions of compacted, appropriately-sized files are the usual sweet spot.

Parquet layout: file split into row groups, each holding column chunks, with a footer of schema and column statistics
A Parquet file splits into row groups (~128 MB); within each, data is stored column by column as chunks, each encoded and compressed independently. A footer holds the schema and per-column statistics (min/max), letting readers skip whole row groups via predicate pushdown.

Parquet is the columnar format that makes analytics affordable. Storing each column contiguously means a query reads only the columns it projects — five of a hundred is roughly twenty times less I/O than a row format. Because a column holds values of one type, encoding is highly effective: dictionary encoding for low-cardinality strings, run-length for repeats, delta for sorted numbers, then a general compressor (snappy for speed, zstd for ratio) on top. And the footer’s per-column min/max statistics enable predicate pushdown: a filter like where ts > X lets the reader skip any row group whose max is below X without decompressing it. Partition pruning, column projection, and row-group skipping compound to turn a petabyte lake into a few gigabytes actually read.

6. Compute: Spark vs Flink

The engine executes the transformations across a cluster. The two dominant choices reflect the batch/streaming split, and knowing when to pick which is the point.

DimensionSparkFlink
ModelBatch-first; streaming as micro-batches.Streaming-first; batch as a bounded stream.
LatencySeconds (micro-batch interval).Sub-second, true record-at-a-time.
Best forLarge batch ETL, ad-hoc, ML.Low-latency stateful streaming.
State & timeSimpler; less event-time nuance.Rich event-time, watermarks, large managed state.
Exactly-onceVia idempotent/atomic sinks.Checkpoint + barrier (Chandy-Lamport).

The mental model: Spark for big, accurate batch; Flink for low-latency, stateful streaming. Both distribute work by partitioning data across executors and running transformations in parallel; the expensive operations are the ones that force a shuffle — joins and group-bys that move data across the network to co-locate keys. Data skew, where one key holds a disproportionate share, creates a straggler task that dominates the whole job’s runtime and is the classic petabyte-scale performance killer. Both engines achieve exactly-once semantics: Flink via distributed checkpoints (barriers snapshot state consistently), Spark via idempotent, atomic writes to the sink so a retried task cannot double-write.

daily_etl:
  raw   = read_parquet("lake/events/date=2026-07-05")  # partition pruning
        .select("user", "event", "ts")                  # column projection
        .filter("ts > cutoff")                          # predicate pushdown
  out   = raw.groupBy("user").agg(...)                  # shuffle: watch for skew
  write_atomic(out, "lake/daily/date=2026-07-05")      # idempotent partition swap

7. Quality, SLAs & Reprocessing

This layer is what upgrades a pipeline into a system with a promise. Each mechanism protects a specific part of the SLA.

Reliability layers: DAG orchestration, schema checks, data quality, idempotent write, lineage, SLA/freshness, backfill
Orchestration sequences the DAG with dependencies and retries; schema and data-quality gates block bad data; idempotent atomic writes make reprocessing safe; lineage tracks inputs to outputs; freshness monitoring watches the SLA; and backfills reprocess history cleanly.
Interview tip. When the prompt says “strong SLAs,” anchor your answer on idempotent, atomic partition writes — it is the keystone. It makes retries safe, backfills safe, and exactly-once achievable, and it is what lets you reprocess a petabyte without fear. Pair it with data-quality gates that block bad data before publish and freshness monitoring that alerts on lateness, and you have covered correctness, reproducibility, and timeliness — the three things “SLA” actually means.

8. Bottlenecks & Tradeoffs

The tensions at petabyte scale are about latency versus correctness, and about the cost of every byte read and stored.

9. Summary

A petabyte pipeline is defined by its promise, not its throughput. The storage layer exists to avoid reading data, the compute layer to process what remains in parallel, and the reliability layer to make the output trustworthy and on time.

ConcernMechanism
Fresh or accurate?Lambda runs both a speed and batch layer; kappa uses one streaming path with log replay.
How do we avoid full scans?Partition pruning + columnar projection + row-group skipping via Parquet statistics.
Why columnar?Parquet reads only projected columns and compresses typed data 3–10× with encodings.
Which engine?Spark for big accurate batch; Flink for low-latency stateful streaming.
How do we get exactly-once?Flink checkpoints/barriers; Spark idempotent atomic sink writes.
How do we make reprocessing safe?Idempotent, atomic partition swaps — retries and backfills never duplicate.
How do we keep data trustworthy?Quality gates block bad partitions; schema registry governs evolution; lineage traces issues.
How do we keep the SLA honest?DAG orchestration with retries plus freshness/completeness monitoring and alerting.
The recurring theme: at a petabyte, reading data is the enemy and trust is the product. Lay storage out to read almost nothing, process the remainder in parallel while watching for shuffle and skew, and wrap it all in idempotent writes, quality gates, and freshness alerts — because the deliverable is a dataset that is correct, complete, and on time, every day.