Markdown Editor

Chat System Design Template (Reference Design)

Real-time 1:1 and group chat: WebSocket gateways, per-conversation ordering, fan-out, receipts, offline sync, push, E2EE

Cara menggunakan: Use when designing messaging. Change the user and message inputs in 3.1 first and recompute gateway count and storage. The main pitfall is ordering by client clocks; keep server-assigned per-conversation sequence numbers.

Pratinjau

System Design: [Chat system name]

Reference design for a real-time chat system with one-to-one and group conversations, multi-device delivery, presence, receipts, offline sync and push notifications. It follows the structure of the System Design Document template and is pre-filled with a worked design so you can adapt it rather than start from a blank page. Every value marked Example is an assumption: replace it with your own numbers and re-run the formulas.

Document control

FieldValue
Document ID[SDD-CHAT-001]
System / feature[Chat platform: 1:1 and group messaging]
Version[0.1]
Status[Draft / In review / Approved / Implemented / Superseded]
Author(s)[@author]
Reviewers[@reviewer-1], [@reviewer-2]
Last updatedYYYY-MM-DD
Related[PRD], [Threat model], [ADR: message store], [ADR: E2EE]

Approvals

RoleNameDecisionDate
Tech lead[@name][Approved / Changes requested]YYYY-MM-DD
Staff / principal engineer[@name][Decision]YYYY-MM-DD
SRE / operations[@name][Decision]YYYY-MM-DD
Security and privacy[@name][Decision]YYYY-MM-DD
Mobile client lead[@name][Decision]YYYY-MM-DD

Revision history

VersionDateAuthorChange summary
0.1YYYY-MM-DD[@name]First draft for design review
0.2YYYY-MM-DD[@name]Example: Replaced client timestamps with server-assigned per-conversation sequence numbers
0.3YYYY-MM-DD[@name]Example: Added large-group fan-out on read above 1 000 members
1.0YYYY-MM-DD[@name]Example: Approved

Contents

1. Overview

1.1 Problem statement

[What problem exists today. Example: Users coordinate through email and a third-party messenger. Messages are lost between devices, there is no shared history for teams, and we cannot meet data-residency commitments. We need a first-party chat service that works on web, iOS and Android, including when a device has been offline for days.]

1.2 Proposed solution

[One paragraph. Example: Clients hold one persistent WebSocket connection to a stateless gateway tier. A chat service assigns every message a monotonically increasing sequence number within its conversation, persists it to a wide-column store partitioned by conversation, and fans it out to the recipients' devices through a routing layer that knows which gateway holds each device's connection. Devices that are offline receive a push notification and catch up by asking for everything after the last sequence number they have seen. Presence and typing indicators are ephemeral and never touch durable storage.]

1.3 Decisions requested from reviewers

IDQuestionRecommended optionSection
Q-01Example: How are messages ordered?Server-assigned sequence per conversation7.2
Q-02Example: Fan-out on write for all groups?Write fan-out up to 1 000 members, read fan-out above7.3, 10
Q-03Example: Is end-to-end encryption in scope for v1?1:1 chats in v1, groups in v27.6
Q-04Example: Which message store?Wide-column store, partition by conversation and time bucket5.2, 10
[Q-nn][Question][Option][Section]

2. Requirements

2.1 Functional requirements

IDRequirementPriority
FR-01Example: Send and receive text messages in 1:1 conversationsMust
FR-02Example: Group conversations up to 1 000 members; broadcast channels up to 100 000 membersMust
FR-03Example: Deliver every message to all of a user's devices (up to 5)Must
FR-04Example: Show delivered and read receipts per message (per member in groups up to 100)Must
FR-05Example: Show online / last-seen presence and typing indicatorsShould
FR-06Example: Sync history after a device has been offline, in order, without gapsMust
FR-07Example: Push notification to offline devices within 5 sMust
FR-08Example: Attachments (images, files up to 100 MB) via object storageShould
FR-09Example: Edit and delete own messages within 24 hCould
[FR-nn][Requirement][Must / Should / Could]

2.2 Non-functional requirements

IDQualityTargetMeasured as
NFR-01LatencyExample: Send-to-receive p99 at or below 300 ms when both parties are online in the same regionClient-reported timing, sampled
NFR-02AvailabilityExample: 99.95% monthly for send and syncSynthetic send/receive probes
NFR-03DurabilityExample: An acknowledged message is never lostReplication RF=3, restore drills
NFR-04OrderingExample: All members see a conversation in the same orderSequence-gap detector on clients
NFR-05ScaleExample: 15 million concurrent connections at peakGateway connection gauge
NFR-06PrivacyExample: Message content encrypted in transit and at rest; E2EE for 1:1Security review
[NFR-nn][Quality][Target][Measurement]

