Skip to content
About
Main content

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.

Try each one before you open the answer.
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 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 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 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

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 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 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 ; 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 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 and encoding. A needs whole rows: it touches every column chunk, decompresses each page, and on waits two round trips per file for the (about 2 × 30–70 ms). The file is organized by column chunk; the query wants a row.

Fix

Keep 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

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

Read moreWriter–reader resolutionWhat serialization forbids

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; minimizes CPU cycles in use. In memory, Arrow is commonly 2–10× larger (no , fixed-width values, , 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 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 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× and 4–16 µs of decoding. The ratio will fall (smaller window, more table overhead), so add a 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 per block, so a flipped bit costs 4–16 KB instead of 256 KB. Scans over this store get a little slower, and 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

: 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, 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 . 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 blocks, so late samples need a separate buffer and .

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 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 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 , so nobody owns the ratio. Give one team the budget for both the CPU and the bytes.

Read moreAn organizational constraintDictionary encoding

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