
The headline fact is almost absurdly tidy: GitHub says 99.99999% of its repositories now live simultaneously in its own data centers and in Azure, with the first copies landing in Azure’s Central US region in August 2025. But the company itself frames that number as the easy part. The remaining fraction — a tiny sliver of repositories, but among the busiest on the platform — is stuck for a reason that has nothing to do with disk space or bandwidth. It’s stuck because of a 25-millisecond round trip.
Why moving data was never the hard part
Git hosting at GitHub’s scale depends on replication: most repositories keep five copies, spread across data centers, to balance durability and availability. During the migration, traffic is engineered to "follow the data" — as replicas move to Azure, GitHub’s internal routing layer, called Spokes, increasingly serves nearby requests from the nearest replica, until the old data center replica is retired entirely. Today, most repositories already have two of their five replicas in Azure, and Azure is reportedly handling about half of all Git traffic.
That is a genuinely large logistics operation, but logistics is not where distributed systems usually break. The real cost in a system like this is coordination — the requirement that multiple machines agree before anything can be considered "done." Agreement means round trips across a network, and round trips cost time no engineering team can negotiate away. Once a replica sits in Azure’s Central US region, roughly a thousand miles from GitHub’s own data centers, each such round trip takes about 25 milliseconds — and a single write typically requires several of them.
For most repositories, that added delay is survivable. For the busiest ones — the ones already pushing against their throughput limits — it’s enough to tip them over.
One lock to serialize them all
To understand why, it helps to know how GitHub’s current write path actually works. The system GitHub calls Spokes uses a quorum: a write only needs a majority of replicas to agree before it’s considered committed. In practice, though, the quorum waits for every healthy replica to respond, up to a timeout, rather than moving on as soon as a majority has answered. And because each write holds a lock on the entire repository until it commits, the slowest healthy replica doesn’t just add latency to its own write — it sets the pace for every write queued behind it. Add a 25-millisecond cross-region hop into that chain, and a repository that was already near its throughput ceiling has nowhere left to absorb the delay; it falls further behind with each new push.
GitHub’s fix leans on a principle the company has been stating as a design goal for its next-generation storage layer: minimize coordination — specifically, don’t serialize work that doesn’t actually need to be ordered together. Git’s own consistency rules only require that updates to a single reference (a branch or tag) are applied in order; updates to different references are, in the vast majority of cases, independent of one another. So instead of locking an entire repository for every write, GitHub is narrowing the scope of its quorum down to individual references.
| Aspect | Repository-wide locking | Per-reference serialization |
|---|---|---|
| What gets locked | The entire repository for each write | Only the specific reference being updated |
| Concurrent writes | One write in flight at a time, repository-wide | Many writes in flight, as long as they touch different references |
| Effect of a slow replica | Delays every pending write across the repository | Delays only writes contending on the same reference |
| Queueing behavior under load | Growing queue, each write waits longer than the last | Queue only forms around genuinely contended references |
| Reported effect on throughput | Throughput ceiling fixed by slowest replica | Reported 3–4x higher throughput at lower latency in internal tests |
That last row carries an important caveat: GitHub describes these as results from internal testing with "realistic traffic," not a universal guarantee for every workload or every repository shape. Even so, the underlying logic generalizes well beyond Git: narrowing the unit of agreement, rather than trying to make agreement itself faster, is one of the oldest tricks in distributed systems design — because agreement across machines, not raw data volume, is usually the expensive part.
Separating "where data lives" from "how fast you can read it"
Per-reference locking addresses writes, but most Git traffic isn’t writing at all. GitHub notes that typical repositories see somewhere between 100 and 1,000 reads for every write — which matters because, today, reads and writes compete for the same replica resources. A read-heavy repository can be write-bottlenecked and still spend most of its actual traffic serving code browsing, clones, and CI checkouts.
GitHub’s second move is to let object storage — Azure Blob Storage, in this case — become a formal member of the write quorum, through what it calls a "virtual replica." This ephemeral node streams incoming writes into durable blob storage but serves no reads itself; a write isn’t considered acknowledged until it’s durably stored there. Because per-reference serialization already prevents writes from queuing behind each other, this extra storage step adds a small amount of latency without creating a new bottleneck — and the virtual replica even batches writes to different references into a single network trip to object storage.
flowchart LR A[Dual-written copies: own DCs plus Azure] --> B[Traffic follows replicas to Azure] B --> C[Per-reference quorum unlocks last busy repos] C --> D[Object storage joins quorum as virtual write replica] D --> E[On-demand virtual read replicas scale independently]
Once durability lives in object storage, read replicas no longer need to join the write quorum at all — they can warm a cache straight from blob storage and reconcile in the background. In GitHub’s internal load testing, this arrangement reportedly let their testing harness hit its own limits before finding a ceiling in the new read path, a contrast to the clear ceilings it found in the current architecture. That’s a promising signal, not proof the read path is now unlimited in production — and GitHub itself flags unresolved issues, including workloads that need stricter real-time consistency than background reconciliation currently offers.
An infrastructure story, not a relocation story
It’s tempting to read "GitHub moves to Azure" as a story about cloud vendors and server locations. The more useful reading is about what coordination costs at scale. Storage is cheap and increasingly elastic; agreement is not. GitHub’s own account is candid that this is a bridge, not a finish line — the company says write throughput remains bounded by the quorum itself, and that the harder, unresolved problem is ordering the small amount of data that genuinely needs ordering, without just moving the bottleneck somewhere else.
The lesson travels well beyond one company’s Git infrastructure. Any system that serves both heavy read traffic and bursty write traffic across distant data centers runs into the same wall: adding more servers or more storage doesn’t help if every operation still has to wait for the same conversation to finish. The real engineering work, as GitHub’s own migration shows mid-flight, is deciding what actually needs to wait — and letting everything else move on without it.