2.3 Out of scope

  • [Example: voice and video calls (separate real-time media design)]
  • [Example: full-text server-side search of message content]
  • [Example: federation with external chat networks]
  • [Item deferred, with the release that will take it]

3. Capacity estimation

3.1 Assumptions

InputSymbolValueSource
Daily active usersUUExample: 50 millionGrowth plan
Messages sent per user per daymmExample: 40Comparable products
Average stored message (payload + metadata)ssExample: 300 bytesSchema in 5.2
Average delivered copies per message (recipients x devices)ddExample: 3Mix of 1:1 and small groups
Peak concurrent users as share of DAUccExample: 30%Traffic history
Connections per gateway nodeggExample: 100 000Load test of gateway
Peak to average factorkkExample: 3Evening peak
RetentionYYExample: 2 years hot, then coldProduct decision

3.2 Throughput

Messages per day and average send rate:

Mday=U×m=5×107×40=2×109 messages/dayM_{day} = U \times m = 5 \times 10^{7} \times 40 = 2 \times 10^{9} \ \text{messages/day}
QPSsend=2×10986 400≈23 000 messages/s,QPSpeak=k⋅QPSsend≈70 000 messages/sQPS_{send} = \frac{2 \times 10^{9}}{86\,400} \approx 23\,000 \ \text{messages/s}, \qquad QPS_{peak} = k \cdot QPS_{send} \approx 70\,000 \ \text{messages/s}

Deliveries (one per recipient device) at peak:

Dpeak=d×QPSpeak≈3×70 000=210 000 deliveries/sD_{peak} = d \times QPS_{peak} \approx 3 \times 70\,000 = 210\,000 \ \text{deliveries/s}

3.3 Connections and gateways

Cpeak=c×U=0.3×5×107=1.5×107 connectionsC_{peak} = c \times U = 0.3 \times 5 \times 10^{7} = 1.5 \times 10^{7} \ \text{connections}
Ngw=Cpeakg×1.5=1.5×107105×1.5=225 gateway nodesN_{gw} = \frac{C_{peak}}{g} \times 1.5 = \frac{1.5 \times 10^{7}}{10^{5}} \times 1.5 = 225 \ \text{gateway nodes}

The factor 1.5 keeps headroom for losing one availability zone of three without reconnect storms overloading the survivors.

3.4 Storage

Sday=Mday×s=2×109×300 B=600 GB/dayS_{day} = M_{day} \times s = 2 \times 10^{9} \times 300 \ \text{B} = 600 \ \text{GB/day}
S2y=600 GB×730≈438 TB,with RF=3:≈1.3 PB rawS_{2y} = 600 \ \text{GB} \times 730 \approx 438 \ \text{TB}, \qquad \text{with RF} = 3: \approx 1.3 \ \text{PB raw}

Attachments are stored separately in object storage and are usually larger than the message store; estimate them from your media mix.

3.5 Bandwidth

Bout≈Dpeak×500 B (framed, encrypted)≈105 MB/s≈840 Mbit/sB_{out} \approx D_{peak} \times 500 \ \text{B (framed, encrypted)} \approx 105 \ \text{MB/s} \approx 840 \ \text{Mbit/s}

Heartbeats every 30 s add 1.5×107/30=500 0001.5 \times 10^{7} / 30 = 500\,000 small frames per second, terminated at the gateway and never forwarded.

3.6 Summary

QuantityEstimateDesign consequence
Send rate~23 000/s average, ~70 000/s peakPartitioned chat service and log
Deliveries~210 000/s peakRouting layer must be in memory, not a database lookup per message
Connections15 million peak~225 gateway nodes across 3 zones
Storage~600 GB/day, ~1.3 PB raw for 2 yearsWide-column store with time buckets; tiering after 2 years
[Quantity][Estimate][Consequence]

4. API design

4.1 Endpoints and frames

Real-time traffic uses WebSocket frames; history, conversation management and attachments use HTTPS.

