~learn.

Zoom Video Meetings at Scale, the whole design.

Video meetings for 300 million daily meeting participants, by keeping a small, ordered control plane apart from a media plane of routers that forward packets without decoding them, pick a layer for every receiver, and cascade across regions.

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

SQ-23 · Zoom Video Meetings at Scale · design sheethimanshukukreja.in/learn/sheets/bonus-23-zoom-video-meetings

At a glance#

Number Value What it drives
Daily meeting participants 10M (Dec 2019) → 300M (Apr 2020) a count of joins, not people: one person in five meetings counts five times
Meeting minutes 3.5T annualized (Oct 2020) · ~9.6B a day treated as participant-minutes; ~32 min per join
Concurrent participants ~6.7M average · ~20M at the peak hour a 3× peak; ~3.3M meetings of 6
Media at peak ~15 Tbps in · ~30 Tbps out · ~3B packets / s per-packet work must be a lookup and a send
Forward vs mix ~4,500 servers vs ~360,000 route, don't mix: mixing decodes every camera and encodes per receiver
Joins ~60,000 / s at the top of the hour · design for 100,000 the herd hits the control plane, not the media plane
Delay < 150 ms one way in region (ITU-T G.114) nearest zone per participant; one clean long hop between regions
Data centres 17 in 2020, 2 in India (Mumbai, Hyderabad) meeting zones, 50% spare capacity
End-to-end encryption up to 200 participants · no cloud recording servers become relays without keys

Architecture#

DiagramA control plane and a forwarding media plane
%% title: A control plane and a forwarding media plane
flowchart TD
  CL@{ icon: "sd:phone", label: "Desktop, mobile and web clients<br/>audio + 3 video layers" } ==> API@{ icon: "sd:api", label: "API gateway<br/>join, auth, signalling sockets" }
  API --> MS["Meeting service<br/>passcode · waiting room · roles"]:::service
  MS --> CTRL["Meeting controller<br/>one owner per meeting, versioned events"]:::core
  CTRL --> ST@{ icon: "tech:redis", label: "Redis<br/>live state · leases with epochs" }
  CTRL --> ZS["Zone selector<br/>RTT · capacity · residency"]:::service
  ZS --> ZC@{ icon: "sd:server", label: "Zone controller<br/>MMR health and load" }
  ZC --> MMR["MMR<br/>forwards packets, picks layers"]:::hot
  CL ==>|"UDP · TLS 443 fallback"| MMR
  MMR <==>|"one copy per layer"| BB@{ icon: "sd:globe", label: "Backbone<br/>MMR-to-MMR cascade" }
  MMR -.-> REC@{ icon: "sd:worker", label: "Recorder<br/>a silent participant" }
  REC --> OBJ@{ icon: "sd:object-storage", label: "Object storage<br/>~360 TB a day" }
  class CL client
  class API,BB edge
  class ST,OBJ store
  class ZC,REC service
DiagramOne sender, a layer per receiver
%% title: One sender, a layer per receiver
flowchart TD
  K@{ icon: "sd:laptop", label: "Sender<br/>180p 150 kbps · 360p 400 kbps · 720p 1.5 Mbps" }
  MMR["MMR<br/>budget per receiver: audio, share, speaker, thumbnails"]:::hot
  BWE["Bandwidth estimate per downlink<br/>arrival-time feedback · GCC-style rules"]:::core
  A@{ icon: "sd:phone", label: "Phone on 4G<br/>~600 kbps · 360p" }
  R@{ icon: "sd:laptop", label: "Laptop on fibre<br/>~20 Mbps · 720p" }
  T@{ icon: "sd:desktop", label: "Gallery tiles<br/>180p" }
  K ==> MMR
  BWE -.-> MMR
  MMR ==> A
  MMR ==> R
  MMR ==> T
  A -.->|"feedback"| BWE
  class K,A,R,T client

Key flows#

DiagramKavya joins, then speaks
%% title: Kavya joins, then speaks
sequenceDiagram
  autonumber
  participant K as Kavya's app
  participant A as API gateway
  participant C as Meeting controller
  participant Z as Zone selector
  participant M as MMR, Mumbai zone
  participant R as MMR, San Jose zone
  K->>A: Join with meeting ID and passcode
  A->>C: Admit Kavya (host)
  C->>Z: Nearest zone with room
  Z-->>K: Mumbai MMR, media token, 60 s
  K->>M: UDP hello with token
  K-)M: Opus audio, 3 video layers
  M-)R: One copy of each layer San Jose needs
  Note over K,M: about 1.5 s from click to first audio
