Published on

Co nieco o System Designie - projektujemy komunikator (część 1)

Authors
  • avatar
    Name
    Piotr Kołodziejczyk
    Twitter
Co nieco o System Designie - projektujemy komunikator (część 1)

System Design to jedna z tych dziedzin, gdzie liczy się nie tylko znajomość technologii, ale też zdolność myślenia o skali. Jak to jest, że WhatsApp obsługuje 100 miliardów wiadomości dziennie przy zespole liczącym zaledwie kilkadziesiąt inżynierów? Zacznijmy od podstaw - wymagań i obliczeń.

Wymagania funkcjonalne

Zanim cokolwiek narysujemy, ustalamy co system ma robić. Dla komunikatora P2P:

  • Wysyłanie i odbieranie wiadomości tekstowych między dwoma użytkownikami
  • Wiadomości powinny docierać w czasie rzeczywistym (sub-sekundowym)
  • Obsługa multimediów: zdjęcia, filmy, dokumenty
  • Statusy dostarczenia: wysłano, dostarczono, odczytano (✓, ✓✓, niebieskie ✓✓)
  • Powiadomienia push gdy użytkownik jest offline
  • Historia wiadomości - możliwość załadowania poprzednich konwersacji
  • Wskaźnik "pisze..." (typing indicator)

Co nie wchodzi w zakres tej wersji: grupy, połączenia głosowe/wideo, enkrypcja end-to-end (to osobne, złożone tematy).

Wymagania niefunkcjonalne

Tu zaczyna się prawdziwy system design. Wymagania niefunkcjonalne definiują jak system ma działać, nie co ma robić:

  • Dostępność: 99.99% uptime - maksymalnie ~52 minuty przestoju rocznie
  • Latencja: wiadomość P2P powinna dotrzeć poniżej 200ms (P99) gdy obie strony są online
  • Trwałość danych: wiadomości nie mogą znikać - zero utraty danych
  • Spójność: użytkownik może zobaczyć wiadomości w nieco innej kolejności na różnych urządzeniach (eventual consistency), ale nie może stracić żadnej
  • Skalowalność: system musi działać przy 10x wzroście bez przeprojektowania

Szacowanie skali - back-of-the-envelope

To serce pierwszego etapu system designu. Bez liczb nie wiadomo jakich komponentów potrzeba i ile ich potrzeba.

Założenia

Użytkownicy zarejestrowani:    500 milionów
DAU (Daily Active Users):      100 milionów
Wiadomości/użytkownik/dzień:   40
Średni rozmiar wiadomości:     100 bajtów (tekst)
Odsetek wiadomości z mediami:  20%
Średni rozmiar zdjęcia:        300 KB (po kompresji)

Przepustowość wiadomości (QPS)

Wiadomości/dzień = 100M użytkowników × 40 wiadomości = 4 miliardy wiadomości/dzień

Średnie QPS = 4 000 000 000 / 86 400 s ≈ 46 300 wiadomości/sekundę

Peak QPS (zakładamy 2× średniej)92 600 wiadomości/sekundę

To jest liczba, która podbija wszystkie nasze decyzje architektoniczne. Żaden klasyczny monolityczny serwer tego nie obsłuży.

Storage - wiadomości tekstowe

Wiadomości/dzień:         4 000 000 000
Rozmiar wiadomości:       ~100 bajtów
                          + metadata (ID, timestamp, sender, receiver, status): ~50 bajtów
Łącznie na wiadomość:     ~150 bajtów

Dane/dzień = 4B × 150 B = 600 GB/dzień

Retencja 5 lat = 600 GB × 365 × 51 095 TB1.1 PB

Storage - media (zdjęcia)

Użytkownicy wysyłający media/dzień: 100M × 20% = 20 milionów
Średni rozmiar zdjęcia:             300 KB

Dane mediów/dzień = 20M × 300 KB = 6 TB/dzień

Retencja 5 lat = 6 TB × 365 × 510.95 PB11 PB

Bandwidth

Ruch przychodzący (upload):
  - Tekst: 600 GB / 86 400 s ≈ 56 Mb/s
  - Media: 6 TB / 86 400 s ≈ 556 Mb/s
  Łącznie: ~612 Mb/s

Ruch wychodzący (download):
  - Każdą wiadomość czyta co najmniej 1 odbiorca
  - Podobna skala co upload, ~612 Mb/s baseline
  - Peak (pobieranie historii, CDN) ~3-5 Gb/s

Podsumowanie szacunków

MetrykaWartość
DAU100M
QPS (średnie)~46 300 msg/s
QPS (peak)~92 600 msg/s
Storage wiadomości/rok~219 TB
Storage mediów/rok~2.2 PB
Bandwidth łącznie~600 Mb/s - 5 Gb/s