ChannelOperationPurposeIdempotent
WebSocketsendSend a message; carries a client-generated clientMsgIdYes, deduplicated by clientMsgId
WebSocketmsg (server to client)Deliver a message with conversationId and seqClient deduplicates by seq
WebSocketackDevice confirms receipt up to a sequence numberYes
WebSocketreadUser marks a conversation read up to seqYes, monotonic
WebSockettyping, presenceEphemeral signalsNot stored
HTTPS GET/v1/conversations/{id}/messages?after={seq}&limit=200Catch-up sync and historyYes
HTTPS POST/v1/conversationsCreate a 1:1 or group conversationWith Idempotency-Key
HTTPS POST/v1/attachmentsGet a pre-signed upload URLWith Idempotency-Key

4.2 Frame examples

A send frame from the client, the acknowledgement to the sender, and the delivery frame each recipient device receives:

{"type": "send", "clientMsgId": "01J9ZK7Q4V3M2N8R5T6Y", "conversationId": "c_8f2a", "body": {"text": "Running 5 minutes late"}}
{"type": "sent", "clientMsgId": "01J9ZK7Q4V3M2N8R5T6Y", "conversationId": "c_8f2a", "seq": 18342, "serverTs": "2026-03-14T18:02:11.412Z"}
{"type": "msg", "conversationId": "c_8f2a", "seq": 18342, "sender": "u_19", "body": {"text": "Running 5 minutes late"}, "serverTs": "2026-03-14T18:02:11.412Z"}

4.3 Errors, limits and versioning

CodeMeaningClient action
not_memberSender is not in the conversationRemove conversation locally
rate_limitedToo many sends; includes retryAfterMsBack off and retry
too_largeBody over 64 KBUse an attachment
resync_requiredServer cannot replay the requested range from cacheCall the history endpoint
  • Rate limit: [Example: 20 messages per second per device, 600 per minute per user].
  • Versioning: protocol version is negotiated in the WebSocket subprotocol header (chat.v1); the server supports the current and previous versions.

5. Data model

5.1 Entities

The core entities are conversations, their members, messages and per-device cursors that record how far each device has synced.

5.2 Storage choices

DataStorePartition keyMain queriesWhy
MessagesExample: Wide-column store (Cassandra / ScyllaDB class)(conversation_id, bucket), clustered by seq descLatest N; range after seqAppend-heavy, range reads within one partition
Conversations, membershipsExample: Relational database, sharded by conversation_idconversation_idMembers of a conversation; last_seq counterTransactional membership changes
User inbox indexExample: Wide-column storeuser_id, clustered by last activityConversation list with unread countsOne read renders the inbox
Device cursorsExample: Key-value storedevice_idDelivered position per conversationSmall, very frequent writes
Presence, routingExample: In-memory store with TTLuser_id, device_idWhich gateway holds this deviceEphemeral; lost data rebuilds on reconnect
AttachmentsObject storage + CDNContent hashGet by URLLarge blobs out of the message path

bucket is the number of whole 10-day periods since the conversation was created. It caps partition size for very active groups (target below 100 MB per partition).

6. High-level design

Clients connect to gateways through a layer-4 load balancer; everything behind the gateways is stateless except the stores and the log.

ComponentResponsibilityScales by
WebSocket gatewayTerminate TLS and WebSocket, authenticate, heartbeats, hold device sessionsConnections per node
Chat serviceValidate membership, deduplicate, assign seq, persist, publish to logStateless replicas, partitioned by conversation
Message logOrdered, durable hand-off from write path to fan-outPartitions (by conversation_id)
Fan-out workersExpand recipients and devices, route to gateways or pushConsumer group size
Presence serviceTrack online state and last seen; publish changes to subscribersSharded by user_id
Push serviceBatch and send notifications through platform providersStateless replicas
[Component][Responsibility][Scaling unit]

7. Detailed design

7.1 Send path (critical path)

The sender gets its sent acknowledgement as soon as the message is durable; delivery to recipients happens asynchronously from the log.

Steps that fail before the store write return an error and the client retries with the same clientMsgId. If the service crashes after the store write but before replying, the retry finds the existing clientMsgId in a short-lived deduplication index (24 h TTL) and returns the original seq.

7.2 Ordering and consistency model

  • Order is per conversation. The chat service increments conversation.last_seq with a single-row atomic update in the shard that owns the conversation. Every member sees messages in seq order, regardless of client clocks or which gateway delivered them.
  • Gaps are detectable. A client that receives seq 18344 after 18342 knows 18343 is missing and requests after=18342. This makes delivery at-least-once on the wire and exactly-once in the client view.
  • Read-your-writes for the sender: the sender shows the message immediately (optimistic, keyed by clientMsgId) and replaces it with the server copy when sent arrives.
  • Eventual consistency elsewhere: inbox order, unread counts, presence and receipts may lag by seconds. They are derived views and can be rebuilt from the message store and memberships.
  • Hot conversations (for example a broadcast channel) serialize on one counter row. At Example: 2 000 sends per second per row this is sufficient; above it, batch sequence allocation in the chat service leader for that conversation.