DiagramArjun's cab enters a dead spot
%% title: Arjun's cab enters a dead spot
sequenceDiagram
  autonumber
  participant A as Arjun's phone
  participant M as MMR
  A-)M: Arrival times, several reports a second
  M->>M: Delay rising, estimate falls to 600 kbps
  M-)A: Kavya at 360p, thumbnails paused, audio kept
  A-)M: NACK for missing video packets
  M-)A: Resent from a short buffer
  Note over A: audio rebuilt from Opus in-band FEC
  Note over M: Rahul and the others stay on 720p
DiagramMeenal behind a firewall that allows only HTTPS
%% title: Meenal behind a firewall that allows only HTTPS
sequenceDiagram
  autonumber
  participant C as Meenal's client
  participant P as Corporate proxy
  participant M as MMR
  C->>M: UDP to the media ports
  C->>P: TLS on 443, raced in parallel
  P->>M: Tunnelled media session
  M-->>C: TLS works first, UDP never answers
  Note over C,M: same meeting, more delay under loss, lower layers sooner

Data model#

DiagramCore entities
%% title: Core entities
classDiagram
  class Meeting {
    +meetingId : string
    +hostId : user
    +passcode : hash
    +waitingRoom : bool
    +locked : bool
    +residency : region list
  }
  class Participant {
    +participantId : string
    +sessionId : survives reconnects
    +role : host / co-host / attendee
    +muted : bool
    +zone : meeting zone
  }
  class MediaToken {
    +meeting : meetingId
    +participant : participantId
    +role : string
    +expires : about 60 s
  }
  class Subscription {
    +receiver : participantId
    +sender : participantId
    +tilePx : int
    +layer : 180p / 360p / 720p / paused
  }
  class ControllerLease {
    +meetingId : string
    +epoch : int, fencing
    +version : last event
  }
  class Recording {
    +meetingId : string
    +chunks : object keys
    +retention : days
  }
  Meeting "1" --> "many" Participant : roster
  Participant "1" --> "1" MediaToken : admitted with
  Participant "1" --> "many" Subscription : receives
  Meeting "1" --> "1" ControllerLease : owned under
  Meeting "1" --> "0..many" Recording : produces

Lifecycle#

DiagramA participant's life in a meeting
%% title: A participant's life in a meeting
stateDiagram-v2
  [*] --> JOINING
  JOINING --> WAITING: waiting room on
  JOINING --> IN_MEETING: admitted by policy
  WAITING --> IN_MEETING: host admits, token issued
  WAITING --> [*]: host denies
  IN_MEETING --> RECONNECTING: network change or MMR failure
  RECONNECTING --> IN_MEETING: same session within 60 s
  RECONNECTING --> [*]: timed out
  IN_MEETING --> [*]: leaves or removed, token revoked
  class IN_MEETING ok
  class WAITING queue

Decisions#

Decision Chose Over Because
Media topology SFU: forward packets, never decode MCU mixing ~80× fewer servers, no added decode delay, a layout per receiver, E2EE possible
Small meetings SFU too Peer-to-peer mesh 10 people means 9 uploads from a phone: ~11 Mbps up
Adaptation Simulcast or SVC layers, chosen per receiver Re-encoding per receiver the server can't decode; layers let it pick by dropping packets
Audio Forward the 3 loudest by audio-level header All streams to all silence costs nothing; still no decoding
Transport UDP with FEC, NACK, adaptive jitter buffer TCP everywhere a late packet is useless; TCP stalls behind a loss
Firewalls Race UDP and TLS on 443 A slow sequential ladder joins stay under a few seconds for blocked networks
Placement Nearest zone per participant, cascaded MMRs One MMR per meeting each stream crosses an ocean once, on a clean backbone
Meeting state One controller per meeting, lease + epoch fencing State spread across MMRs ordered host decisions; a stale owner can't admit anyone

When it breaks#

Failure What users see Contain it with
Top of the hour, ~60,000 joins / s slow joins pre-warm from yesterday's curve; one-round-trip join; jittered retries
An MMR dies with 400 people a few seconds of freeze reconnect with token and session to another MMR in the zone; state is in the controller
A keyframe storm in a 5,000-person webinar frozen tiles at every speaker change collapse requests at each MMR; pre-subscribe likely speakers at a low layer
4G loss in bursts crackle, frozen video FEC up as loss rises; NACK on short round trips; lower layers
UDP blocked slow join, more delay TLS on 443 raced in parallel; bigger jitter buffer
Uninvited guests strangers sharing screens passcodes and waiting room on by default; revoke tokens at the MMR
Controller failover host controls pause a few seconds new epoch rebuilds from snapshot and reconnects; media keeps flowing
A 40,000-person town hall joins and keyframe requests pile onto one meeting a tree of MMRs per region; no roster to attendees; degrade attendee video first

Say it in the interview#