Zatím vždycky platilo: klient se zeptá, server odpoví. Ale co chat, kde zpráva „naskočí" sama? Co živé skóre, notifikace nebo „uživatel právě píše…"? Tam potřebuješ, aby server poslal data klientovi sám, ve chvíli, kdy se něco stane — bez toho, aby se klient ptal. Tahle kapitola je o tom, jak na to.
Pár pojmů na úvod
- Realtime = data se k uživateli dostanou (skoro) okamžitě, jakmile vzniknou.
- Push = server pošle data klientovi sám od sebe. Pull = klient si o ně musí říct.
- Spojení (connection) = otevřená „linka" mezi klientem a serverem, kterou můžou data téct.
- WebSocket = trvale otevřené obousměrné spojení (k tomu se dostaneme).
Problém: HTTP je jednosměrné
Z Foundations víš, že HTTP funguje na principu request → response. Server sám od sebe klientovi nic poslat nemůže — musí počkat, až se klient zeptá. Jenže u chatu nebo notifikací chceš opak: server má novinku a chce ji klientovi dotlačit hned. Jak to obejít? Existují čtyři přístupy, od nejprimitivnějšího po nejlepší:
| Technika | Jak funguje | Kdy ji použít |
|---|---|---|
| Polling | Klient se každých pár sekund ptá „něco nového?" | Nejjednodušší, ale plýtvá; stačí na málo časté updaty |
| Long-polling | Klient se zeptá a server drží odpověď, dokud nemá novinku | Když nemůžeš WebSockety, slušný kompromis |
| SSE (Server-Sent Events) | Jedno trvalé spojení, server jednosměrně posílá proud událostí | Server → klient (notifikace, živý feed), netřeba posílat zpět |
| WebSocket | Jedno trvalé obousměrné spojení | Chat, hry, spolupráce — obě strany posílají kdykoli |
Polling — primitivní, ale funkční
Klient se prostě opakovaně ptá: „je něco nového?" Většina odpovědí je „ne", takže se plýtvá requesty i zátěží. Čím kratší interval, tím „živější", ale tím větší zátěž. Hodí se jen tam, kde updaty nejsou časté a nevadí menší zpoždění.
Long-polling — chytřejší ptaní
Klient se zeptá, ale server odpověď nezdrží — drží spojení otevřené, dokud nemá co poslat (nebo dokud nevyprší limit). Jakmile přijde novinka, odpoví, klient si ji zpracuje a hned se zeptá znovu. Méně plýtvání než polling, funguje i tam, kde WebSockety nejdou.
SSE — server posílá proud událostí
Server-Sent Events: klient otevře jedno spojení a server přes něj jednosměrně (server → klient) posílá události, jak přicházejí. Jednoduché, běží přes obyčejné HTTP a automaticky se obnovuje při výpadku. Ideální, když klient jen přijímá (živé notifikace, feed, průběh úlohy) a nepotřebuje stejnou cestou posílat zpět.
WebSocket — plně obousměrné spojení
WebSocket naváže jedno trvalé spojení, přes které můžou obě strany posílat zprávy kdykoli. To je ono pro chat, online hry nebo společné editování dokumentu. Navázání začíná jako HTTP a pak se „povýší" (upgrade) na WebSocket. Cena: spojení musíš udržovat a starat se o znovupřipojení, když spadne.
Škálování realtime (tady to začne bolet)
Realtime spojení jsou stavová (stateful) — to jde proti tomu, co jsi se naučil o stateless škálování. Dva problémy a jejich řešení:
- Uživatelé jsou připojení k různým serverům. Alena je na serveru 1, Bóris na serveru 2. Když Alena pošle zprávu, jak ji server 1 doručí Bórisovi na serveru 2? Řešením je pub/sub backplane (typicky Redis, viz Messaging): server 1 zprávu publikuje, všechny servery ji dostanou a doručí svým připojeným klientům.
- Kam load balancer pošle nové (a znovu navázané) spojení. Sticky sessions a backplane řeší různé věci (ne „buď, anebo"): backplane směruje zprávy mezi servery, sticky/consistent routing zase posílá spojení (hlavně reconnect) konzistentně. I s backplane často chceš obojí.
Tři věci, na které se u realtime ve velkém zapomíná:
- Auth na spojení. WebSocket se musí autentizovat při navázání (handshake) a u citlivých zpráv i autorizovat per zpráva — veřejný endpoint bez auth je díra (Security).
- Presence („kdo je online") je distribuovaný stav přes všechny servery — typicky se drží v Redisu.
- Connection draining při deployi. Při rolling update máš tisíce otevřených spojení; musíš je řízeně rozpustit (klienti se přepojí jinam), ne je tvrdě zabít, jinak ztratíš rozpracované zprávy.
Failure modes — jak to v praxi praská
- Spadlé spojení → realtime spojení vypadávají (Wi-Fi, tunel). Klient musí umět automaticky se znovu připojit a dorovnat zmeškané zprávy.
- Zmeškané zprávy → mezi výpadkem a znovupřipojením můžou zprávy utéct; potřebuješ je umět doposlat (např. podle ID poslední doručené).
- Příliš mnoho otevřených spojení → každé spojení drží zdroje; tisíce naráz vyčerpají paměť/limity.
- Bez backplane → na víc serverech si uživatelé navzájem zprávy „nevidí".
🛠️ Cvičení
- Vyber techniku. Pro tyto případy zvol polling / long-polling / SSE / WebSocket a zdůvodni: notifikace o nové objednávce, chat dvou lidí, průběh dlouhé úlohy (0–100 %), společné editování dokumentu.
- Polling vs realita. Spočítej, kolik zbytečných requestů za hodinu udělá 1000 klientů, co se ptají každé 2 sekundy, když novinka přijde průměrně jednou za 10 minut.
- Backplane. Nakresli, jak se zpráva od uživatele na serveru 1 dostane k uživateli na serveru 2 přes Redis pub/sub.
- Znovupřipojení. Navrhni, jak klient po výpadku spojení dorovná zprávy, které mu mezitím utekly (nápověda: ID poslední doručené zprávy).
- SSE, nebo WebSocket? Pro „živý feed cen akcií, kam uživatel nic neposílá" rozhodni a zdůvodni.
Náčrt řešení — rozbal, až si cvičení zkusíš sám
- Vyber techniku — notifikace o nové objednávce = SSE (server jen tlačí, klient nic neposílá), chat dvou lidí = WebSocket (obě strany posílají kdykoli), průběh dlouhé úlohy 0–100 % = SSE (jednosměrný proud událostí), společné editování = WebSocket (obousměrná spolupráce). Pointa: WebSocket jen tam, kde posílá i klient, jinak stačí jednodušší SSE. Past: nezapomeň na auth při navázání spojení.
- Polling vs realita — 1000 klientů × (3600 s / 2 s) = 1 800 000 requestů za hodinu, ale novinka přijde jen ~6× za hodinu na klienta (jednou za 10 min), tedy ~6000 užitečných; zbytek (~1 794 000) je zbytečný. Pointa: polling masivně plýtvá, když jsou updaty řídké. Past: zkrácení intervalu „pro živost" zátěž jen znásobí.
- Backplane — server 1 přijme zprávu od svého klienta a publikuje ji do Redis pub/sub kanálu; všechny servery (1 i 2) jsou přihlášené, dostanou ji a doručí svým připojeným klientům, takže Bóris na serveru 2 zprávu dostane. Pointa: backplane směruje zprávy mezi servery, které o sobě jinak nevědí. Past: backplane řeší doručení zpráv, ne routing spojení — na reconnect chceš i sticky/consistent routing.
- Znovupřipojení — klient si pamatuje ID poslední doručené zprávy; po reconnectu pošle serveru „pošli mi všechno od tohohle ID dál" a server dorovná, co mezitím uteklo. Pointa: bez tohohto uživateli při každém výpadku zprávy zmizí. Past: server musí zmeškané zprávy nějakou dobu držet (buffer/historie), jinak nemá co doposlat.
- SSE, nebo WebSocket? — SSE: feed je čistě server → klient (uživatel nic neposílá), takže nepotřebuješ obousměrnost; navíc SSE běží přes obyčejné HTTP a sám se obnovuje při výpadku. Pointa: vyber nejjednodušší techniku, co úkol splní. Past: WebSocket by tu byl zbytečná režie navíc na udržování spojení.
🧠 Otázky & odpovědi
Proč obyčejné HTTP nestačí na chat?
HTTP funguje request → response: server odpoví jen tehdy, když se klient zeptá, a sám od sebe mu nic poslat nemůže. U chatu ale potřebuješ, aby server dotlačil novou zprávu klientovi ve chvíli, kdy přijde. Proto se používají techniky s trvalým spojením (SSE, WebSocket), kterými server umí klientovi posílat data sám.
Jaký je rozdíl mezi SSE a WebSocketem?
SSE je jednosměrné (server → klient): server posílá proud událostí, klient jen přijímá. Běží přes obyčejné HTTP a sám se obnovuje. WebSocket je obousměrný: obě strany můžou posílat zprávy kdykoli. Na živé notifikace nebo feed stačí SSE; na chat, hry nebo společné editování, kde posílá i klient, potřebuješ WebSocket.
Kdy stačí long-polling místo WebSocketu?
Když potřebuješ realtime efekt, ale WebSockety z nějakého důvodu nejdou (omezení sítě, proxy, staré prostředí). Long-polling drží request otevřený, dokud nemá novinku, takže se chová skoro jako push, jen je o něco méně efektivní. Je to dobrý kompromis tam, kde plný WebSocket není možný.
Proč je škálování realtime těžší než běžného HTTP?
Realtime spojení jsou stavová — uživatel je trvale připojený ke konkrétnímu serveru, což jde proti stateless škálování. Když je víc serverů, uživatelé na různých serverech o sobě „nevědí". Řeší to pub/sub backplane (např. Redis): server zprávu publikuje a všechny servery ji doručí svým připojeným klientům.
Co musí klient umět, aby realtime fungovalo spolehlivě?
Automaticky se znovu připojit, protože realtime spojení běžně vypadávají (Wi-Fi, tunel, uspání zařízení). A po znovupřipojení dorovnat zmeškané zprávy — typicky si pamatuje ID poslední doručené zprávy a řekne serveru „pošli mi všechno od téhle dál". Bez toho uživateli při každém výpadku zprávy mizí.