7.3 Fan-out

For groups up to Example: 1 000 members, fan-out workers push each message to every member's online devices (fan-out on write). For broadcast channels above that size, the worker sends only a lightweight "channel has new messages up to seq" signal to online subscribers, and clients pull the content (fan-out on read). This bounds the worst case: one post to a 100 000 member channel produces 100 000 small signals instead of 300 000 full payloads, and offline members cost nothing until they open the channel.

7.4 Delivery and read receipts

ReceiptProduced whenStored asShown to
SentMessage is durableseq returned to senderSender
DeliveredAny recipient device acks the seqdevice_cursor.delivered_seq (max)Sender, aggregated per member
ReadRecipient opens conversation up to seqmembership.read_seq (monotonic max)Sender; per member in groups up to 100 members, count only above that

Receipts are watermarks, not per-message rows: "read up to 18342" covers every earlier message. This keeps receipt writes proportional to activity, not to message count.

7.5 Offline sync and push

On reconnect, the device sends its delivered_seq per conversation (or a single opaque sync token). The server returns conversations whose last_seq is higher, and the client pages through GET .../messages?after= for each. Push notifications are hints only: the payload carries the conversation and sender name (or nothing, for E2EE), never the authoritative message, so a lost or duplicated push never corrupts history. Push sends are collapsed per conversation (one notification with a count) and respect the user's mute settings.

7.6 End-to-end encryption note

For 1:1 chats in v1, clients use an established double-ratchet protocol with per-device identity keys published to a key directory. The server then stores and routes ciphertext only. Consequences that must be accepted explicitly: server-side search, link previews and content moderation are not possible on encrypted content; multi-device requires encrypting to every device key; and history on a new device needs a client-side backup or device-to-device transfer. Group E2EE (sender keys or the MLS protocol) is a v2 decision.

8. Scalability and performance

ConcernApproachLimit before redesign
ConnectionsHorizontal gateways; connection-count based load balancingExample: ~150 000 connections per node before GC pauses rise
Send throughputChat service partitioned by conversation_id; message log partitionsExample: ~300 000 sends/s with 256 log partitions
Hot conversationSingle counter row; batch allocation above 2 000/sOne conversation at ~20 000 sends/s
Reconnect stormJittered reconnect (1-30 s) and token-bucket admission at gatewaysFull-zone failover of 5 million clients
Storage growthTime-bucketed partitions; move buckets older than 2 years to cold storage[Limit]

Latency budget for send-to-receive, both users online (target p99 300 ms):

StepBudget
Client to gateway (mobile network, one way)80 ms
Gateway to chat service, membership check and seq15 ms
Message store write (quorum)20 ms
Log publish and fan-out consume30 ms
Routing lookup and gateway delivery15 ms
Gateway to recipient (one way)80 ms
Headroom60 ms

9. Reliability, security and operations

9.1 Failure modes

FailureImpactDetectionMitigation
Gateway node dies~100 000 devices disconnectConnection gauge dropClients reconnect with jitter to other nodes; routing entries expire by TTL
Routing cache staleDelivery sent to wrong gateway and droppedDelivery ack rateClient gap detection triggers sync; routing entries refreshed on every heartbeat
Message log lagDelivery delayed, sends still succeedConsumer lag secondsAutoscale fan-out workers; alert at 10 s lag
Message store write timeoutSend fails, client retriesWrite error rateIdempotent retry via clientMsgId; quorum across 3 zones
Push provider outageOffline users not notifiedProvider error rateQueue and retry with backoff; messages still sync on open
Region outageUsers homed in region cannot sendSynthetic probesExample: Conversations homed in one region; warm standby promoted in under 15 min (RPO under 1 min)
[Failure][Impact][Signal][Mitigation]

9.2 Security and abuse

