~learn.

GitHub, the whole design.

A code host for a billion repositories, by making each reference update a majority vote across five replicas, turning every accepted push into one event that isolated consumers fan out to pull requests, webhooks, CI and search, and running untrusted CI jobs on fresh VMs scheduled fairly per account.

7 diagrams · 8 sections · revise in ~10 min · Download PDF prints it in the light theme, diagrams included

SQ-29 · GitHub · design sheethimanshukukreja.in/learn/sheets/bonus-29-github-code-hosting

At a glance#

Number Value What it drives
Repositories ~1 billion · five replicas by default ~100 PB on fileservers at an assumed 20 MB each
Pushes 3.35B a month · ~1,300 / s design for ~5,000 / s; each push is a small distributed transaction
Reads per write 100 to 1,000 130,000 to 1.3M Git reads / s; reads must scale without adding voters
Spokes operations 100B+ a day ≈ 1.2M / s routing, voting and repair are one busy service
Work after a push 60+ pieces of logic · 20 services one Kafka event, isolated consumers, per-task retries
Actions jobs 124M a day · 1,440 / s (2026 average) ~430,000 VMs busy at an assumed 5 minutes a job
Webhooks 2XX within 10 s · 3-day redelivery per-host queues and breakers; delivery id as idempotency key
Code search 15.5B docs · 25 TB index · ~100 ms p99 per shard n-gram index sharded by blob id; every query hits every shard

Architecture#

DiagramVote on the write, fan out the event, run CI on disposable machines
%% title: Vote on the write, fan out the event, run CI on disposable machines
flowchart TD
  DEV@{ icon: "sd:laptop", label: "Developers and agents<br/>git over SSH or HTTPS" } ==> LB@{ icon: "sd:load-balancer", label: "GLB, layer 4<br/>stateless directors" }
  LB ==> GITP["Git proxies<br/>auth · rate limits"]:::service
  LB --> APP["Rails monolith<br/>web · API · PRs"]:::service
  APP --> MY@{ icon: "tech:mysql", label: "MySQL and Vitess<br/>PRs · issues · users" }
  GITP ==> SPK["Spokes<br/>route · vote · repair"]:::hot
  SPK ==> FS@{ icon: "sd:servers", label: "Fileservers<br/>five replicas" }
  SPK -.-> BLOB@{ icon: "sd:object-storage", label: "Azure Blob Storage<br/>virtual replica" }
  SPK ==>|"ref updated"| K@{ icon: "tech:kafka", label: "Kafka<br/>one event per push" }
  K ==> JOBS@{ icon: "sd:worker", label: "Push jobs<br/>per-task retries" }
  K --> SRCH@{ icon: "sd:search", label: "Code search<br/>sharded by blob id" }
  JOBS ==> HOOK@{ icon: "sd:webhook", label: "Webhook delivery<br/>per-host queues" }
  JOBS ==> ACT["Actions control plane<br/>fair share · leases"]:::core
  ACT ==> RUN@{ icon: "sd:server", label: "Runner VMs<br/>one job each" }
  class DEV client
  class LB edge
  class MY,FS,BLOB store
  class K queue
  class JOBS,SRCH,HOOK,RUN service
DiagramOne reference update as a vote across five replicas
%% title: One reference update as a vote across five replicas
flowchart TD
  P["Push arrives<br/>pack already on replicas"]:::service
  LOCK["Lock on a majority<br/>per reference, rolling out 2026"]:::core
  CHECK{"Old value matches?"}
  APPLY["Apply tentatively<br/>XOR checksum update"]:::core
  VOTE{"Majority with<br/>equal checksums?"}
  COMMIT(["Commit · acknowledge"]):::ok
  ROLL(["Roll back · reject"]):::bad
  REPAIR@{ icon: "sd:worker", label: "Sweeper<br/>repair the losers" }
  P ==> LOCK ==> CHECK
  CHECK ==>|"yes"| APPLY ==> VOTE
  CHECK -->|"no"| ROLL
  VOTE ==>|"yes"| COMMIT
  VOTE -->|"no"| ROLL
  COMMIT -.->|"minority disagreed"| REPAIR
  class REPAIR service

Key flows#

DiagramNandini pushes the tag v4.0.0
%% title: Nandini pushes the tag v4.0.0
sequenceDiagram
  autonumber
  participant N as Nandini's laptop
  participant P as Git proxy, SSH
  participant S as Spokes
  participant R as Five replicas
  participant K as Kafka
  participant J as Push jobs
  N->>P: git push, key and write permission checked
  P->>S: Which replicas hold the repository
  S-->>P: Five replicas, preference order
  P->>R: Send pack, new objects only
  S->>R: Vote on ref update, old and new value
  R-->>S: Applied, checksums match on a majority
  S->>R: Commit
  P-->>N: new tag v4.0.0
  S->>K: Push event, repo, ref, old and new SHA
  K->>J: PR sync, webhooks, workflow trigger, indexing
  Note over J: each consumer has its own offset and retry policy
