See It Flow
Messaging Platform · 16 architecture decisions

Trade-offs & Decisions

These are real alternatives, not a restatement of the chosen design. Each decision explains when another answer is defensible and which product or operational constraint would make you switch.

Mangesh MandlikLast updated:
Decision 01

WebSocket vs SSE vs Long Polling

Realtime chat is bidirectional, but not every product with updates needs the full operational cost of WebSocket.

WebSocket

Best fit for frequent bidirectional messaging, receipts, typing, and low-latency server push over one persistent connection.

SSE

Useful when updates are mostly server-to-client and the client can send separately over ordinary HTTP. Simpler than WebSocket in some environments, but still two channels for chat.

Long polling

Broad compatibility and simple semantics, but higher request overhead and worse latency under heavy chat workloads.

Key takeaways
  • Use WebSocket when bidirectional low-latency messaging is central.
  • SSE and long polling stay viable for lower-frequency or mostly one-way products.
Decision 02

Gateway Tier vs Combined Messaging Service

You can combine connection handling and message logic at small scale, but the two workloads scale differently.

A combined service is simpler early: one deployment, fewer hops, fewer moving parts. It is completely reasonable for modest concurrency.

A separate gateway tier becomes attractive when persistent connection count, TLS termination, draining behavior, and connection churn need independent tuning from message validation, authorization, and storage throughput.

Key takeaways
  • Combine for simplicity when scale is low.
  • Split when connection-plane and message-plane scaling or failure isolation diverge strongly.
Decision 03

Persist Before Deliver

Push-first feels lower latency, but it makes ACK meaning ambiguous and weakens reliability.

Durable-first says Alice gets a positive ACK only after the write contract succeeds. Delivery to Bob may still happen immediately afterward, but the sender-facing promise is crisp.

Push-first can look fast in demos, but a crash between transient push and durable write turns a UI success into silent loss. Reliable messaging products almost always choose durable-first for accepted messages.

Key takeaways
  • Latency saved by push-first is rarely worth the correctness ambiguity.
  • Durable-first keeps sender ACK semantics understandable.
Decision 04

Client-Generated ID vs Server-Only ID

Retries happen before the client knows any server-assigned identifier, which is why many messaging systems use both IDs.

Client-generated request IDs make retries safe because the first and second attempt still name the same logical send. Server-generated message IDs remain useful as canonical durable identities for storage, sync, and downstream references.

Key takeaways
  • Use client IDs for idempotency, server IDs for canonical storage identity.
Decision 05

Strict Conversation Sequence vs Time-Sortable IDs

The more you want strict contiguous order, the more serialization or ownership correctness you usually need.

Strict sequence numbers make gaps explicit, history reads simple, and conversation order deterministic. Time-sortable IDs reduce coordination and may be sufficient where approximate order is acceptable.

Strict Ordering Across DevicesPer-conversation sequence numbers keep retries idempotent and sync explicit.SenderGatewayWebSocketMessaging Serviceassigns seqMessage Storeconv_42 → seq 101..103client_message_idseq + message_idA: Phonecursor 101A: Laptopcursor 100A: Tabletcursor 103gap detected → request seq 101..102duplicate seq 103 = no-op
Strict per-conversation sequence numbers let every device advance a contiguous cursor, detect gaps immediately, and treat retried deliveries with the same stable message identity as harmless duplicates.

The right choice depends on the product promise. A consumer chat app often benefits from strict per-conversation ordering; a looser social inbox may accept approximate timestamp order.

Key takeaways
  • Do not pay sequencing cost unless the product benefits from the stronger guarantee.
  • Approximate order and strict order are different contracts.
Decision 06

Conversation Log vs Per-User Inbox

Conversation logs minimize duplication; inboxes can simplify user-centric fetches. Hybrids are common because messaging needs both perspectives.

Conversation logs store once and naturally preserve order. Per-user or per-device inboxes accelerate unread and delivery bookkeeping but amplify group writes. A hybrid log-plus-sync-index model often balances cost and ergonomics.

Key takeaways
  • Say which store is source of truth.
  • A hybrid is often better than ideological purity.
Decision 07

Sync Cursor vs Per-Conversation Polling

Scanning every conversation can work for tiny products, but reconnect at scale usually wants a durable cursor or change index.

A sync cursor is efficient when one device may have hundreds of conversations and only a few changed. Per-conversation polling stays useful for local history reads and targeted gap repair.

Key takeaways
  • Cursor-based reconnect is the scalable default.
  • Conversation polling still has a place as a narrower history primitive.
Decision 08

Write Fan-Out vs Read Fan-Out vs Hybrid

Group architecture depends on size, activity, online ratio, receipts, and storage budget; no single threshold is universally right.

Write fan-out helps readers and notifications but amplifies writes. Read fan-out keeps writes cheap but shifts cost to reads and caching. Hybrids let the product use one strategy for small interactive groups and another for huge audiences.