ThreatControl
Session hijackingShort-lived access tokens on WebSocket upgrade; re-authenticate on token expiry
Sending to conversations the user leftMembership checked on every send and every history read
Spam and abusePer-user and per-device rate limits; new-account limits; user reporting (with E2EE, reported messages are forwarded by the reporter's client)
Metadata exposureMinimise retained metadata; delete push tokens on logout; encrypt at rest
Malicious attachmentsContent-type allowlist, size limits, malware scan before the download URL is issued
[Threat][Control]

9.3 Observability

SignalMetric or logAlert threshold
Send latencyp50, p99 from send to sentp99 above 150 ms for 10 min
End-to-end deliverySampled send-to-ack latencyp99 above 300 ms for 10 min
Undelivered backlogMessages older than 60 s without delivered receipt to any online deviceAbove 0.1%
Fan-out lagLog consumer lagAbove 10 s
ConnectionsPer node and per zoneNode above 130 000
Client gap rateSync requests triggered by gapsAbove 1% of sessions

9.4 Cost estimate

ItemUnit costQuantityMonthly cost
Gateway nodes[$/node-month]Example: 225[$]
Chat and fan-out services[$/node-month][n][$]
Message store[$/TB-month]Example: ~55 TB raw added per month[$]
Message log[$/partition-month]Example: 256 partitions[$]
Push provider and SMS fallback[$/1 000 sends][n][$]
Total[$]

10. Trade-offs and alternatives

DecisionOptionsChosenWhyWhat we give up
TransportLong polling; Server-Sent Events plus POST; WebSocketWebSocketBidirectional, low overhead per frame, mature on all platformsStateful gateways and reconnect handling
OrderingClient timestamps; global sequence; per-conversation sequencePer-conversation sequenceTotal order where users notice it, no global bottleneckNo order across conversations
Message storeRelational; document store; wide-columnWide-columnLinear write scaling, efficient range reads per partitionLimited secondary indexes and transactions
Fan-outAlways on write; always on read; hybridHybrid at 1 000 membersLow latency for normal groups, bounded cost for huge channelsTwo code paths
ReceiptsPer-message rows; watermarksWatermarksWrites scale with activity, not messagesCannot show "read" on one older message independently
EncryptionTLS only; E2EE for 1:1; E2EE for allE2EE for 1:1 in v1Strong privacy for most sensitive chats with manageable scopeNo server search or moderation on 1:1 content
[Decision][Options][Chosen][Why][Cost]

11. Delivery plan and risks

11.1 Milestones

MilestoneScopeExit criterionTarget date
M11:1 chat, single region, web clientLoad test at 2x peak sends, zero lost messagesYYYY-MM-DD
M2Groups, receipts, mobile pushGap rate below 0.1% for 14 days in betaYYYY-MM-DD
M3Offline sync at scale, attachments7-day offline device syncs in under 10 sYYYY-MM-DD
M4E2EE for 1:1External cryptography review passedYYYY-MM-DD

11.2 Rollout and rollback

[Example: Ship behind a per-user flag. Start with internal staff, then 1%, 10%, 50% and 100% of users, holding each step for at least 48 h while watching send latency, gap rate and undelivered backlog. Rollback disables the flag; messages already written stay readable through the history API.]

11.3 Risks

IDRiskLikelihoodImpactMitigationOwner
R-01Example: Reconnect storm after a gateway deploy overloads authMediumHighRolling deploys with connection draining; jittered reconnect[@name]
R-02Example: Hot broadcast channel saturates one shardMediumMediumHybrid fan-out; batch sequence allocation[@name]
R-03Example: Push provider throttles burstsMediumMediumCollapse keys, per-app send rate control[@name]
[R-nn][Risk][L / M / H][L / M / H][Mitigation][@name]

12. Appendices

12.1 Open questions

IDQuestionOwnerDue
OQ-01Example: Do we keep history forever for paid workspaces?[@name]YYYY-MM-DD
OQ-02Example: MLS or sender keys for group E2EE?[@name]YYYY-MM-DD
[OQ-nn][Question][@name]YYYY-MM-DD

12.2 Glossary

TermMeaning
seqServer-assigned, gap-free, increasing number of a message within its conversation
Fan-outCopying or signalling one message to every recipient device
Watermark"Everything up to this seq" marker used for receipts and sync
PresenceOnline or last-seen state of a user
E2EEEnd-to-end encryption: only endpoints hold the keys
[Term][Meaning]

12.3 References

ReferenceUse
RFC 6455 The WebSocket ProtocolTransport in 4.1
RFC 9420 The Messaging Layer Security (MLS) ProtocolCandidate for group E2EE
Apple Push Notification service and Firebase Cloud Messaging documentationPush integration in 7.5
[Internal ADR or document][Use]

Standard and licence note

Based on common industry practice for real-time messaging system design and on the protocols cited in 12.3. This is a reference design to adapt: the numbers are illustrative assumptions, not measurements. Original text written for this collection. Template licence: MIT.

Templat terkait