SYSTEM DESIGN · DESIGN PROBLEMS · CHAT AND REAL TIME · HARD · 8 OF 41

Design Google Docs: Real-Time Collaborative Editing Without Losing a Keystroke

A LADDER walkthrough of a collaborative document editor: requirements, scale math for millions of concurrent editors, the API and an operation-log data model, then the hard parts: why concurrent edits conflict, operational transformation versus CRDTs, one session server per document, durability with an op log and snapshots, failover, offline edits, live cursors, version history and permissions.

Reported atGoogleMicrosoftMetaAtlassian

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
1 / 20

Press play to hear the narration, or step through at your own pace.

Notes

Listen

Listen. Users create and edit rich text documents. Many people can edit at once and see each other's cursors live. There is version history with restore, comments anchored to ranges of text, and sharing with view, comment and edit permissions. Non-functional: others see your edit within a couple of hundred milliseconds, every copy of the document converges to the same text, and an edit that was acknowledged is never lost.

Scope out spreadsheets, slides and export. Ask the questions that bound the hard part. How many simultaneous editors per document? About a hundred, with more people just viewing. Is offline editing required? Yes, for minutes or hours, which matters for the algorithm choice. How big can a document get? Tens of megabytes, with images stored separately as blobs.

Requirements
  • functional:
  • create, open, edit rich text
  • many people edit at once
  • see others’ cursors live
  • version history, restore
  • comments on ranges
  • share: view, comment, edit
  • non-functional:
  • others see edits in ~200 ms
  • all copies converge
  • never lose an acked edit
Scope and questions
  • out of scope: spreadsheets,
  • slides, drawing, spell check,
  • export to PDF
  • ask: max editors per doc?
  • → 100 editing, more viewing
  • ask: offline editing?
  • → yes, minutes to hours
  • ask: doc size? → up to ~50 MB
  • with images (images separate)

Ask scale

Ask about scale. Fifty million daily users; at peak, five million documents are open and two million people are actively typing. Clients batch keystrokes roughly every two hundred milliseconds, so each typist sends about one operation a second: two million operations a second overall. At a hundred bytes each, raw op logs would be many terabytes a day, so we must compact them into snapshots. Crucially, a single document usually has only one to three editors.

Scale math
50M daily users
peak: 5M docs open, 2M users
  actively typing
client batches keystrokes
  every ~200 ms → ~1 op/s each
  → ~2M ops/s overall

op ≈ 100 B → 200 MB/s
  ≈ 17 TB/day raw at peak
  → compact into snapshots
per doc: usually 1 to 3 editors

Define API

Define the API. Over HTTP: create a document, load the latest snapshot together with its revision number, list history and manage sharing. Then the client opens a WebSocket for the document. It sends operations tagged with the revision they were based on and a client sequence number. The server acks with the new revision, and pushes other people's operations and cursor positions.

API
REST:
  POST /docs → { doc_id }
  GET  /docs/{id} → snapshot
        + revision
  GET  /docs/{id}/history
  POST /docs/{id}/share
WebSocket /docs/{id}/live:
  → op { base_rev, client_id,
         client_seq, ops[] }
  ← ack { client_seq, rev }
  ← remote op, cursor

Data model

The data model has three parts. Document metadata and access control lists in a relational database, where transactions and joins help. An append-only operation log keyed by document and revision number, in a wide-column store built for heavy appends. And snapshots: the full document at some revision, stored as blobs in object storage. The current document is the latest snapshot plus the operations after it.

Data model
docs (SQL): id, owner, title,
  latest_rev, snapshot_rev
acl: (doc_id, principal, role)

ops (wide-column, append):
  PK (doc_id, rev)
  client_id, client_seq,
  op payload, ts

snapshots (object storage):
  docs/{id}/rev-4200.bin

High-level design

Draw the high-level design, starting with opening a document. Editor A's connection lands on an edge gateway, which checks their permission in the metadata database, then asks the session registry which server owns document nine. If no server owns it yet, one takes a lease on it, loads the latest snapshot, replays the operations after it, and holds the document in memory. All editors of document nine connect through to that one server.

Now an edit. Editor A types, and the client sends insert s at position three, based on revision forty-one. The session server is the single place that orders operations for this document. It adjusts the operation if needed, assigns revision forty-two, appends it to the op log, acks A and broadcasts it to B and the other editors. The interesting word there is adjusts, and that is the first deep dive.

