Design a Large File Distribution System
Get a 200 GB checkpoint from one origin onto 10,000 machines in five data centres within minutes, under capped links, verifying every byte.
Last updated 2026-10-26. Difficulty: hard. Patterns: peer-to-peer, chunking, bandwidth, integrity, stragglers. Reported at Anthropic, OpenAI, Meta, Google, NVIDIA, Databricks.
The interviewer asks, the candidate answers and draws, and you press Next. Skip to any stage or key moment, and ask the AI Mentor anything along the way.
Or sit it in the style of Anthropic · OpenAI · Meta · Google, run as that company’s round is reported to go.
The short answer
How to design Large File Distribution System in a system design interview, in five steps:
- Scope it. Publish an artifact once, distribute it to a target set, verify on every machine, stay inside bandwidth caps, survive failures mid-transfer, report progress, cancel, retain and roll back.
- Set the targets. Completion time: 99 % of hosts < 3 min; Origin egress: ≤ 10 GB/s, ~1 copy; Cross-data-centre bandwidth: ≤ 40 Gbps per DC link; Integrity: every piece verified.
- Do the numbers. Everyone pulls from the origin: ~56 hours; The floor with peer-to-peer: ~80 s; Pieces, manifest and bitfield: 3,125 pieces · 100 KB · 391 B.
- Draw the design. Signing key, Manifest builder, Origin store, Origin seeders, DC seeders, Distribution API, Distribution controller, DC tracker, Peer agent.
- Go deep where it is hard.
- Central fan-out, trees or a swarm → Swarm: pieces, rarest-first, everyone uploads as soon as a piece verifies.
- Pieces, hashes and a signed manifest → Fixed 64 MB pieces, a signed manifest of SHA-256 piece hashes with a Merkle root.
- Tracker, scheduler or DHT → A tracker per DC for peer lists and progress; peers choose pieces themselves.
- Which piece next: rarest-first and endgame → Rarest-first among neighbours, finishing started pieces first.
- Racks, data centres and link budgets → Rack-first, same-DC peer lists; DC seeders own disjoint WAN slices; token buckets on capped links.
- Stragglers, failures and when it is done → Per-block deadlines and re-requests, endgame, and a per-pool quorum for done, with stragglers sent to repair.
Functional requirements
- Publish an artifact once. A 200 GB model checkpoint or a 5 GB container image is split into pieces, hashed and signed once, and uploaded to a single origin.
- Distribute it to a target set. Up to 10,000 machines in five data centres, chosen by pool or label, with a priority and a deadline. Several distributions can run at once.
- Verify on every machine. Every piece is checked against a signed manifest before it is written to disk or passed on; the whole artifact is checked before it is marked ready.
- Stay inside bandwidth caps. The origin allows 10 GB/s, each data centre’s WAN link 40 Gbps for this job, and each host 20 of its 25 Gbps, because hosts keep serving while they download.
- Survive failures mid-transfer. Machines reboot, peers die, a seeder or the tracker fails. Transfers resume from what is already verified, never from zero.
- Report progress. Per machine, per data centre and overall: hosts verified, rates, an ETA, and the stragglers with a likely cause.
- Cancel, retain and roll back. Stop a bad release everywhere within seconds. Keep the previous artifact on every host so a rollback moves no bytes.
- Out of scope. Building the artifact, loading weights into GPUs and shifting traffic (the deploy system does that), delta updates between versions (content-defined chunking, as in Design Dropbox), and the training fabric.
Non-functional requirements
- Completion time (99 % of hosts < 3 min). The floor is file size ÷ per-host bandwidth: 200 GB ÷ 2.5 GB/s = 80 s. A good design puts the median near 100 s and p99 within about 2× of the floor.
- Origin egress (≤ 10 GB/s, ~1 copy). The origin is an object store shared with training jobs. Reading it once per artifact (200 GB) rather than once per machine (2 PB) is the difference between 20 s and 56 hours.
- Cross-data-centre bandwidth (≤ 40 Gbps per DC link). This is the requirement that drives the hardest trade-off: speed against link caps. It forces topology-aware peer selection and a few seeders per data centre that pull disjoint slices, so each piece crosses each WAN link about once.
- Integrity (every piece verified). No machine loads or forwards a byte that does not match the signed manifest. A flipped bit from a disk, a NIC or a bad peer costs one piece, never a corrupt model in production.
- Fault tolerance (1 % of hosts fail, no restart). Hosts, seeders and the tracker can all fail during a rollout. A failure costs at most the blocks in flight from one sender.
- Do no harm (≤ 20 of 25 Gbps per host). Hosts serve production while they download, and rack uplinks are oversubscribed 2.5:1. Per-host caps and rack-first peering keep distribution from crowding out real traffic.
- Confidentiality (mTLS, target hosts only). Model weights are among the most valuable things the company owns. Only authenticated machines in the target set may join a swarm or fetch a piece.
- Progress visibility (fresh within 5 s). The deploy system and operators need per-DC progress, an ETA and a named list of stragglers with causes, not a single percentage.
Back-of-envelope estimates
- Everyone pulls from the origin: ~56 hours. 10,000 × 200 GB = 2 PB. At the origin’s 10 GB/s cap that is 200,000 s ≈ 56 h. An origin with unlimited egress does not save it: a remote DC’s 2,000 hosts need 400 TB through a 5 GB/s WAN link, ~22 h. Central fan-out is not slow, it is impossible.
- The floor with peer-to-peer: ~80 s. Each host can receive at most 2.5 GB/s (20 Gbps), so 200 GB takes at least 80 s. The swarm’s total upload, 10,000 × 2.5 GB/s = 25 TB/s, moves 2 PB in the same 80 s, and the origin needs only 20 s to inject one copy. The bound is max(F/u_origin, F/d_host, N·F/Σu) = max(20, 80, 80) = 80 s, independent of N.
- Pieces, manifest and bitfield: 3,125 pieces · 100 KB · 391 B. 200 GB ÷ 64 MB = 3,125 pieces. A SHA-256 per piece is 32 B, so the hash list is 100 KB. One bit per piece makes a host’s bitfield 391 B, and the whole fleet’s availability map 10,000 × 391 B ≈ 4 MB.
- One copy across a WAN link: ~40 s. 200 GB ÷ 5 GB/s (40 Gbps) = 40 s per remote DC, shorter than each host’s own 80 s floor, so the WAN is not the bottleneck as long as each piece crosses it once. Topology-blind peering would push 400 TB per DC through the same link.
- Rack uplink with random peers: ~160 s per rack. 40 hosts × 200 GB = 8 TB per rack. With peers chosen at random across a 2,000-host DC, 39 of 1,999 are rack-mates, so ~98 % of bytes cross the 400 Gbps (50 GB/s) uplink: 7.8 TB ÷ 50 GB/s ≈ 160 s, twice the floor. Rack-first peering is a speed requirement, not a nicety.
- Store-and-forward doubling: ~19 min. If a host forwards only after it has the whole file, holders double each round: log₂ 10,000 ≈ 14 rounds × 80 s ≈ 1,120 s. A pipelined binary tree does better, ~160 s, because each interior host splits 2.5 GB/s between two children, but half the hosts are leaves whose upload is never used.
- Seeders’ share of demand in a DC: ~2 %. 8 DC seeders × 12.5 GB/s (100 Gbps) = 100 GB/s, against 2,000 hosts × 2.5 GB/s = 5 TB/s of demand. Seeders inject; peers must supply the other 98 %. Queued on seeders alone, the DC would take 400 TB ÷ 100 GB/s = 4,000 s.
- Tracker load: ~5 k heartbeats/s. 10,000 agents every 2 s = 5,000/s fleet-wide, 1,000/s per DC tracker, each a few hundred bytes with the bitfield. About 2.5 MB/s in total. The control plane is tiny; the whole problem is bandwidth.
Components
- Signing key (KMS · key in an HSM): Signs the manifest’s Merkle root together with its name, version, size and expiry. The private key never leaves the HSM, and every agent ships with the public key pinned. A compromised tracker or peer can then slow or misdirect a transfer, but cannot get a machine to load bytes the release pipeline did not produce.
- Manifest builder (64 MB pieces · SHA-256): Runs once per artifact, next to the bytes: splits the file into fixed 64 MB pieces (3,125 for 200 GB), hashes each, builds a Merkle tree over the piece hashes and has the root signed. Fixed size rather than content-defined chunks, so a piece’s index alone gives its offset. Hashing 200 GB is about 100 core-seconds.
- Origin store (object store · 10 GB/s cap): The single source of truth for artifacts and manifests, addressed by Merkle root so a path never changes meaning. It is shared with training jobs, so distribution may use 10 GB/s of it. In this design it is read about once per artifact, by the origin seeders, and never by the 10,000 agents.
- Origin seeders (8 hosts · 100 Gbps): A small tier in the origin’s data centre that reads each piece from the origin once (each host a disjoint eighth, 1.25 GB/s each), shares pieces among themselves, and serves every other data centre’s seeders across the WAN. They also act as the DC seeders for their own data centre, and keep the last few artifacts so rollbacks and late joiners never touch the origin.
- DC seeders (8 per DC · WAN slices): The only hosts in a data centre allowed to pull across the WAN. Each owns the pieces whose index mod 8 is its slot and pulls them under a 625 MB/s token bucket, so the eight together hold the link at its 40 Gbps budget. They inject pieces into the DC; the peers multiply them, since 100 GB/s of seeder upload is 2 % of the DC’s 5 TB/s demand.
- Release pipeline (training or build job): The job that produced the checkpoint or image. It runs the manifest builder, starts a distribution for the signed root and a target set, watches progress, and hands off to the deploy system once each pool’s quorum has verified.
- Distribution API (start · status · cancel): Authenticated entry point for release pipelines and operators: create a distribution, read its progress, cancel it. It fetches the manifest and checks its signature before anything moves, so a bad artifact fails here and not on 10,000 hosts. Starts are idempotent on a client key so a retry never launches a second swarm.
- Distribution controller (global · leader-elected): Plans each distribution: hosts per DC, the WAN relay plan, each link’s budget and the seeders’ slices, and pushes it to the trackers. Merges per-DC roll-ups into the global view and decides when a pool is done, on a quorum of verified hosts rather than 100 %. Its durable state is small and lives in the state store, so failover to a new leader is quick.
- DC tracker (soft state · 1 per DC): Admits agents in its data centre by certificate identity and target set, hands out topology-aware peer lists (about 20 rack-mates, 10 from other racks, plus the DC seeders), and takes a heartbeat from each agent every 2 s, 1,000 a second per DC. It is never on the data path, and everything it holds is rebuilt from the next round of heartbeats, so a standby needs no replication.
- Peer agent (on every host · mTLS): A daemon on each of the 10,000 hosts. Checks the manifest’s signature, then downloads pieces rarest-first from about 30 neighbours, verifies each piece’s hash before writing or serving it, and uploads a piece to others the moment it verifies. Persists its bitfield so a restart resumes, and caps itself at 20 Gbps each way.
- Distribution state (Postgres / etcd): Distributions, manifests, target sets, plans, budgets and the final outcome per host (verified, evicted, failed). Kilobytes per distribution plus one row per host. It deliberately does not hold live piece availability: that is soft state in the trackers.
- Progress view (time series · per DC): Counters and histograms every few seconds: hosts verified, rate and ETA distributions per DC, bytes per link, the rarest pieces, and stragglers with causes. Histograms rather than averages so percentiles merge correctly across data centres. A view for people and the deploy system; the per-host outcome of record is in the state store.
- Local NVMe (preallocated · atomic rename): Each verified piece is written at offset index × 64 MB into a preallocated file, and the bitfield is updated only after the data is durable. When the last piece lands the agent checks the root, fsyncs and renames the file into place, so a loader sees either the old artifact or the whole new one. The previous artifact stays for rollback.
- Neighbour peers (~30 · rack first): The other agents this one is connected to: about 20 in its own rack and 10 elsewhere in the same DC, never another DC. They exchange bitfields once and a HAVE message per verified piece, which is how each agent sees rarity. No tit-for-tat choking: every peer is ours, so upload slots exist for fairness and caps, not incentives.
User flows
- Publish a checkpoint and seed it. The write path. The artifact is chunked and signed once, read from the origin once, and lands in every data centre’s seeders within the WAN budget, pipelined so agents can start in the first second.
- The release pipeline hands the finished 200 GB checkpoint to the manifest builder. The builder runs next to the bytes, in the training cluster, because it must read all 200 GB once to hash them. Hashing is about 100 core-seconds, so spread over a few dozen cores it is bounded by how fast storage can be read. This is the only time anything reads the whole file before distribution.
- The builder cuts 3,125 pieces of 64 MB, hashes each, and builds a Merkle tree over the hashes. Fixed size, so piece i is bytes [i × 64 MB, (i + 1) × 64 MB) and its index alone locates it. The manifest also carries a file table, which shard file covers which byte range, so the loader can map pieces back to the checkpoint’s files. The hash list is 100 KB; the Merkle root is the artifact’s 32-byte identity.
- The root is signed, and the pieces and signed manifest are uploaded to the origin under that root. Signing the root, not the file, means one small signature covers every piece transitively. Uploads are keyed by root, so the origin is content-addressed and immutable: uploading the same bytes twice is a no-op, and a path can never point at different bytes later.
- The pipeline starts a distribution for that root and a target set. The API fetches the manifest and verifies its signature before anything moves, so a bad artifact fails once here instead of on 10,000 hosts. The controller resolves the target set into hosts per DC and persists the distribution before acknowledging.
- The controller computes the plan: link budgets, which DC pulls from which, and each seeder’s slice. Budgets come from network configuration, not guesses: 10 GB/s from the origin, 40 Gbps per WAN link, 20 Gbps per host. The plan goes to each DC’s tracker, which tells its seeders their slice. Budgets can be changed mid-rollout, for example halved when production traffic peaks.
- Origin seeders read each piece from the origin once, each a disjoint eighth, and share among themselves. At 10 GB/s the origin tier holds a full copy in 20 s, but nothing waits for that: each piece is served onward the moment it verifies. Origin egress for the whole rollout is one copy, about 200 GB, instead of 2 PB.
- Each remote DC’s seeders pull their slices across the WAN from the origin seeders, under the link budget. Disjoint slices are what make "each piece crosses the WAN once" true: without them, eight seeders would race for the same first pieces. One copy crosses in 40 s. Seeders then fetch the other seven slices from each other inside the DC, so each holds a full copy for late joiners.
- One machine downloads and verifies. The read path, run 10,000 times at once. The agent pulls by rarity from rack-mates first, verifies every piece before using or sharing it, and is uploading within a second of starting.
- The agent announces itself to its DC’s tracker and gets a peer list. The tracker picks neighbours by topology: mostly rack-mates, who share a top-of-rack switch with no oversubscription between them, plus a few from other racks so new pieces can enter the rack. Random within each group, so the swarm is a well-connected random graph rather than islands.
- It fetches the signed manifest from a DC seeder and checks it against its pinned key. The manifest can come from anyone, because it verifies itself: a forged one fails the signature, a tampered hash list fails the root. 10,000 fetches of 100 KB is 1 GB, nothing for the seeders, and the origin is not involved.
- It connects to its neighbours over mTLS and exchanges bitfields. From then on each agent knows which neighbour has which piece, and how many neighbours have each piece: its local view of rarity. There is no central lookup per piece, which is what keeps the tracker at 1,000 requests a second per DC.
- It requests the rarest pieces it lacks, from rack-mates first, with several blocks in flight per connection. Rarest-first among neighbours, ties broken at random, and a piece once started is finished first. Four 4 MB requests in flight per connection keep the pipe full across a sub-millisecond round trip. Seeders are asked only for pieces no neighbour has, so they spend their upload on new pieces.
- When a piece’s last block arrives, the agent hashes it and writes it to disk only if it matches. Verifying per piece means corruption costs at most 64 MB of re-download and points at the sender. SHA-256 with CPU extensions runs at about 2 GB/s a core, so keeping up with 2.5 GB/s takes about 1.25 cores.
- It sends HAVE to its neighbours and starts serving that piece immediately. This is the pipelining: a piece is useful to others a fraction of a second after it lands here, not after the whole 200 GB. It is why each host uploads about as much as it downloads and the swarm gets close to the 80 s floor.
- Every 2 s it reports progress to the tracker, which may swap some neighbours. The heartbeat doubles as the peer-list refresh: the tracker replaces neighbours that died or have nothing this agent needs. Three missed heartbeats, 6 s, take an agent out of everyone’s lists.
- With every piece verified, it checks the root, fsyncs, renames the file into place and reports complete. The rename is atomic, so a model loader sees the old artifact or the whole new one, never a mix. The agent keeps seeding until the distribution closes: a finished host is the best source there is.
- 10,000 machines start in the same second. The scale case: an empty swarm, five data centres, one capped origin and capped WAN links. It shows the swarm reaching close to the 80 s floor instead of collapsing onto the seeders, and no link going over its budget.
- The controller opens the distribution and all 10,000 agents announce within a few seconds. Two thousand announces per DC tracker in a second or two is trivial. The herd problem here is not the tracker but everyone wanting the same first bytes. Responses carry jitter on the heartbeat interval so the load spreads evenly from then on.
- With no peer holding anything yet, agents ask the DC seeders, which give each agent different pieces. If agents simply queued on the seeders, 2,000 × 200 GB ÷ 100 GB/s would take 4,000 s. The seeders’ job is to inject each piece into the DC a few times and then step back, so they serve the pieces they have sent least and refuse the rest.
- Within seconds each agent holds a few distinct pieces and trades with its neighbours. The first two to four pieces are chosen at random: having something to trade quickly matters more than rarity at the start. Upload capacity grows with the swarm, since every holder is a new source, and after about 10 s agents get ~98 % of their bytes from each other, most from rack-mates.
- Remote seeders are still filling over the WAN, so new pieces enter each remote DC at 5 GB/s. A remote DC cannot finish before its 40 s WAN copy, and it does not need to: each host’s own floor is 80 s. Because pieces are pipelined, agents start on whatever has already crossed.
- Rack-first selection keeps rack uplinks well below capacity. Topology-blind peering would push ~98 % of bytes through each rack’s 400 Gbps uplink and cap the rack at ~160 s. Rack-first means a piece enters a rack two or three times and then spreads at switch speed inside it.
- Copies stay even: when the swarm is 90 % done, no piece is held by only a few hosts. Rarest-first equalises copy counts, so the last pieces anyone needs are about as common as the first. With random choice, the coupon-collector tail would leave a few pieces with a handful of holders and thousands of agents queueing on them in the last 10 %.
- The median host verifies at about 100 s and 99 % by about 3 minutes. Origin egress is about one copy, each WAN link ran at its cap and never above it, and the median landed within 25 % of the floor. The last 1 % is the subject of the straggler flow.
- Peers die and one sends bad bytes. The failure path. A peer stalls, a disk returns garbage, a host reboots, the tracker and a seeder crash. Nothing restarts from zero: each failure costs at most the blocks in flight from one sender.
- A neighbour’s link flaps and a block request to it stalls past its deadline. Deadlines adapt per peer: a 4 MB block at the 300 MB/s this peer was delivering should take ~13 ms, so a 1 s floor is generous and still catches a dead link fast. TCP alone can sit on a half-open connection for minutes.
- The agent cancels, re-requests the block from another holder, and deprioritises the slow peer. Blocks of one piece can come from different peers, so a failure costs at most the 4 MB in flight. A peer that times out twice is dropped from this agent’s set and replaced on the next heartbeat.
- A peer with a failing disk sends a piece that fails its hash check. This is why the check happens before writing and before serving: the bad bytes never reach this disk or any other host. When the piece’s blocks came from several peers, the agent re-fetches each block from one sender at a time to find the culprit.
- The agent reports the sender, and the tracker quarantines it after two independent reports. Two reporters, not one, so a single buggy agent cannot evict healthy peers. A quarantined host stops serving but keeps downloading; it re-verifies its whole store and gets a hardware ticket.
- A host reboots halfway through and resumes from its persisted bitfield. The bitfield is updated only after a piece’s data is durable, so it never claims a piece the disk lacks. After a crash the agent re-hashes its pieces in the background before serving them, which takes under a minute for 100 GB at 2 GB/s per core.
- The DC tracker crashes; agents keep trading with the neighbours they already have. The tracker is not on the data path, so losing it stops introductions, not transfers. The standby rebuilds all its state from the next round of heartbeats, 2,000 agents in 2 s, with nothing to replicate or replay.
- A DC seeder dies mid-slice, and its remaining pieces are reassigned to the other seven. Pieces from that slice that already crossed are held by agents, so only the missing ones are fetched again. The WAN budget is split seven ways so the link stays at 40 Gbps rather than dropping to 35.
- Declaring done with a slow 1 %. The background path that decides the rollout. Heartbeats roll up into a live view, the last pieces are fetched in endgame mode, and each pool completes on a quorum instead of waiting for the slowest host in 10,000.
- Trackers roll heartbeats up per DC and send them to the controller every 5 s. Aggregating at the edge turns 5,000 heartbeats a second into one roll-up per DC every 5 s, about one message a second at the controller. Rates and ETAs travel as histograms so percentiles merge correctly across DCs.
- The controller writes the merged view, and the pipeline reads it through the API. The ETA comes from each host’s remaining bytes over its own recent rate, not from the average, because the average is dominated by hosts already finished.
- An agent whose last blocks are all requested enters endgame and asks every holder for them. Without endgame, the last pieces arrive at the pace of whichever sender happened to be slowest. Endgame starts when every missing block is already in flight, about the last 480 MB (30 connections × 4 blocks × 4 MB), and costs well under 1 GB of duplicates per host, all inside the DC.
- The controller flags hosts far below the DC median and asks them why. At 10,000 hosts some are always on a failing disk, a bad cable or a host pinned by its own job. A straggler is a host below a quarter of the DC’s median rate or with an ETA past the p99. The causes have different fixes: a disk-bound host needs nothing from the network; a host short of peers needs a new list.
- Each pool is done when 99 % of its hosts have verified; the rest go to repair rather than being waited on. Waiting for the slowest host in 10,000 can turn a 3-minute rollout into a 30-minute one. The quorum is per pool and set by the caller, because only the consumer knows whether 1 % missing is a small capacity dip or a training job that cannot start.
- The pipeline is notified and starts the model swap; stragglers keep downloading or are drained. Distribution and deploy are separate jobs: this system delivers verified bytes, the deploy system decides when to load them and shift traffic. Evicted hosts are kept out of serving until they verify.
- Later, seeders and agents garbage-collect old artifacts, keeping the current and previous one. Keeping the previous version on every host makes a rollback a local rename that moves zero bytes. Two 200 GB artifacts are a fixed, budgeted slice of each host’s NVMe; seeders keep more, so a machine added next week fetches from them and not from the origin.
Deep dives
Central fan-out, trees or a swarm
Why not have every machine download from the origin, or build a broadcast tree?
Central fan-out is the obvious answer, and the numbers rule it out. 10,000 machines × 200 GB is 2 PB; at the origin’s 10 GB/s cap that is 200,000 s, about 56 hours. A CDN or an object store with unlimited egress removes the origin cap but not the WAN: 2,000 hosts in a remote data centre pulling 200 GB each is 400 TB through a 40 Gbps link, about 22 hours. The total, 2 PB, is fixed; the only question is whose upload capacity carries it.
The insight is that every machine has upload capacity too. 10,000 hosts × 2.5 GB/s is 25 TB/s, enough to move 2 PB in 80 s. The textbook lower bound for sending a file F to N peers is max(F/u_origin, F/d_host, N·F/Σu): here max(20 s, 80 s, 80 s) = 80 s, and it does not grow with N. A design approaches it only if every machine starts forwarding long before it has the whole file, and if no machine’s upload sits idle.
- Every machine pulls from the origin or a CDN rejected
- Store-and-forward tree (forward only after the whole file arrives) rejected
- Pipelined fixed broadcast tree (forward each piece as it arrives) situational: inside one tightly managed job on a fast fabric, where any failure stops the job anyway, such as broadcasting weights to a training job’s ranks with a collective
- Swarm: pieces, rarest-first, everyone uploads as soon as a piece verifies chosen
The answer: A swarm with 64 MB pieces: each agent pulls the rarest pieces it lacks from about 30 neighbours and serves each piece to others the moment its hash verifies. Pipelining is the whole trick: a piece reaches its 10,000th host a few hops and a second or two after its first, so the rollout costs about one file time, 80 s at 2.5 GB/s, plus startup and a tail. A seeder tier reads the origin once and each WAN link carries about one copy, so the capped resources stay well under their caps. Trees are not wrong: interleaved pipelined trees reach the same throughput, but every slow interior host becomes the problem of everyone below it, and repairing a tree mid-transfer is the hard part. Facebook has described using BitTorrent to push its large web server binary to its fleet during deploys, and Uber’s Kraken and Alibaba’s Dragonfly are open-source peer-to-peer distributors for container images built on the same idea.
Derive the 80 seconds. What makes the real number worse?
Each host must receive 200 GB and can take at most 2.5 GB/s, so no design beats 80 s for that host, and the swarm’s 25 TB/s of total upload is exactly enough to feed all 10,000 at that rate. Startup adds a few seconds while the first pieces spread, pipelining adds a few hops of 0.1 to 0.2 s, and the tail adds whatever the slowest few percent cost. I would expect the median near 100 s and p99 at 2 to 3 minutes. If the median were far above that, I would look at rack uplinks and seeders first, because that is where a topology mistake shows up.
It is a 5 GB container image instead. Does anything change?
The shape stays and the numbers shrink: 5 GB at 2.5 GB/s is a 2 s floor, so fixed costs dominate, such as announces, TLS handshakes and the first pieces spreading. I would use 16 MB pieces so there are still ~300 to trade. Below about 1 GB, or for a few hundred machines, a per-DC pull-through registry cache is simpler and fast enough; the swarm earns its complexity when size × machine count is large.
Half the hosts have 100 Gbps NICs and half 25 Gbps. Does the swarm cope?
Better than a tree would. Fast hosts download faster and, more importantly, upload more, so they become the main sources without any configuration. Each host finishes near its own floor, 20 s or 80 s, as long as total upload covers the demand, which it does here. The one change is a per-host upload cap proportional to its NIC, so a slow host is never asked to serve more than it can.
Why not IP multicast? The network can copy packets for free.
Most data-centre networks do not enable multicast across racks, and it has no reliability of its own, so you add a repair protocol for lost packets. One slow receiver then forces the group to slow down or needs a separate repair path, and those repair paths end up looking like a peer-to-peer swarm anyway. Where a fabric supports it well, multicast for the bulk plus unicast repair is a fair optimisation, not the base design.
How would you know the swarm is actually close to optimal?
Put the floor on the dashboard: per-DC p50 and p99 against size ÷ host bandwidth, origin egress against one copy, and each WAN link’s bytes against one copy per DC. I would also track how much of the fleet’s upload budget is in use during the transfer. If it sits well below 80 %, peers are idle for lack of pieces to trade, which points at piece selection or peer lists.
Pieces, hashes and a signed manifest
How big is a piece, and how does a machine trust a piece it got from another machine?
Peers cannot be trusted for integrity, even inside one company: disks flip bits, NICs corrupt frames in ways TCP checksums miss, a buggy agent can serve the wrong offset, and a compromised host could serve modified weights. With only a whole-file hash checked at the end, one bad byte is found after 200 GB, with no idea which of 30 senders caused it, and it has already been passed on to others.
So the unit of verification must be the unit of exchange, and the list of expected hashes must come from a source the machine can check independently. Piece size trades overhead against granularity: 1 MB pieces mean 200,000 hashes, a 6.4 MB manifest and 25 KB bitfields in every heartbeat; 1 GB pieces leave only 200 units to spread across 10,000 machines and a 0.4 s pipeline hop. Somewhere in 16 to 64 MB is the usual middle.
- One hash for the whole file rejected
- Fixed 64 MB pieces, a signed manifest of SHA-256 piece hashes with a Merkle root chosen
- Merkle tree down to small blocks, with a proof per block situational: the hash list is too big to ship up front (millions of pieces), or blocks must be checked individually as they arrive; BitTorrent v2 uses per-file SHA-256 Merkle trees over 16 KiB blocks
- Content-defined chunks situational: consecutive versions share most of their bytes, such as container image layers, and only changed chunks should ship; that is the sync problem in Design Dropbox
The answer: Fixed 64 MB pieces, 3,125 for 200 GB. The manifest lists name, version, size, piece size, the SHA-256 of every piece, a file table mapping pieces to the checkpoint’s shard files, and an expiry; the Merkle root over the piece hashes is the artifact’s identity, and the KMS signs the root with the metadata. An agent checks the signature with its pinned public key, recomputes the root from the list, and then accepts any piece from anyone whose hash matches. Pieces are requested in 4 MB blocks, possibly from several peers; a piece that fails is dropped and its blocks re-fetched one sender at a time to find the culprit. Peers talk over mTLS with workload certificates, which controls who is in the swarm and keeps the weights encrypted on the wire, but the hashes, not TLS, are what guarantee the bytes. Hashing costs about 1.25 cores at 2.5 GB/s with SHA-256 CPU extensions; BLAKE3 is several times faster if that matters.
Why both a signature and mTLS? Isn’t one enough?
They protect different things. The signature proves the bytes are what the release pipeline produced, end to end, however many peers relayed them; mTLS cannot, because an authenticated but buggy or compromised peer can still send wrong bytes. mTLS proves who is on the other end and encrypts the transfer, which matters because the weights are valuable. Drop the signature and one compromised peer can poison the fleet; drop mTLS and anyone on the network can join the swarm and download the weights.
Someone replays an old, correctly signed manifest as if it were the new release. What stops it?
Three checks. The manifest carries an expiry, so a stale one fails after its window. The distribution names the exact root to fetch, and the agent gets that root from the authenticated tracker, not from a peer. And the agent refuses a lower version than the one it holds unless the distribution is marked as a rollback. That is the rollback-attack protection The Update Framework formalises for software updates.
Why 64 MB and not 4 MB like a sync client?
Because the costs that matter here are per piece: hashes in the manifest, bits in every bitfield and heartbeat, scheduling decisions, and HAVE messages to 30 neighbours. At 4 MB, 200 GB is 50,000 pieces and each agent sends 1.5 M HAVE messages per rollout. At 64 MB a pipeline hop is still only 0.1 to 0.2 s, and 3,125 pieces give 30 neighbours plenty to trade. For a 5 GB image I would drop to 16 MB so there are still about 300.
Every piece verified, yet the final root check fails on one host out of 10,000. What happened?
Each piece was right when it was hashed, so something changed it afterwards, or it was written at the wrong offset. Both point at that host’s disk or a bug in its agent. The agent re-hashes every piece on disk, re-fetches the failing ones and reports; if it fails twice, the host is quarantined and gets a hardware ticket. One bad machine costs one machine, which is the point of per-piece hashes.
How do you keep the weights confidential if a host is compromised?
First, limit what any host can get: the tracker admits a host only if its certificate identity is in the distribution’s target set. For stronger protection, encrypt the artifact with a per-release data key and let the swarm move only ciphertext; each host fetches the key from KMS with its own identity at load time. Then seeders, relaying peers and the origin never see plaintext, and revoking a host is revoking its access to the key.
Tracker, scheduler or DHT
Who knows which machine has which piece? Why not a DHT, like public BitTorrent?
Something has to introduce peers to each other and, ideally, know where the pieces are. Public BitTorrent avoids central points with a Kademlia DHT and peer exchange, because there is no trusted operator and trackers get taken down. Inside one company the constraints flip: there is an operator, machines are known and authenticated, and the network topology is known exactly. What matters is that peer choice respects racks and link budgets, and that a rollout is observable.
The load is small. 10,000 agents heartbeating every 2 s is 5,000 requests a second fleet-wide, 1,000 per data centre, and the whole availability map is about 4 MB. The real question is how much the coordinator decides: only who your neighbours are, or which piece to fetch from whom.
- A tracker per DC for peer lists and progress; peers choose pieces themselves chosen
- A central scheduler that assigns every piece transfer situational: heterogeneous or congested networks where a global view pays for itself; Dragonfly’s scheduler picks the parents each peer downloads from
- A DHT (Kademlia) for discovery rejected
- Gossip and peer exchange only, seeded from a static list situational: as the fallback while a tracker is unavailable
The answer: One tracker per data centre with a hot standby, and the global controller above them. The tracker admits agents by certificate identity and target set, hands out peer lists of about 20 rack-mates, 10 hosts from other racks and the DC seeders, refreshes them on each 2 s heartbeat, and keeps every agent’s bitfield as soft state. Peers choose pieces themselves from neighbours’ bitfields and HAVE messages, so the tracker is never on the data path; if it dies, transfers carry on and the standby rebuilds from the next heartbeats. The one thing the coordinator does assign is the seeders’ WAN slices, because that is where global coordination saves real bandwidth. A DHT solves a trust and takedown problem this system does not have, and it gives up exactly the topology control this system needs. Uber’s Kraken is shaped the same way at a high level: a tracker introduces peers, and dedicated origin hosts seed.
DC3’s tracker is down for two minutes in the middle of a rollout. What breaks?
Agents already in the swarm keep their connections and keep trading, so most of DC3 finishes on time. New or restarted agents cannot get a peer list; they fall back to the DC seeders in their config and to peer exchange with whoever they reach. DC3’s progress goes stale on the dashboard. When the standby takes over, it rebuilds from the next heartbeats in about 2 s, with nothing to replay.
At 100,000 machines, does one tracker per DC still work?
Per DC that is 20,000 agents and 10,000 heartbeats a second of a few hundred bytes each, still one process with headroom. Beyond that I would shard the tracker by groups of racks, since peer lists are mostly rack-local anyway, and send bitfield deltas instead of whole bitfields. The controller does not notice, because it only sees per-DC roll-ups.
Why not let the tracker tell each peer exactly which piece to fetch next?
Because a local choice is nearly as good and far more robust. A peer with 30 random neighbours sees rarity well enough, and rarest-first on a random graph keeps copy counts within a small factor of each other. Central assignment would put 400,000 decisions a second on the critical path and turn every scheduler pause into a fleet-wide stall. I keep central assignment for the one place it pays: which seeder fetches which slice across the WAN.
How do you pick neighbours so the swarm does not split into islands?
Random within topology groups rather than nearest-only: about 20 random rack-mates and about 10 random hosts from other racks. The cross-rack links keep the graph connected and are how new pieces enter each rack, and random choice gives a graph only a handful of hops across. Each heartbeat can swap a few neighbours, which heals the graph as hosts die.
A bad release is caught halfway through. How do you stop it everywhere?
Cancel through the API: the controller marks the distribution cancelled and pushes that to every tracker, and each agent learns it on its next heartbeat, within about 2 s. Agents stop requesting, refuse to serve that root to anyone, and delete partial data; seeders stop their WAN pulls. Because hosts that already finished have not loaded anything (loading is the deploy system’s job), nothing needs to be rolled back, only deleted.
Which piece next: rarest-first and endgame
Each machine sees 30 neighbours holding different pieces. Which piece should it ask for next?
The order in which pieces spread decides whether the swarm can use its upload capacity. Sequential order looks natural and is wrong: everyone holds the same prefix, so nobody has anything its neighbours lack, and all demand lands on whoever is furthest ahead, which is the seeders. The swarm degenerates back into central fan-out. Random order is better but leaves a coupon-collector tail: near the end, some pieces have few holders and thousands of machines queue on them.
Two observations shape the answer. Diversity is what makes trading possible, so a machine should take the piece that is least common around it. And the end of a transfer is different from the middle: the last few blocks are each in flight to one sender, so a single slow sender sets the finishing time.
- Sequential, in file order rejected
- Random situational: for the first two to four pieces, so a new peer has something to trade within a second
- Rarest-first among neighbours, finishing started pieces first chosen
- Endgame mode: ask every holder for the last blocks chosen
The answer: Random for the first few pieces, rarest-first after that with ties broken at random, a rule that a started piece is finished before new ones are begun (so half-done, unverifiable pieces do not pile up), and endgame mode at the end. Rarity comes from neighbours’ bitfields and HAVE messages, and the seeders serve the pieces they have sent least, so new pieces enter each DC already spread out. Endgame starts when every missing block is already requested, about the last 480 MB with 30 connections × 4 blocks × 4 MB in flight: the agent asks every neighbour that has those blocks and cancels the others when one arrives. All of this is in Bram Cohen’s 2003 BitTorrent paper, minus tit-for-tat: inside a company every peer cooperates, so there is no choking for incentives, only upload slots for fairness.
The loader wants to stream layers into GPUs in order as they arrive. Doesn’t that argue for sequential?
It argues for a mix. Give the next few pieces in file order a deadline-based priority and fetch everything else rarest-first, the way streaming BitTorrent clients do. In a swarm this size the head of the file spreads fast anyway, since everyone wants it. But loading usually cannot finish until the slowest piece lands, so I would measure whether streaming saves anything over waiting the extra seconds for the whole file.
Seeders hold every piece. Why does rarity matter at all?
Because seeders can serve only 2 % of a DC’s demand. If a piece is held only by seeders and 500 peers want it, those 500 queue on 100 GB/s that is shared with everything else. Rarest-first makes peers grab such a piece early, so within seconds it has dozens of peer copies and the seeders are no longer needed for it. It also means a seeder dying costs nothing, because every piece already has many copies.
Endgame duplicates traffic. How much, and could it break a budget?
It is bounded by what is in flight when it starts: about 480 MB per agent, asked of perhaps three holders each, with most duplicates cancelled within one block time, so well under 1 GB per host, under 0.5 % of 200 GB. It is all inside the DC and mostly inside the rack, so the WAN budget is untouched; seeders never duplicate across the WAN. When many agents hit endgame together, at the very end, the extra load falls on peers that are done and idle.
Peers keep choosing the same rare piece and hammer its one holder. What do you do?
Break ties at random so peers spread over the rarest few pieces instead of one. Cap each peer at about 8 concurrent uploads, so a hot holder serves at its capacity and turns the rest away, which sends them to other rare pieces. And as soon as anyone completes that piece it sends HAVE and its rarity drops, so a hot spot lasts a fraction of a second.
Racks, data centres and link budgets
How do you stop this from saturating the WAN and the rack uplinks?
The network is not flat. Each rack’s 40 hosts share a 400 Gbps uplink, 2.5:1 oversubscribed against their 1 Tbps of NICs; each remote data centre gets 40 Gbps of WAN for this job; the origin allows 10 GB/s. A topology-blind swarm chooses neighbours at random, so about 98 % of a host’s peers are outside its rack: ~7.8 TB per rack crosses the uplink, which caps each rack at ~160 s, twice the floor, and crowds out the production traffic sharing it. Across data centres it is worse: random peering over the whole fleet sends most bytes over the WAN.
The fix is to make each expensive link carry each piece once, or a few times, and let the cheap links multiply it. A piece should cross the origin link once, each WAN link once and each rack uplink a couple of times, then spread inside the rack at switch speed. That needs preference (who your neighbours are) and enforcement (token buckets on every capped link).
- Topology-blind random peers rejected
- Strict hierarchy: one leader per DC, then per rack, each fetching only from its parent rejected
- Rack-first, same-DC peer lists; DC seeders own disjoint WAN slices; token buckets on capped links chosen
- A DC-to-DC relay plan over the WAN situational: the WAN is not a star, for example DC4 is reached cheaply through DC3: DC4’s seeders then pull from DC3’s seeders instead of the origin DC’s
The answer: The tracker builds each agent’s list from its own data centre only: about 20 rack-mates, 10 random hosts from other racks, and the DC’s 8 seeders. An agent fetches a piece from a rack-mate if one has it, else from another rack, else from a seeder, and never crosses the WAN; with that rule a piece typically enters a rack two or three times and uplinks stay well below capacity. Only seeders cross the WAN, each owning the pieces whose index mod 8 is its slot, under a 625 MB/s token bucket so the eight together hold the link at 40 Gbps; the origin seeders read the origin under a 10 GB/s bucket. Each agent caps itself at 20 Gbps each way so the host’s own traffic keeps 5 Gbps. Budgets live in the controller’s plan and can change mid-rollout. It is the same principle as Netflix filling ISP caches off-peak, compressed into seconds: move the expensive copy once, then serve it locally.
DC5’s WAN budget is cut from 40 to 10 Gbps mid-rollout. What happens?
The controller pushes a new plan and DC5’s seeders lower their buckets to about 156 MB/s each within a heartbeat. A full copy now takes 160 s to cross instead of 40, so DC5’s hosts finish near 160 s rather than 80; nothing else changes, because inside the DC the swarm keeps pace with whatever arrives. The dashboard shows DC5’s ETA move, and the deploy system can wait for it or go ahead in the other four.
Where does the topology data come from, and what if it is wrong?
From the inventory system: host → rack → DC, which the network team already keeps for placement. Each agent reports its rack in the announce, and the tracker can cross-check against measured throughput, since rack-mates should see close to NIC speed. A wrong label costs efficiency, not correctness: that host just pulls more over its uplink. A rack whose uplink runs far hotter than its peers’ during a rollout is the signal to fix the inventory.
Why dedicated seeders? Ordinary peers could do the WAN fetch.
They could, and in a small system I would let them. Dedicated seeders get 100 Gbps NICs, so one copy crosses at the full budget with few hosts; they are the only hosts that need WAN egress rules; and they keep the last few artifacts, so late joiners and rollbacks never go back to the origin. They also make the budget simple: eight known hosts per DC with a bucket each, not a budget spread over whichever peers happen to be fast.
The service on these hosts says distribution slows it down. What can you do?
Lower the per-host cap, for example from 20 to 10 Gbps, which moves the floor to 160 s. Mark distribution traffic with a lower DSCP class so switches favour production packets. And schedule large rollouts off-peak, the Netflix idea. The swarm’s speed is simply the per-host budget, so this is a policy setting, not a redesign.
Stragglers, failures and when it is done
99 % of machines finished in three minutes. Why is the rollout still running at minute twenty, and what do you do about it?
At 10,000 machines something is always broken: a disk writing at 200 MB/s instead of 3 GB/s, a NIC with CRC errors, a host pinned by its own job, a flapping top-of-rack switch. If done means every target verified, the rollout takes as long as the worst of 10,000, and the worst of 10,000 is very slow. Failures also happen mid-transfer: a host reboots, a peer dies holding half a piece, a seeder disappears.
These are two problems. Inside the swarm, nothing should ever wait on one peer, so a failure costs only the bytes in flight. At the rollout level, done must be defined by what the consumer needs, 99 % of a serving pool or 100 % of a training job’s ranks, with the rest handled as repair, and the progress view must say exactly who is slow and why.
- Wait for 100 % of targets rejected
- Per-block deadlines and re-requests, endgame, and a per-pool quorum for done, with stragglers sent to repair chosen
- Send every block request to two peers from the start rejected
The answer: Inside the swarm, each block request has a deadline of max(1 s, 4× the expected time at that peer’s recent rate); on expiry it is cancelled and re-requested from another holder, and a peer that times out twice is dropped and replaced at the next heartbeat. Agents persist their bitfield after each durable piece so a reboot resumes, and a seeder that dies has its remaining slice reassigned. At the rollout level, agents heartbeat every 2 s, trackers roll up per DC every 5 s, and the controller flags a host as a straggler when its rate is below a quarter of the DC median or its ETA is past the p99, then asks the agent for a diagnosis: disk write rate, NIC errors, peer count, hashing rate. A pool is done when its quorum, 99 % by default, has verified; stragglers keep downloading and the deploy system keeps them out of serving until they verify. A host that fails verification twice, or cannot reach a tenth of the median rate, gets a repair ticket instead of more retries.
A training job needs all 1,024 of its hosts to have the weights. A quorum does not help. What then?
Then done is 100 % of that job’s hosts, and the lever is replacing stragglers rather than waiting for them. Give the job a deadline, say two minutes past its p99; at the deadline the scheduler swaps the slow hosts for spares that already hold the artifact and starts. Distributing to the spare pool before the job starts makes the swap free. Waiting indefinitely on one bad disk is the expensive option.
How do you tell a host starved by the network from one limited by its own disk?
From the agent’s own counters. If blocks arrive slowly from several peers that are serving others quickly, it is this host’s network path; if blocks arrive fast but disk write throughput or fsync latency is poor, it is the disk; if hashing is the slow stage, it is CPU contention. The cause goes into the roll-up, so the dashboard says "88 stragglers: 51 disk, 30 network, 7 unreachable" rather than one number. Each cause has its own fix, and only the network one is ours.
A whole rack goes dark for 30 seconds in the middle of a rollout.
Its 40 agents miss heartbeats and drop out of other agents’ lists after 6 s; their neighbours re-request whatever they were fetching from them elsewhere, losing at most the blocks in flight. When the rack comes back, its agents resume from their persisted bitfields and re-announce. Because rarest-first kept every piece well replicated, the swarm loses no data; only those 40 hosts lose 30 seconds.
How does the progress view stay accurate without 10,000 machines flooding it?
Aggregation at the edge: agents report to their DC tracker every 2 s, the tracker merges counts and histograms and sends one roll-up per DC every 5 s, so the controller sees about one message a second. Histograms of rate and ETA merge correctly across DCs, where averages and percentiles would not. Per-host detail stays in the tracker and is fetched only when someone drills into a straggler.
What would you alert on?
Origin egress above two copies per artifact, which means the seeders are being bypassed; any WAN link above its budget; rack uplink utilisation above 70 % during a rollout; p99 completion more than twice the floor; hash-mismatch reports above a small baseline, which signal failing hardware or a bad agent release; and any distribution below its quorum past its deadline. The first two cost money or hurt other teams, so they page.
The theory behind it
- Distributing software updates at scale: How apps, games, devices and package managers deliver updates to millions of clients: update manifests and version checks, CDN and peer-assisted delivery, delta patches, signing and integrity, staged rollouts and kill switches, rollback, bandwidth peaks on release day, and offline or constrained devices.
- Merkle trees: How hash trees let systems compare and verify large datasets cheaply: structure and root hashes, finding differences between replicas, proofs of inclusion, uses in databases' anti-entropy, file sync, Git, certificate transparency, blockchains and audit logs, and design choices for building them.
- Tail latency, percentiles and queueing: Why p99 matters more than the average, how fan-out amplifies tail latency, Little's law and queueing near full utilisation, hedged requests, timeouts and deadlines, and how to set and measure latency budgets.