CS PAPERS DEEP-READ · PAPER 59
Verma, Pedrosa, Korupolu, Oppenheimer, Tune, Wilkes · Google · EuroSys 2015
In 2015 Google published the first systematic account of Borg — a system that had already been quietly running inside the company for over a decade, deciding which machine every Google program (Search, Gmail, ads, data analysis) runs on. Kubernetes, which you have probably heard of, is essentially Borg's ideas rewritten and open-sourced.
What Borg does fits in one sentence: it treats the thousands of machines in a datacenter building as one giant machine to hand out. You no longer say "give me machine 3 and machine 7"; you say "run 500 copies of this program, each needing roughly this much compute and memory."
Before Borg — and still, in many companies today — each team fenced off its own pile of machines. Two problems follow. Every pile has to be bought for its busiest moment, so most of the time it sits idle. And the piles can't lend to each other: while the ads machines are bored at 3am, the reporting jobs queued up next door still can't touch them.
An analogy: an office building assigns each company its own floor and forbids anyone from using another floor's meeting rooms. Every floor must then keep a room big enough for an all-hands — a meeting that happens twice a year.
The work running on those machines comes in two kinds with opposite personalities. Online services (web search, email) must stay up, and users feel it immediately when they slow down — but their load swings wildly across the day. Batch jobs (crunching data, building reports) run for minutes or hours and then vanish; nobody minds if they're slow, and re-running them is fine.
Borg's core move is to run both kinds on the same machines, letting batch jobs eat whatever the online services aren't using right now. Back to the building: meeting rooms are no longer assigned — you just book one. But meetings have ranks: when a real client arrives, the internal brainstorm inside has to clear out.
First, ranks. Every piece of work carries a priority. When machines get tight, Borg simply kicks the low-priority work out and hands the space to the high-priority work; the evicted batch job queues up for the next round — it can just re-run.
Second, watch real usage and take back what isn't used. Engineers almost always ask for more than they need, out of fear of running short. Borg doesn't quite trust that number: it keeps watching how much you actually use, draws a line a safe margin above that, and lends the gap between that line and your request to batch jobs. When your usage really does climb, it takes the loan back.
Third, it repairs itself. At ten thousand machines, some break every day. Borg listens to every machine's heartbeat; when one goes silent, the work on it restarts elsewhere automatically.
Google did the arithmetic: splitting online services and batch jobs onto separate machines would make a median-sized datacenter buy 20–30% more machines. More lasting is the habit it established — you describe what you want, not where it runs — which is how essentially every cloud and every Kubernetes cluster works today. One honest note on the price: this bargain is paid for by batch jobs accepting that they may be killed at any moment, so such programs must tolerate being cut off halfway and starting over.
Treat the whole building as one machine: you say what you want, not where it runs. Online services and batch jobs are mixed onto the same machines, and utilization is squeezed up by priority-based eviction plus reclaiming the resources you asked for but never used, with failed machines replaced automatically — saving 20–30% of the fleet, and defining what Kubernetes would later look like.
Want the architecture diagram, how the scheduler picks one machine out of ten thousand, and the measured numbers? → Switch to the deep read
Borg is Google's cluster manager: users submit a declarative job spec (how many copies, how much CPU and memory each, what constraints), and Borg picks machines inside a cell of tens of thousands of them, starts the work, watches it, and restarts it on failure. Using priority-based preemption it packs latency-sensitive online services and throughput-oriented batch work onto the same machines, then uses resource reclamation to lend out capacity that was requested but never used — pushing datacenter utilization far up while keeping the online services available. It is the first systematic account of a decade-plus of Google production experience, and the direct design blueprint for Kubernetes.
cgroups caps how much CPU and memory each program may use. Unlike a virtual machine it carries no operating system of its own, so it is far cheaper, but the isolation is also less complete.By Abhishek Verma, Luis Pedrosa, Madhukar Korupolu, David Oppenheimer, Eric Tune and John Wilkes of Google, published at EuroSys 2015. By the time it was written, Borg had been in production for over a decade — the systems behind famous papers like GFS, MapReduce and Bigtable are themselves just tasks running on Borg. Its external contemporaries were Mesos (2011) and YARN (2013); inside Google there was also Omega (2013), an experimental successor replacing Borg's monolithic master with shared state and optimistic concurrency. A year later the same people wrote Borg, Omega, and Kubernetes (2016) to review a decade of lessons — and Kubernetes is Borg's model rewritten in the open.
Google had to keep two temperamentally opposite workloads alive on the same fleet. Online services (Search, Gmail, ad serving) run indefinitely and are sensitive to tail latency, but their traffic peaks and troughs across the day. Batch work (index building, log analysis, MapReduce jobs) runs from seconds to days, exits when done, and only cares about throughput.
The old approach was to carve the fleet into disjoint pools by purpose: each pool sized for its own peak, so each pool sits on a large idle margin that it cannot lend to its neighbor. Worse, at ten-thousand-machine scale hardware failure is routine, not exceptional — if humans had to patch, restart and reconfigure by hand, operations could never keep up.
So there were three problems, pulling against each other: raise utilization; but raising utilization means mixing workloads, and mixing lets neighbors interfere with the online services, so keep those available anyway; and let thousands of engineers ship and keep programs running without needing to know that machines exist.
Borg's first decision was to narrow the interface down to a declarative spec: a job declares how many copies, how many milli-cores / how much memory / how much disk each needs, and what kind of machine it requires. Then you hand it over. Borg picks machines, installs packages, starts the tasks, watches liveness, restarts failures, and moves work off dead machines. "Which machine is my program on" stops being a question engineers have to answer.
Two small parts make this actually usable. One is the alloc (short for allocation): a reserved slice of resources on a machine into which several cooperating tasks can be placed (a main program plus a log-shipping sidecar, say), moving together with the reservation — this is precisely the ancestor of the Kubernetes pod. The other is BNS (the Borg Name Service): every task gets a stable name (kept in Chubby) so others can find it without knowing which machine or port it currently sits on. Without that naming layer, "tasks move around at any time" would be a catastrophe rather than a feature.
The architecture itself is plain: a cell has one logical Borgmaster (in fact 5 replicas, holding cell state and electing a leader via Paxos, with the scheduler inside) plus a small agent, the Borglet, on every machine. The Borgmaster polls each Borglet every few seconds to pull current state and push pending work; polling rather than having Borglets report in lets the master control the pace of traffic, so it isn't swamped by tens of thousands of machines at once during recovery. Conversely, when the master is unreachable a Borglet keeps looking after the tasks already running, so a brief master outage doesn't reach the programs actually serving users.
The main engine of utilization is putting online services and batch work on the same machines. Each task carries a priority, and priorities fall into non-overlapping bands (roughly, from high to low: monitoring → production → batch → best-effort). Production (prod) means the online services that must not go down; everything else is non-prod.
The rule is blunt: when a machine can't fit everything, a higher-priority task pushes a lower-priority one out — the latter is killed and re-queued. With one important exception: production tasks do not preempt one another, or a single eviction would set off a cascade of "I evict you, you evict him."
Priority alone isn't enough, or everyone would label their own work top priority. Hence quota: how much a team may request, at a given priority, over a given period, checked as admission control at submission time. Quota at high priority is expensive and scarce, while quota at the lowest priority is close to unlimited — the price being that such work can be preempted at any moment. Priority decides who yields at run time, quota decides who may ask for how much at submission time; the two must stay separate, or "priority" degenerates into an arms race nobody backs down from.
Mixing alone is not enough, because engineers almost always ask for more than they need — better to over-insure than be paged at 3am for out-of-memory. So on any machine there is a persistent gap between what has been "handed out" and what is actually in use.
Borg's answer is to split a task's resources into three lines. The limit is what you requested, and what you are charged for. The usage is the real consumption Borg keeps observing. In between sits the reservation, which Borg estimates from recent usage plus a safety margin. Everything between reservation and limit is judged "you almost certainly won't need this" and reclaimed — lent out to non-production work. When usage rises the reservation rises with it and the loan is called back immediately, with the cost borne by low-priority tasks that can survive being killed. The paper reports that roughly 20% of the workload runs on such reclaimed resources.
Scheduling has two steps. First, feasibility checking: which machines could hold this task. What counts as "enough room" depends on priority — for production tasks it is computed against the neighbours' declared limits, for non-production tasks against their estimated reservations, and that is exactly where overcommitment happens. Second, scoring, to pick one of the feasible machines: the policy is a hybrid of spreading (scatter load, leaving headroom for spikes) and best fit (pack a machine tight, reducing fragmentation). Pure spreading leaves nowhere able to take a large task; pure packing falls over at the first peak.
The difficulty is scale: a cell has tens of thousands of machines, and some cells see arrival rates above 10,000 tasks per minute, so scoring machine by machine can never keep up. Borg cuts it down three ways: score caching (reuse the last score unless the machine changed materially), equivalence classes (the hundreds of tasks in one job have identical requirements, so score once, not hundreds of times), and relaxed randomization (examine a random subset and stop once there's enough, rather than chasing the global optimum). All three buy the same thing: trading "optimal" for "computable in time." One easily overlooked measurement: a task takes a median of about 25 seconds from scheduling to running, roughly 80% of it spent installing packages — which is why Borg prefers machines that already have the package. In real systems the bottleneck is often not in the algorithm you were staring at.
Once you pack strangers onto one machine, isolation becomes the crux. Borg uses cgroups-based containers rather than virtual machines — saving one operating system per VM, at the cost of less complete isolation. Resources are treated in two classes: compressible ones (CPU, disk I/O bandwidth) are simply throttled when exceeded; non-compressible ones (memory, disk space) cannot be slowed down, so exceeding them means the task is killed.
Availability rests on a set of habits: evicted tasks are automatically rescheduled; the replicas of a job are deliberately spread across failure domains (different racks, different power) so one outage can't take them all; and the rate of simultaneous disruptions is capped. The paper also mentions something very practical — Fauxmaster, a high-fidelity simulated master that can replay real cell state offline, used for debugging, capacity planning, and asking "what if we changed this policy?" The "how many machines did that save" results in the next section came out of it.
The evaluation method is itself worth learning: cell compaction — feed a real cell's workload to Fauxmaster, keep removing machines and re-packing from scratch each time until the workload no longer fits. The smallest machine count that still works is the workload's true cost; change one policy, recompute, and the difference tells you what that policy is worth in machines.
First, it turned "the cluster is the computer" from a slogan into an engineering fact with production evidence: a decade-plus of running records at ten-thousand-machine scale showing that "engineers declare needs and never touch machines" works, and that it saves fleet-sized money. "Mix, preempt by priority, overcommit against measured usage" became the default agenda for every cluster scheduler afterwards.
Second, it is the design blueprint for Kubernetes. The alloc became the pod; "declarative spec plus a master continuously pulling reality toward the desired state" became K8s's core loop; the Borglet became the kubelet. Most of what we now call cloud-native practice — containers, declarative configuration, control plane separated from data plane, priority and preemption — has its prototype in this paper.
① In one sentence: users submit a declarative "what I want" spec and Borg picks machines, starts, watches and restarts the work inside a cell of tens of thousands — allocating a datacenter as if it were one machine.
② The pain: fleets carved into disjoint pools, each sized for its own peak and unable to lend its idle capacity; at ten-thousand-machine scale, failure is routine and manual operations can't keep up.
③ Mechanism one, mixing: online services and batch work on the same machines, with priority bands and preemption keeping the former first; production tasks never preempt each other, to avoid cascades.
④ Mechanism two, reclamation: limit (requested) / usage (measured) / reservation (measured + margin); the band between reservation and limit is lent to low-priority work, and about 20% of the workload runs there.
⑤ Scheduling: feasibility checking then scoring (a hybrid of spreading and best fit), made computable by score caching, equivalence classes and relaxed randomization; median task startup 25s, 80% of it package installation. Supporting parts: allocs (pod ancestors), BNS naming, cgroups isolation.
⑥ Results (measured by cell compaction): segregating prod from non-prod costs 20–30% more machines; coarsening resource requests costs 30–50% more; big cells beat small ones; median cell about 10,000 machines.
⑦ Impact and limits: the direct blueprint for Kubernetes (alloc→pod, declarative control loop, kubelet); but the authors themselves call the job-only grouping and one-IP-per-machine mistakes, container isolation is incomplete, and the gains lean on Google's homogeneous environment in ways a public cloud may not be able to copy.