Deep dive: conflicts

Deep dive one: why concurrent edits conflict. The document says cat. Editor A appends s at position three, making cats. At the same time editor B inserts the, with a space, at position zero. If B's copy applies A's operation unchanged, it inserts s at position three of the cat and produces nonsense. Positions shift whenever someone else edited earlier in the text. Locking paragraphs or last-writer-wins would lose work, so we need an algorithm.

Why edits conflict
doc: "cat"  (both at rev 1)
A: insert "s" at 3  → cats
B: insert "the " at 0
        → the cat

apply both naively on B:
  "the cat" + "s" at 3
  → "thes cat"   ✗

positions shift when someone
else edits before them

Deep dive: OT

Deep dive two: operational transformation, or OT. The server orders operations. B's insert became revision two. A's arrives based on revision one, so the server transforms it against everything A had not seen: B inserted four characters before position three, so A's insert moves to position seven. Everyone ends with the cats. OT is mature and compact, and it relies on one central server ordering each document's operations, which we have.

Operational transformation
server order: B’s op is rev 2
A’s op arrives based on
  rev 1, so transform it:
  B inserted 4 chars at 0,
  before position 3
  → A’s op becomes
    insert "s" at 7
result everywhere: "the cats"

needs one central ordering
  point per document

Deep dive: CRDT

The alternative is a CRDT, a conflict-free replicated data type. Every character gets a unique ID, and an insert says after character X, not at position three, so positions never shift. Operations can merge in any order and all copies converge, which is excellent for long offline sessions and peer-to-peer. The cost is metadata per character and tombstones for deleted text. With a central server already in the design, OT is a fine choice; mention CRDTs as the offline-first option.

CRDTs: the alternative
  • every character gets a unique
  • id (site, counter)
  • insert = "after char id X",
  • not "at position 3"
  • merges commute: any order of
  • delivery → same result
  • + great for offline and
  • peer-to-peer
  • - metadata per character,
  • tombstones for deletes
  • choice here: OT + server

Deep dive: ownership

Deep dive three: ownership. OT needs exactly one server ordering each document at a time. A session server takes a lease on a document in etcd or ZooKeeper and renews it every few seconds; edge gateways route all connections for that document to the lease holder. Each server owns thousands of small documents. To be safe against a paused old owner, every op log write carries the lease epoch and the log rejects writes from an older one.

One owner per document
  • owner = holds a lease on the
  • doc in etcd or ZooKeeper,
  • renewed every few seconds
  • edges route doc_id → owner
  • docs are small and mostly
  • 1 to 3 editors: thousands
  • of docs per server
  • fencing: op log rejects
  • writes from an old lease
  • (lease epoch in each write)

Deep dive: durability

Deep dive four: durability. Never ack an operation until it is durably appended to the op log; batching appends every fifty milliseconds keeps that cheap. Every five hundred operations or so, or when the last editor leaves, write a snapshot. Loading a document is the latest snapshot plus the few operations after it. Old operations are compacted into history checkpoints a few minutes apart, so raw logs do not grow forever.

Log and snapshots
  • ack only after the op log
  • write is durable
  • (batch appends every ~50 ms)
  • snapshot every ~500 ops or
  • on close
  • load = snapshot + ops after
  • compact old ops into history
  • checkpoints (minutes apart)
  • → raw logs do not grow
  • forever

Deep dive: failover

Deep dive five: failover. If the session server dies, its lease expires within about ten seconds and another server takes over, rebuilding the document from the snapshot and the op log. Clients reconnect and resend any operations that were never acked. The server dedupes on client ID and client sequence, so an op that was logged but not acked is not applied twice. Since we ack only after logging, nothing acknowledged is lost.

Session server failure
  • lease expires (~10 s)
  • another server takes it,
  • loads snapshot + log
  • clients reconnect, resend
  • ops not yet acked
  • dedupe on (client_id,
  • client_seq)
  • acked ops are in the log,
  • so nothing acked is lost

Deep dive: offline

Deep dive six: offline editing. The client keeps editing its local copy and queues operations with the revision they were based on. On reconnect, it sends the queue, and the server transforms them against every operation since that revision before applying them. The client does the mirror image for operations it missed. For hours of heavy offline edits the merges get messy, which is exactly where CRDT-based editors shine.

