The Blueprint
Infrastructure advanced

Design a distributed file system (GFS / HDFS)

Petabyte-scale storage with a master and thousands of chunk servers — the original Google File System pattern that spawned everything modern.

asked at Google · Meta · Amazon Rarely asked ~40 min walkthrough ✎ Practice on canvas →
01

Scope the requirements

The system built to store the whole web crawl. Optimised for large sequential reads/writes, not random access — that's the whole design philosophy.

Functional — what it must do

  • Store files >GB in size across commodity hardware.
  • Read + write with sequential-access primary, random-access secondary.
  • Append is a first-class operation (many concurrent writers).
  • Automatic replication + recovery on node failure.
  • Explicitly cut: POSIX semantics, small-file optimisation, transactional writes — offer them back.

Non-functional — the numbers that shape the design

  • Throughput over latency: sustained aggregate ~100 GB/s reads across the cluster.
  • 10s of PB across thousands of chunk servers.
  • Hardware failure is normal — nodes die daily; system self-heals within minutes.
  • Chunk size 64 MB — large enough to amortise metadata, small enough to distribute.
  • 99.9% availability on read; writes may block during master failover.
Say this out loud

The design assumes commodity hardware fails constantly. Everything is replicated 3× by default; the master reconciles the world every few seconds.

02

Back-of-envelope estimates

Chunks (64 MB) are the unit of everything: storage, replication, metadata. Metadata size determines master RAM.

QuantityEstimateHow you got there
Total storage~50 PBspread across ~1,000 chunk servers × ~50 TB each.
Chunks total~800M50 PB ÷ 64 MB. Each has 3 replicas → 2.4B chunk replicas cluster-wide.
Metadata size~64 GB800M chunks × ~80 B (id, version, replica locations). Fits in master RAM.
Read throughput~100 GB/s aggregateclients read direct from chunk servers; master serves metadata only.
Master QPS~10k/smetadata lookups. Cached client-side to reduce load.
Chunk server health checksevery 10 smaster pings; missed 3 → chunk server marked dead, re-replication triggered.
Say this out loud

One master holds all metadata in RAM. Chunk servers own the data. Clients talk to master once, then straight to chunk servers.

03

Sketch the API

Client library provides open/read/write/append/close. Under the hood: talk to master for chunk location, then chunk server for bytes.

RPCmaster.getChunkLocations(fileId, offset, len)→ [ (chunkId, [replica_hosts]) ]. Cached client-side.
RPCchunkServer.read(chunkId, offset, len)→ bytes. Direct from chunk server, no master hop.
RPCchunkServer.write(chunkId, offset, bytes)Goes to the primary replica; primary chains to secondaries.
RPCchunkServer.append(chunkId, bytes)Atomic append. Primary orders concurrent appends.
RPCmaster.createFile(path, replicas=3)Allocates chunk ids + assigns replica hosts.
  • Reads bypass the master after the first metadata lookup — clients cache chunk locations and go direct to chunk servers.
  • Append is the primary write pattern — most log-like workloads append. Overwrite is supported but discouraged.
04

Data model

Master holds file → chunk mapping + chunk → replica mapping in RAM. Chunk servers hold the actual bytes as Linux files.

namespace (master)
path → { fileId, chunk_ids[] }
In-memory tree on master. Persisted via WAL + checkpoint for recovery.
chunk_map (master)
chunk_id → { version, primary_host, replicas[], size }
In-memory. Master rebuilds this from chunk server reports on startup.
chunks (chunk server)
chunk_id → file on disk
Just Linux files, one per chunk. Chunk server tracks version + checksum per chunk.
wal (master)
sequential log of metadata ops
Every metadata mutation appends to WAL; checkpoint periodically for fast recovery.
The database call

Master is a specialised in-memory database. Chunk servers use the local filesystem — no fancy DB, just files. This is a deliberate design — GFS predates modern key-value stores and the file abstraction was chosen because it maps directly to what the hardware wants.

05

High-level design

One master (with shadow backup), thousands of chunk servers, client library. Master is the metadata brain; chunk servers own the bytes; clients cache aggressively.

client master chunk servers master state Client A Client B Master in-memory metadata Shadow master hot standby Chunk srv 1 Chunk srv 2 Chunk srv 3 × 1000 WAL + checkpoint Namespace tree 1. locate chunk heartbeat mutations replicate WAL 2. read bytes
master serves metadata; clients read + write direct to chunk servers. shadow master takes over on failure.
  1. Client wants to read /path/file offset 100 MB → calls master.getChunkLocations → master returns { chunkId: 42, replicas: [cs17, cs42, cs103] } → client caches this.
  2. Client picks nearest replica (say cs17), calls cs17.read(chunkId=42, offset=..., len=...) → receives bytes directly. Zero master involvement.
  3. Write: client → master to allocate a chunk if needed → master picks 3 replicas and designates one as primary → returns primary + secondaries to client → client pipelines bytes to primary → primary orders + forwards to secondaries → confirms back.
  4. Every chunk server sends heartbeats every 10 s with a list of chunks it holds. Master reconciles: unexpected chunks get deleted; missing replicas trigger re-replication.
  5. Master failure: WAL + checkpoint replayed on shadow → shadow becomes new master → clients retry and get the new master's address from chubby-style coordination service.
