Scenarios
Longer problems, like those in senior system design interviews. A strong answer estimates with numbers first, names the mechanism, then recommends a fix and says what it costs.
1You own a Kafka topic read by 15 teams, kept forever for audit. Choose the serialization format and the compatibility policy.Show the answer
Narrow the choice
Readers are in many teams, so binding to code at compile time is out. Teams upgrade at different times, so consumers must tolerate unknown fields: either Protobuf-style tags (about a byte per field, field numbers fixed forever) or Avro with a registry (no framing bytes, but the registry becomes a dependency). Records are individual messages, so reference the schema by a 4–5-byte fingerprint rather than embedding it.
Because retention is forever
Every schema ever registered must resolve forever: replicate the registry, back it up as durably as the topic, and keep a copy of every schema in the audit store.
Compatibility
Transitive: each new schema must be compatible with all earlier versions, because replays start at offset zero and a failed replay lands on another team months later.
Rules no checker enforces
Units in field names; a change of meaning means a new field; never reuse a field number; contract tests with expected value ranges.
Trade-offs
Protobuf costs about 10 bytes per 10-field record and a permanent tag namespace; Avro saves about 6 bytes but makes the registry a hard dependency. After ZSTD on batches, all formats, even JSON, are within 1.3–2× in size, so decide on contract and CPU, not size.
Read moreSerializationThe three escapesWriter–reader resolution
2A nightly job reads Parquet files from S3, compressed with ZSTD level 3. Finance wants the storage bill 40% smaller. Do you switch to level 19, and what will it do to query latency?Show the answer
Reads
Use the break-even D / B > r / (r − 1). From S3, B is about 50–100 MB/s per connection and ZSTD decodes at about 1 GB/s, so D / B is 10–20, far above the threshold of 2 or less: a higher ratio helps reads. Decoding speed barely changes with level, so reads will not slow down.
Writes
That is where the cost lands: level 19 compresses 50–100× more slowly. If the nightly writer needs 10 core-hours at level 3, level 19 may need hundreds, so check it fits the batch window; compress files in parallel, or use a middle level (9–12) if the writer is on a critical path.
Expectations
Gains flatten: going from a ratio of 4 to 5 saves 20% of the bytes. Measure on real data.
Recommend
High levels for cold object-store data written once; low levels for hot data or local copies. On machines with local NVMe caches, B is 3–7 GB/s and ZSTD can lose, so keep a fast codec there.
Read moreWhen does compression pay?ZSTD level and decode speedLevel controls the encoder's search
3On a Parquet lake, an analyst's query for the last 10 events of one parcel is slow, while total weight by depot is fast. Explain, and fix it without giving up the lake.Show the answer
Explain
The aggregation reads 2 of about 10 columns and gains from projection and encoding. A point lookup needs whole rows: it touches every column chunk, decompresses each page, and on object storage waits two round trips per file for the footer (about 2 × 30–70 ms). The file is organized by column chunk; the query wants a row.
Fix
Keep Parquet for scans, and add a path for lookups: a row-oriented or key-sorted store, such as an LSM store keyed by parcel ID with an order-preserving key encoding. Or sort and partition the lake by parcel so statistics let the query skip most chunks, and cache footers.
Cost
A second copy of the data, and a pipeline to keep it in sync. Do not try to tune Parquet into serving point lookups; the gap is structural.
Read moreColumnar file encodingsThe footerOrder-preserving keys
4Design the key encoding for a sorted key-value store whose keys are (tenant as a string, event_time as a signed 64-bit integer, score as a double in descending order).Show the answer
The rule
The engine compares keys as raw bytes with memcmp, so every part must encode to bytes that sort in the intended order.
Tenant
Variable-length, so escape 0x00 as 0x00 0xFF and end with 0x00 0x01; then ("ab", "c") and ("a", "bc") stay distinct and correctly ordered. If tenants need language-correct order, store a collation sort key next to the original (larger, and not reversible).
event_time
Big-endian, with the sign bit flipped (XOR the first byte with 0x80).
score, descending
Apply the float rule (sign bit set: invert all bits; otherwise flip only the sign bit), then invert all eight bytes for descending order. Choose and document where NaN and negative zero sort. If nulls are allowed, add a type tag or reserved byte for each part.
Trade-offs and test
Escaping expands keys, keys cannot be decoded in place, and sort keys take extra storage. Test with a property: random tuples sorted logically must equal the same tuples sorted by their encoded bytes.
Read moreOrder-preserving byte encodingLab: make memcmp sort your numbers
5After a producer deploy, dashboards show revenue 100 times too high. No alert fired, and the schema registry accepted the change. Diagnose, and prevent it.Show the answer
Diagnose
Most likely a change of meaning, such as dollars to cents, or a new unit or enum value. Schema compatibility checks only structure, so the change passed. Compare value distributions before and after the deploy, find the first bad offset, and look in the producer's diff for unit or rounding logic.
Repair
Once the field is fixed, quarantine or replay the affected range.
Prevent
Units in field names; "new meaning, new field"; contract tests on value ranges and null rates at the pipeline's edge; canary consumers; and failing fast on out-of-range values (quarantine the record and alert) instead of silently passing them through.
Be honest
This class of failure never disappears; validation only narrows it.
6A team plans to read Parquet into Arrow and do heavy analytics in Python. They say Arrow is zero-copy, so this will be free. Evaluate the claim.Show the answer
Memory
Parquet minimizes bytes at rest; Arrow minimizes CPU cycles in use. In memory, Arrow is commonly 2–10× larger (no bit-packing, fixed-width values, validity bitmaps, 64-byte alignment), so a 5 GB Parquet file may need 10–50 GB of RAM.
CPU
Reading Parquet into Arrow still means decoding: decompressing, expanding dictionaries, run-lengths and bit-packing, and building bitmaps. That is real CPU; measure it.
What zero-copy means
It removes serialization between Arrow-aware components, for example between processes or over Arrow Flight. Bytes still cross the network. The large win is replacing a row-oriented database driver, where encoding and decoding take 60–80% of the time.
Recommend
Read only the needed columns, stream one row group at a time to bound memory, and benchmark the whole conversion before promising anything.
Read moreArrow and Parquet
7A log store serves 100-byte point lookups from 256 KB blocks compressed with ZSTD, and lookups are slow. What do you change?Show the answer
Estimate
262,144 ÷ 100 ≈ 2,600× more bytes decompressed than needed, about 0.26 ms of decoding at 1 GB/s before the disk read even counts.
Change
Move to 4–16 KB blocks: 40–160× amplification and 4–16 µs of decoding. The ratio will fall (smaller window, more table overhead), so add a trained dictionary of about 110 KB to win back 2–5× on small records.
Costs
The dictionary is a permanent, versioned artifact: keep every old one and store its ID in each block header. Add a CRC32C per block, so a flipped bit costs 4–16 KB instead of 256 KB. Scans over this store get a little slower, and compression CPU per byte rises.
If scans matter too
Keep a second copy in large-block Parquet for them.
Read moreBlock size is three decisionsTrained dictionariesChecksums
8Design the encoding for a metrics store: a million series, one sample every 10 seconds, double-precision values that are mostly smooth. Queries are range scans over one series, with occasional random point reads.Show the answer
Timestamps
Delta-of-delta: zero for a regular series, so most timestamps cost about 1 bit (Gorilla reports 96% at one bit).
Values
First test whether they are decimals in disguise; if so, ALP turns them into integers and then frame of reference and bit-packing, at several GB/s. If they are truly smooth floats, use Gorilla's XOR encoding, but expect poor results on signals that oscillate around zero.
Random reads
Delta and XOR form serial chains, so restart every 64–128 samples to allow point reads at block granularity. The restart interval trades ratio for point-read cost.
Layout
Sort or cluster by series, then time, so each series is contiguous.
Limits
Gorilla's 12× figure is for regularly sampled metrics; do not assume it for irregular data. And the design assumes append-only blocks, so late samples need a separate buffer and compaction.
Read moreDelta and delta-of-deltaGorilla's XORALP: decimals in disguise
9The same data set is written by a streaming job and by a nightly batch job, with the same codec. The streaming files are 3 times larger. Why, and what do you do?Show the answer
Diagnose
Row order. The streaming job writes in arrival order: categorical columns have runs of length about 1, deltas are random, and per-file min/max ranges overlap, so skipping fails too. The batch job sorts, so run-length encoding gets k runs instead of N, deltas are small, and file ranges are disjoint.
Fix
Add a compaction step that re-sorts files by the column most queries filter on, low-cardinality columns first. It improves ratio and skipping, not just file count, so justify its budget with both.
Caveat
A file has only one sort order. If queries filter on several columns unpredictably, consider Z-order or Hilbert ordering, which gives each column partial locality at the cost of the leading column's strong locality.
Read moreRow order dominates the codecSort order is a scarce resource
10Storage per day doubled, but the volume of data did not. How do you find out why, and how do you stop it happening silently?Show the answer
Find it
Compression fails by degrading, not by erroring. Compare the compression ratio per column per day. Typical causes: a new customer with high-cardinality IDs where there used to be a few values (the dictionary passed 1 MB and fell back to plain encoding), a writer that stopped sorting, a changed batch size, or a column that started holding random floats. Check dictionary-fallback counters and run lengths per column.
Prevent
Alert on per-column ratio as a data-quality signal.
The organizational cause
Ingestion is measured on throughput and the query team on latency, so nobody owns the ratio. Give one team the budget for both the CPU and the bytes.
11Analysts scan Parquet files on local NVMe at 5 GB/s. The files already use dictionary, run-length and bit-packing encodings. A vendor proposes adding ZSTD on top. Is that wise?Show the answer
Estimate
After good lightweight encoding, a general-purpose pass adds only 1.2–1.5×. With r = 1.35, the break-even needs D / B > 1.35 / 0.35 ≈ 3.86, so decoding at 3.86 × 5 GB/s ≈ 19 GB/s. One ZSTD core decodes about 1–1.5 GB/s; you would need more than ten cores doing nothing else. Reads would get slower.
Contrast
On object storage at 100 MB/s per connection, the same codec is easy: the threshold is 386 MB/s and ZSTD gives 1 GB/s.
Recommend
No general-purpose pass on the NVMe copy (or LZ4 at 3–4 GB/s if some saving is needed), and ZSTD on the object-store copy. The trade-off is a larger NVMe copy.
Read moreWhen does compression pay?The conclusion from the break-even formula
12You ingest 2 TB a day. Should the ZSTD compression level be 3 or 19?Show the answer
Figures
Level 3 compresses at about 250 MB/s per core with a ratio of about 3.1; level 19 at 5–10 MB/s (say 7) with a ratio of about 3.6.
CPU
Level 3: 2 × 10¹² ÷ 250 × 10⁶ = 8,000 core-seconds, about 2.2 core-hours a day. Level 19: 2 × 10¹² ÷ 7 × 10⁶ ≈ 285,700 core-seconds, about 79 core-hours, or 3.3 cores running all day.
Bytes
645 GB against 556 GB stored per day, a saving of about 89 GB a day.
Decide
Ingestion cannot afford level 19 at line rate, and you do not yet know which data will be read often. Write at level 3, then rewrite at 9–19 during nightly compaction for data that is read many times; reading costs nothing extra. Past a ratio of about 4, more ratio helps storage cost much more than query time.
Also
The team paying for the CPU and the team gaining the bandwidth are different, so set the policy centrally.
Read moreLevel controls the encoder's search
13A compliance team asks for per-column encryption. What happens to compression, and what is the correct order of operations?Show the answer
Order
Compress first, then encrypt: ciphertext is random and does not compress.
What leaks
Per-column encryption keeps per-column compression possible, but the footer exposes each column's compressed size, which reveals something about its distribution, and ciphertext length leaks content length (the CRIME and BREACH class of attack). For data at rest this is usually acceptable; record it as a known residual risk.
Integrity
Keep a checksum per compressed block, or use authenticated encryption: one flipped bit in a compressed block destroys the whole block.
Read moreGeneral-purpose compressionChecksums