DiagramWebhooks to a fast receiver and a dead one
%% title: Webhooks to a fast receiver and a dead one
sequenceDiagram
  autonumber
  participant F as Fan-out
  participant Q as Per-host queues
  participant S as Senders
  participant B as Chat bot
  participant V as CI vendor
  F->>Q: Signed delivery, delivery id, per subscribed host
  Q->>S: Chat bot queue, within its cap
  S->>B: POST, 10 s timeout
  B-->>S: 200 in 50 ms
  Q->>S: CI vendor queue
  S->>V: POST, 10 s timeout
  alt vendor keeps timing out
    S-->>Q: Breaker opens · stop sending, probe now and then
    Note over Q: queued only until the delivery's shelf life · then logged as failed
  else vendor recovers
    S->>V: Drain at a ramped rate
    V-->>S: 2XX · duplicates skipped by delivery id
  end
DiagramAn Actions job with a lease and an epoch
%% title: An Actions job with a lease and an epoch
sequenceDiagram
  autonumber
  participant T as Trigger
  participant C as Control plane
  participant V1 as Runner VM 1
  participant V2 as Runner VM 2
  T->>C: Workflow admitted, jobs into the account's queue
  C->>V1: Assign job, epoch 1, scoped token
  Note over V1: VM 1 stalls, lease lapses
  C->>V2: Reassign job, epoch 2
  V2-->>C: Running, epoch 2
  alt VM 1 comes back
    V1->>C: Report with epoch 1
    C-->>V1: REVOKED · take new work
  end
  V2-->>C: Completed · VM destroyed

Data model#

DiagramCore entities
%% title: Core entities
classDiagram
  class Repository {
    +repoId : int
    +replicas : five fileservers
    +checksum : XOR of ref hashes
  }
  class Reference {
    +name : branch or tag
    +sha : commit id
    +lock : per reference
  }
  class PushEvent {
    +eventId : string
    +oldSha : string
    +newSha : string
  }
  class PullRequest {
    +headRef : string
    +testMergeRef : hidden reference
    +mergeable : derived
  }
  class WebhookDelivery {
    +deliveryId : GUID, same on redelivery
    +host : destination
    +status : delivered or failed
  }
  class WorkflowJob {
    +account : string
    +label : runner image and size
    +epoch : int
    +state : QUEUED to COMPLETED
  }
  Repository "1" --> "many" Reference : holds
  Reference "1" --> "many" PushEvent : each update
  PushEvent "1" --> "many" WebhookDelivery : per subscribed host
  PushEvent "1" --> "many" WorkflowJob : triggers
  Repository "1" --> "many" PullRequest : open

Lifecycle#

DiagramA hosted Actions job
%% title: A hosted Actions job
stateDiagram-v2
  [*] --> PENDING_APPROVAL: fork PR, first-time contributor
  [*] --> QUEUED: trigger admitted
  PENDING_APPROVAL --> QUEUED: maintainer approves
  PENDING_APPROVAL --> EXPIRED: 30 days
  QUEUED --> ASSIGNED: fair-share pick · epoch n
  ASSIGNED --> QUEUED: lease lapsed · epoch n+1
  ASSIGNED --> RUNNING: VM accepts · epoch matches
  RUNNING --> COMPLETED: steps done
  RUNNING --> FAILED: step failed or 6 h limit
  QUEUED --> CANCELLED: user or concurrency group
  COMPLETED --> [*]
  FAILED --> [*]
  CANCELLED --> [*]
  EXPIRED --> [*]
  class COMPLETED ok
  class FAILED bad
  class ASSIGNED queue

Decisions#

Decision Chose Over Because
Repository writes Majority vote per write, checksums One primary per repository no election to get wrong; without a majority it refuses, never splits
Lock scope Per reference (rolling out, 2026) Repository-wide lock 25 ms cross-region round trips capped busy repositories
Read scaling Cache-only read replicas over object storage More voting replicas each voter added work to every write
After the push One Kafka event, isolated consumers One big push job per-task retries; ~99.897% to 99.999% fully processed
Webhooks Per-host queues, breakers, shelf life, 3-day redelivery Retry forever late events are useless; backlogs knock receivers over
CI isolation Fresh VM per job, scoped token Reused runners strangers' code must not persist into the next job
CI fairness Per-account queues and caps, leases with epochs One global queue one burst can't starve others; revoked jobs fail fast

When it breaks#

Failure What users see Contain it with
A replica slow or far away slower pushes on busy repos per-reference locks; coalesce test merges; user pushes first
HTTPS Git proxy exhausted HTTPS push and pull fail separate SSH and HTTPS fleets; switch remotes
Webhook host down red deliveries in the log per-host breaker; shelf life; redeliver; reconcile via API
Actions database saturated runs stuck queued automatic intake breaker; queues absorb the burst
Runners hold revoked jobs backlog that feeds itself lease epochs; refuse stale runners at once
Search index corrupted stale or failed code search rebuild from Git; point-in-time rollback

Say it in the interview#