06

Deep dives — where the interview is won

Phase 01

Single master + client caching: why the master isn't a bottleneck

It sounds fragile: one master for a 50 PB cluster. The trick is that master involvement per byte is nearly zero. Clients call master once per file (or once per chunk, for very large files) to get locations, then talk direct to chunk servers for all the actual bytes. Master QPS scales with client count, not data volume.

The 64 MB chunk size is what makes this work. If chunks were 1 MB, a 1 GB file needs 1000 master calls; with 64 MB chunks, 16 calls. And clients cache chunk locations for the file's lifetime unless the master invalidates. In practice, a busy cluster serves 100 GB/s of data with the master handling <10k RPC/s.

client master (rare) chunk servers (hot) Client Master ~10k QPS total Chunk servers ~100 GB/s 1× per file everything else
master call is rare and cached. bulk data never touches the master.
Trade-offMaster RAM is a hard ceiling — at ~80 B per chunk × 800M chunks = 64 GB. Grow the cluster past that requires either larger chunks (harder migration) or federating multiple masters (Colossus's answer).
Phase 02

Replication pipeline: primary orders, secondaries follow

On write, the client sends bytes to the primary replica of the target chunk. Primary orders the write within its chunk (assigns a mutation number), then chains the bytes to the secondary replicas — pipelined for throughput, not fanned out star-shape (which would double the client's uplink). Once all secondaries ack, primary confirms to client.

The chaining pattern spreads bandwidth. If client → primary is 1 Gbps, and each replica forwards to the next, total bandwidth cost is 1× at each replica, not 3× at the client. Networks are laid out to make this cheap: replicas in the same rack talk fast to each other, and racks are chosen to survive one rack failure without losing all replicas.

client primary secondaries Client Primary replica orders writes Sec 1 Sec 2 bytes forward chain
primary is the write serialiser. bytes flow through a pipeline to save client bandwidth.
Trade-offChain adds latency (each hop takes ~1 ms). Acceptable for large writes where the pipeline amortises; small writes see the latency directly. Overwrite-heavy workloads suffer more than append-heavy.
Phase 03

Failure handling: expect death, fix it in the background

Chunk servers die daily in a big cluster. Master notices via heartbeat timeout, marks the server dead, and schedules re-replication of every chunk it held (~50 TB of chunks). Re-replication is throttled to avoid overwhelming the network — a chunk server rebuild happens over hours, not seconds. Since chunks have 2 more replicas alive during rebuild, availability is preserved.

The master itself is the single point of failure. Two mitigations: (1) WAL is replicated to a shadow master in real time, so failover is fast (~30 s including client reconnect), (2) the shadow serves read-only metadata during the primary's slow operations. If both masters die, cluster is unavailable for writes; reads with cached metadata may continue for a while.

event master action recovery Chunk srv dies Detected 30 s Schedule re-replicate Copy chunks to new hosts throttled hours
hardware death is a scheduled task, not an emergency. throttled re-replication.
Trade-offUnder-replicated windows are inevitable during rebuild — 3× replication drops to 2× for hours. Cluster survives concurrent failures during this window; two more failures on the same chunk lose data. Erasure coding (Reed-Solomon) reduces this exposure at higher CPU cost.
07

Follow-ups you should expect

Why 64 MB chunks and not smaller?
Metadata size: smaller chunks × same total data = more chunks × more metadata × larger master RAM. Also fewer master round trips per read. Downside: small files waste a chunk each (though sparse allocation mitigates). GFS's target was large files, so 64 MB was the right point on the trade-off.
How does this compare to HDFS?
HDFS is an open-source implementation of the GFS paper with the same architecture (NameNode = master, DataNode = chunk server) and same trade-offs (single master, large blocks, sequential-optimised). Modern variants add federation (multiple NameNodes) to break the single-master ceiling.
What about S3?
S3 is a completely different architecture — object storage over commodity hardware with an eventually-consistent metadata layer, no per-file mutable state, and REST semantics instead of a file API. Better for many workloads (durability, geo-distribution), worse for large sequential MapReduce-style reads that GFS was tuned for.
How do you support small files without wasting chunks?
You don't — GFS was explicitly designed against small files. Products layer a small-file combiner (many small files → one large chunked file with an index) on top. HBase over HDFS does this; Facebook's Haystack for photos does similar.
Erasure coding vs 3× replication?
3× replication = 200% storage overhead for durability. RS(10,4) erasure coding = 40% overhead for equivalent durability, but reads must reconstruct from multiple chunks (slower) and small-write amplification is high. Modern systems (Colossus, HDFS-EC) use erasure coding for cold data + replication for hot.

Where candidates lose the room

  • Trying to put a database on the master — WAL + in-memory tree is faster than any general-purpose DB for this shape.
  • Making the master involved in byte transfer — you re-invented NFS and lost the throughput story.
  • Small chunks — metadata explodes and master RAM becomes the bottleneck.
  • Synchronous re-replication on failure — you overwhelm the network trying to rebuild in seconds; throttle to hours.
  • Expecting POSIX semantics — the API is deliberately less than POSIX to permit concurrent appends without locks.

Concepts this leans on

Found a bug or want more?

Tell me what's broken, missing, or wrong. Every message lands in my inbox.