Offline edits
  • client keeps editing locally,
  • queues ops with base_rev
  • on reconnect: send queue
  • server transforms them
  • against every op since
  • base_rev, then applies
  • client transforms its view
  • against the server’s ops
  • hours offline + heavy edits
  • → messy merges; CRDT shines

Deep dive: extras

Deep dive seven: the extras. Cursors and selections are ephemeral: never logged, throttled to about twenty updates a second, and broadcast only within the document's session. Version history is a list of pointers to snapshot revisions; restoring an old version is simply a new edit that replaces the content, so history is never rewritten. Comments are anchored to a range, and that range is transformed by every operation, just like a cursor.

Cursors, history, comments
  • cursors and selections:
  • ephemeral, never logged,
  • throttled to ~20/s, sent
  • only within the session
  • history: named versions =
  • pointers to snapshot revs;
  • restore = a new edit
  • comments: anchored to a
  • range that transforms with
  • ops like any position

Deep dive: access

Permissions. The edge checks the role at connect, and the session server caches it and checks each operation: viewers receive operations but cannot send them, commenters may only send comment operations. When an owner revokes access, the change is published as an event and the session drops that user's connection immediately, instead of waiting for them to reload. Link sharing uses a signed token carrying the granted role.

Permissions
  • checked at connect and on
  • every op (role cached in
  • the session)
  • viewer: receives ops, cannot
  • send them
  • commenter: comment ops only
  • access revoked → ACL change
  • event → session drops that
  • connection immediately
  • link sharing: signed token

Trade-offs

The trade-offs. One owner per document makes ordering simple, but caps a single document at what one server can handle, which is fine with a hundred editors. OT is compact and central; CRDTs are better offline but heavier. Failover costs a pause of several seconds. Monitor op ack latency, transform errors, lease churn, log write lag and snapshot age. At ten times the users, simply add session servers; documents spread across them by lease.

Trade-offs
  • single owner per doc: simple
  • ordering, but a hot doc is
  • capped by one server
  • (fine: 100 editors max)
  • OT vs CRDT: compact and
  • central vs offline-friendly
  • failover: ~10 s pause
  • monitor: op ack p99, transform
  • errors, lease churn, log
  • lag, snapshot age
  • 10x: more session servers

Interview summary

The summary. Editors connect over WebSockets to the single session server that owns the document, enforced with a lease and a fencing epoch. That server uses operational transformation to adjust concurrent edits and assigns each a revision. Operations are logged before they are acked, with snapshots every few hundred. On failover a new owner replays the log and clients resend unacked operations, deduped by sequence. Cursors are ephemeral. A CRDT is the offline-first alternative.

Interview summary
  • WebSocket to the one session
  • server that owns the doc
  • (lease, fencing epoch)
  • OT: transform ops against
  • concurrent ones, assign rev
  • log before ack; snapshots
  • every ~500 ops
  • failover: replay, clients
  • resend, dedupe by seq
  • cursors ephemeral, CRDT for
  • offline-first

Interview questions

Why can’t you just apply edits in the order they arrive?

Edits refer to positions in the version the author saw; if someone else inserted or deleted text earlier in the document first, those positions shift, so applying an edit unchanged corrupts the text.

What is operational transformation?

A technique where a central server orders operations and transforms each incoming operation against concurrent operations the author had not seen, adjusting positions so that all clients converge to the same document.

OT or CRDT?

OT is compact and works well with a central server per document; CRDTs give each element a unique ID so operations commute and merge in any order, which suits offline-first and peer-to-peer editing at the cost of more metadata.

Why route all editors of a document to one server?

OT needs a single authority to order operations and transform them consistently; a per-document lease with a fencing epoch ensures exactly one server orders and logs operations at a time.

How do you avoid losing edits if the server crashes?

Append operations to a durable log before acknowledging them; on failover the new owner loads the latest snapshot and replays the log, and clients resend unacknowledged operations, which are deduplicated by client sequence number.

How do you load a document quickly when it has a million edits?

Periodically write snapshots of the full document, so loading reads the latest snapshot and replays only the few operations after it, and compact old operations into coarser history checkpoints.

How do live cursors work?

Cursor and selection positions are sent as ephemeral, throttled messages within the document session, transformed like any position when edits arrive, and never written to the operation log.

How would you implement version history?

Keep named or periodic versions as pointers to snapshot revisions; restoring creates a new edit that replaces the content, so the history remains append-only.