Read deeper: Group Message Fan-Out.

Worked example (illustrative assumptions)

Illustrative assumptions, not measurements: write fan-out costs one delivery record per member per message; a fan-out worker pool sustains 50,000 such writes per second; at any moment 50% of a 200-member friends group is online, but only 5% of a 1M-member channel is actively reading.

AudienceWrite fan-outRead fan-outWhere the switch happens
200-member group200 writes, about 4 ms of pool capacity1 write plus about 100 readsWrite fan-out is cheap enough that its read-side simplicity, receipts and offline queues win
1,000,000-member channel1,000,000 writes, about 20 s of the whole pool for one message1 write plus about 50,000 readsWrite fan-out cost grows linearly with audience; read or hybrid fan-out wins
One message, two audiences — formulas: write fan-out = N writes ÷ 50,000 per second; read fan-out = 1 write plus online fraction × N reads

The crossover lies between these two audience sizes, and where exactly depends on how much per-message write budget you are willing to spend, how many members are typically online, and what receipts the product promises. That is why the page recommends a tunable policy rather than one hard-coded member count.

Key takeaways
  • Avoid pretending one hard-coded member count is globally correct.
  • Separate interactive groups from broadcast-like audiences.
Decision 09

Synchronous vs Asynchronous Group Fan-Out

Inline fan-out can be simpler for small groups; asynchronous workers protect sender latency as group cost grows.

Sync fan-out gives immediate completion semantics for a small recipient set. Async fan-out decouples sender ACK from thousands of downstream deliveries, but then operational lag in the worker path becomes user-visible.

Key takeaways
  • Sender latency should rarely scale linearly with group size.
  • Async fan-out is a latency-control tool, not a correctness downgrade.
Decision 10

Presence Accuracy vs Cost

Presence can be exact-ish and expensive, or approximate and cheap; messaging products usually choose approximate within a bounded staleness window.

Aggressive heartbeats and broad subscriptions make status fresher but increase battery, fan-out, and infrastructure cost. TTL-based leases plus selective subscriptions make presence cheaper and still adequate for user expectations.

Key takeaways
  • Presence is a hint; optimize for cost-aware freshness, not perfect certainty.
Decision 11

Delivery Receipt Granularity

Per-message, per-device receipts are precise but can explode in cardinality. Aggregation trades detail for scale.

A product can track delivery per device internally while exposing a simpler per-user aggregate. Read receipts may use high-watermarks rather than one event per message. Very large groups may suppress detail further.

Key takeaways
  • Granularity is a product decision with direct infrastructure cost.
  • Monotonic aggregates are often easier to scale than full per-message state.
Decision 12

SQL vs Wide-Column / Distributed KV for Messages

There is no universal rule that messaging storage must be Cassandra. Workload size and required semantics decide.

At smaller scale, relational storage can be entirely reasonable and may simplify transactional logic. At very high append rates, wide-column or distributed KV designs often fit better because partition-local ordered appends are the dominant pattern.

Key takeaways
  • Choose storage from actual write rate, read pattern, and transactional needs—not brand mythology.
Decision 13

Conversation Partitioning Strategy

Partitioning by conversation alone is simple, but long-lived or hot conversations often force a bucketing strategy.

Conversation-only partitions minimize read complexity until growth or hotspot behavior becomes unacceptable. Conversation plus time or sequence buckets bound physical size and repair cost but add pagination logic.

Key takeaways
  • Partition design is a physical-storage decision underneath a logical conversation model.
Decision 14

Kafka on the Critical Path vs Async Side Path

A durable log can be the message path, or it can be the side path. The trade-off is between explicit log semantics and lower end-to-end dependency count on every send.

Putting Kafka on the critical path can simplify ordered append and replay semantics, but it also means Kafka availability directly gates accepted sends. Keeping it on the async side path preserves a shorter synchronous chain but requires a separate durable handoff for downstream async work.

Key takeaways
  • Be explicit about whether Kafka failure blocks accepted messages or only auxiliary features.
Decision 15

Single-Region Write Authority vs Multi-Region Active-Active

Active-active writes reduce local latency everywhere, but conversation ordering and partition behavior become much harder.

Single-region or home-region write authority keeps ordering simple and split-brain risk lower. Active-active can improve local write latency for geographically distributed participants, but only if you also define strong ownership transfer or conflict semantics.

Key takeaways
  • If you cannot explain ordering during partition, active-active is not yet a finished design.
Decision 16

Server-Readable Content vs End-to-End Encryption

E2EE improves privacy and changes what the platform can inspect, search, moderate, or back up.

Server-readable content enables richer server-side search, indexing, moderation, and analytics on content itself. E2EE constrains those capabilities and pushes more intelligence and key state to clients, especially for multi-device and group history behavior.

Key takeaways
  • This is a product and trust decision, not just a crypto checkbox.