Wysokopoziomowe komponenty - przegląd AWS

Mając liczby, możemy dobierać technologie. Celujemy w AWS jako provider.

Warstwa połączeń klientów

Amazon API Gateway (WebSocket) albo Application Load Balancer (ALB) z obsługą WebSocket - to punkt wejścia dla każdego klienta mobilnego i webowego. WebSocket jest kluczowy dla real-time: utrzymuje trwałe połączenie TCP zamiast odpytywać co N sekund.

Serwery czatu

Amazon ECS (Fargate) - kontenery z logiką serwera czatu. Każdy kontener obsługuje dziesiątki tysięcy połączeń WebSocket jednocześnie. Przy ~92 600 QPS i zakładając 10 000 aktywnych połączeń na instancję potrzebujemy około 10 instancji przy peaku (z redundancją - 20+).

Alternatywa: EC2 z Auto Scaling Group dla większej kontroli nad parametrami sieci.

Warstwa przechowywania wiadomości

Amazon DynamoDB - baza NoSQL z rekordowo niską latencją. Dla wiadomości czatu DynamoDB to naturalne rozwiązanie:

  • Skaluje do milionów TPS bez konfiguracji
  • Single-digit millisecond latency
  • Automatyczne partycjonowanie
  • Point-in-time recovery (wymaganie trwałości danych)

Klucz partycji: conversation_id, klucz sortowania: message_timestamp (lub UUID z komponentem czasowym jak ULID). Przy takim schemacie załadowanie historii konkretnej rozmowy to jeden, wydajny zapytanie zakresu.

Warstwa cache

Amazon ElastiCache (Redis) - odpowiada za:

  • Mapowanie user_id → adres serwera WebSocket (kto gdzie jest podłączony)
  • Statusy online/offline użytkowników
  • Sesje i tokeny autoryzacji
  • Rate limiting (ochrona przed spamem)
  • Typing indicators (wygasają po 5s - idealny use case dla TTL w Redis)

Przechowywanie mediów

Amazon S3 - obiektowy storage dla wszystkich mediów. 11 PB na 5 lat to nic dla S3. Klienci uploadują bezpośrednio przez presigned URL (bez przechodzenia przez serwery czatu), co drastycznie odciąża backend.

Amazon CloudFront - CDN przed S3. Zdjęcia wysyłane wielokrotnie (forward, share) są serwowane z edge location najbliższej użytkownika, nie z S3 za każdym razem.

Powiadomienia push

Amazon SNS jako router powiadomień:

  • SNS → FCM (Firebase Cloud Messaging) dla Android
  • SNS → APNs (Apple Push Notification service) dla iOS
  • SNS → Web Push dla przeglądarek

Gdy odbiorca jest offline, serwer czatu zamiast próbować wysłać przez WebSocket (który nie istnieje) wrzuca zadanie powiadomienia do SNS.

Kolejkowanie asynchroniczne

Amazon SQS dla operacji które nie muszą być synchroniczne:

  • Zmiana rozmiaru i kompresja zdjęć po uploadzie
  • Wysyłka powiadomień push (bufor przed SNS)
  • Generowanie miniatur wideo
  • Archiwizacja starych wiadomości do S3 (cold storage)

Ogólny zarys architektury

                    ┌─────────────────────────────────────────────┐
AWS Cloud                    │                                             │
 Klienci           │  ┌─────┐     ┌──────────────────────────┐  
 (mobile/web) ─────┼─▶│ ALB │────▶│     Chat Servers (ECS)   │  │
                    │  │ WSS │     │  [A] [B] [C] [D] ...     │  │
                    │  └─────┘     └──────┬───────────┬────────┘  │
                    │                     │           │            │
                    │              ┌──────▼──┐  ┌────▼──────────┐ │
                    │              │  Redis  │  │   DynamoDB    │ │
(ElastiC)  (wiadomości) │ │
                    │              └─────────┘  └───────────────┘ │
                    │                     │                        │
                    │              ┌──────▼──────┐                 │
                    │              │  SQS + SNS  │────▶ FCM/APNs                    │              └─────────────┘                 │
                    │                                             │
S3 ◀── presigned URL ── klient            │
CloudFront ──▶ klient (pobieranie mediów)                    └─────────────────────────────────────────────┘

Co dalej?

Mamy wymagania, mamy liczby, mamy listę komponentów. Ale jak one ze sobą rozmawiają? Jak wiadomość od Ani trafia do Bartka, jeśli obaj są podłączeni do różnych serwerów czatu? Co gdy Bartek jest offline? Jak upload zdjęcia wygląda krok po kroku?

Tego wszystkiego - razem z konkretnymi przepływami danych, schematem DynamoDB i tym jak Redis trzyma mapowanie połączeń - dotyczy część druga.