Appka, co dobře běží pro 10 uživatelů, se může zhroutit při 10 000. Škálování je o tom, jak zvládnout rostoucí zátěž, a performance o tom, jak udržet věci rychlé. Zlaté pravidlo, které si odnes hned na začátku: nejdřív měř, teprve pak optimalizuj. Ladit naslepo bez měření je hádání.
Pár pojmů na úvod
- Instance = jedna běžící kopie tvé aplikace. Můžeš jich mít víc naráz.
- Škálování (scaling) = jak zvýšit kapacitu, aby systém unesl víc zátěže.
- Load balancer = „rozdělovač", který příchozí provoz rozhazuje mezi víc instancí.
- Bottleneck (úzké hrdlo) = nejpomalejší místo, které brzdí celý systém (jako nejužší místo na dálnici).
- Profilování = měření, kde program tráví čas (abys věděl, co reálně zrychlit).
Vertikální vs horizontální škálování
Dvě cesty, jak zvýšit kapacitu — představ si přepravu zboží:
- Vertikální škálování (scale up) = větší kamion: dej aplikaci silnější stroj (víc CPU a RAM). Jednoduché, žádná změna kódu. Ale má strop (největší stroj má limit), je dražší a když ten jeden stroj spadne, spadne všechno.
- Horizontální škálování (scale out) = víc kamionů: pusť aplikaci na víc strojů zároveň. Škáluje skoro neomezeně a je odolné (jeden spadne, ostatní jedou dál). Potřebuje ale load balancer a hlavně stateless služby (viz dál).
Klíč k horizontálnímu škálování je být stateless. Stav (např. přihlášení) nesmí být uložený v paměti konkrétní instance, ale venku (databáze, Redis). Pak je jedno, která instance request obslouží — a přidání instancí okamžitě zvedne kapacitu. Proto je „HTTP je stateless" z Foundations tak důležité.
Load balancing
Load balancer stojí před tvými instancemi a rozhazuje mezi ně provoz, jako dispečer rozdělující zakázky řidičům. Umí to různě — třeba poslat každý další request na další instanci v kruhu (round-robin), nebo tam, kde je zrovna nejmíň práce. Zároveň hlídá zdraví instancí (readiness check z Observability) a mrtvé z rozdělování vyřadí.
L4 vs L7 — na jaké vrstvě balancuje. Je dobré znát rozdíl:
- L4 (transportní, TCP) — rozhoduje jen podle IP/portu, do obsahu nekouká. Je rychlý a hloupý.
- L7 (aplikační, HTTP) — vidí do requestu, takže umí routovat podle cesty/hlavičky (
/apijinam než/), ukončit TLS (TLS termination), přidávat hlavičky a dělat sticky sessions. Za to platí trochu vyšší režií. Většina webových load balancerů (a reverse proxy jako nginx) jsou L7.
Kde hledat úzká hrdla (obvyklé pořadí)
Když je něco pomalé, viník bývá skoro vždy v tomhle pořadí:
- Databáze — chybějící index, N+1, pomalé dotazy (Databáze). Skoro vždy první podezřelý.
- Volání po síti — zbytečně dlouhý řetěz volání cizích služeb, bez sdružování.
- Chybějící cache — počítáš pořád dokola to samé (Caching).
- CPU — náročné výpočty. Většinou až poslední.
Měř, nehádej. „Mně se zdá, že je pomalé tohle" je skoro vždycky vedle. Pravdu ti řekne až profilování (u databáze třeba
EXPLAIN ANALYZEz kapitoly o databázích).
Konkrétní techniky na zrychlení
- Connection pooling — recykluj spojení k databázi místo otevírání nového pokaždé (Foundations, Databáze). Nejčastější levné zrychlení.
- Sdružování (batching) — místo 100 malých dotazů jeden hromadný; místo 100 volání jedno.
- Asynchronní zpracování — dlouhé úlohy (e-mail, generování PDF) odsuň mimo cestu requestu, ať uživatel nečeká (Messaging).
- Pagination — nikdy nevracej neomezené množství dat najednou (API Design).
- CDN + cache — odlehči svému serveru (Caching).
- Read replicas — rozlož čtení na kopie databáze (Databáze).
- Komprese — menší data putují sítí rychleji.
Latence vs průtok (nepleť si)
- Latence (latency) = jak dlouho trvá jeden request. Důležitá pro pocit rychlosti pro uživatele.
- Průtok (throughput) = kolik requestů celkem za sekundu zvládneš.
Nejsou to stejné věci a zlepšení jednoho nemusí pomoct druhému. Sdružování (batching) třeba zvedne průtok, ale jednomu requestu může latenci zhoršit (čeká, až se nasbírá dávka). Klasický trade-off.
⚠️ Senior insight — chvost se neskládá lineárně (tail at scale). Když request volá 10 závislostí sériově a každá má p99 = 100 ms, výsledné p99 NENÍ 100 ms — stačí, aby byla pomalá kterákoli z deseti. Konkrétně: šance, že tě aspoň jedna z 10 trefí do svého horního 1 %, je ~1 − 0,99¹⁰ ≈ 10 % — takže to, co je u jedné závislosti „1 z 100", je u řetězce skoro „1 z 10" (zhruba p90 jedné). Čím víc závislostí v cestě, tím hůř. Proto se volání paralelizují, kde to jde, a pomalé se ořezávají timeoutem.
Řády, které je dobré mít v hlavě
| Operace | Řádově |
|---|---|
| Přístup do RAM | nanosekundy |
| Čtení z SSD | ~100 µs |
| Dotaz do databáze (lokální, indexovaný) | ~0,1–1 ms (z cache i méně) |
| Síť v rámci datacentra | ~0,5 ms |
| Síť mezi kontinenty | 100+ ms |
Nemusíš biflovat čísla, ale měj cit pro řády: síť přes půl světa je zhruba stotisíckrát pomalejší než RAM. Proto: minimalizuj cesty tam a zpět (round-tripy), drž cache blízko a nevolej zbytečně přes celou planetu.
Náklady — škálování stojí peníze
Na výkon se často díváme jen jako na „rychlost", ale je tu druhá strana: každá instance, replika, gigabajt úložiště i přenesený traffic něco stojí. Dobrý backend engineer myslí i na účet, ne jen na latenci.
- Optimalizace = i šetření. Když zrychlíš dotazy nebo přidáš cache, často potřebuješ míň serverů na stejnou zátěž → nižší účet. Výkon a náklady jdou ruku v ruce.
- Right-sizing místo over-provisioningu. Nedávej si „pro jistotu" desetinásobek kapacity, co potřebuješ — platíš za nečinné zdroje. Měř reálnou zátěž a přizpůsob (k tomu pomáhá autoscaling z Infra).
- Nejlevnější je práce, kterou neuděláš. Requesty, co obslouží cache, nestojí databázu nic; data, co nepřeneseš, nestojí za přenos. Proto cache, komprese a méně round-tripů šetří výkon i peníze.
„Funguje to" je málo — otázka zní „funguje to a kolik to měsíčně stojí?". Scaling bez ohledu na náklady je jen půlka řemesla.
Failure modes — kde se to kazí
- Předčasná optimalizace → přidáš složitost bez změřeného zisku.
- Stavová instance → neškáluje horizontálně (session v paměti váže uživatele na jeden server).
- Jedna pomalá závislost → bez timeoutu/circuit breakeru položí celý systém (Patterny).
- Optimalizace špatného místa → neměřil jsi, hádal jsi, a zrychlil něco, co nebylo problém.
🛠️ Cvičení
- Měř, nehádej. Vezmi pomalý endpoint (klidně smyšlený, se třemi dotazy do databáze a jedním voláním cizí služby). Napiš, čím bys změřil, kde se tráví čas, než cokoli ladíš.
- Stateless pro škálování. Appka drží přihlášení v paměti instance. Vysvětli, proč nejde horizontálně škálovat, a přepiš to tak, aby šla.
- Latence vs průtok. Sdružení 100 requestů do jednoho zvedne průtok — ale co to udělá s latencí jednoho requestu? Kdy je to výhra a kdy ne?
- Najdi úzké hrdlo. Pro „p99 latence vyletěla po nasazení" seřaď, kde bys hledal nejdřív: databáze, volání po síti, cache, CPU.
- Řády latencí. Seřaď od nejrychlejšího: přístup do RAM, čtení z SSD, síť v datacentru, síť přes oceán. O kolik řádů se liší RAM a mezikontinentální síť?
Náčrt řešení — rozbal, až si cvičení zkusíš sám
- Měř, nehádej — než cokoli ladíš, změř, kde se tráví čas: zapni profilování a u každého ze tří dotazů použij
EXPLAIN ANALYZE(chybí index? N+1?), u volání cizí služby změř jeho latenci, a celý endpoint si projdi v trace (waterfall ukáže viníka). Pozor: „mně se zdá, že je pomalé tohle" je skoro vždy vedle — bez měření optimalizuješ naslepo a často zrychlíš něco, co nebyl problém. - Stateless pro škálování — přihlášení v paměti instance váže uživatele na jeden konkrétní server, takže přidání instancí nepomůže (a pád serveru = ztráta přihlášení); přepiš to tak, že stav přesuneš ven (session do databáze nebo Redis), pak request obslouží kterákoli instance. Pozor: stateless je přímo klíč k horizontálnímu škálování — bez něj ti load balancer nepomůže.
- Latence vs průtok — sdružení 100 requestů do jednoho zvedne průtok (víc práce za sekundu), ale jednomu requestu zhorší latenci, protože čeká, než se nasbírá dávka; výhra je, když ti jde o celkovou propustnost a pár ms navíc nevadí, prohra, když uživatel čeká na jednu rychlou odpověď. Pozor: zlepšení jednoho nemusí pomoct druhému — vždy věz, který z nich zrovna řešíš.
- Najdi úzké hrdlo — pořadí podezřelých: nejdřív databáze (chybějící index, N+1, pomalé dotazy), pak volání po síti (dlouhý řetěz cizích služeb), pak chybějící cache, a CPU obvykle až nakonec. Pozor: tohle je jen pravděpodobné pořadí — stejně to změř (profiluj), ať neladíš špatné místo, protože p99 po nasazení může zvednout i jediná nová pomalá závislost.
- Řády latencí — od nejrychlejšího: přístup do RAM (nanosekundy) → čtení z SSD (~100 µs) → síť v datacentru (~0,5 ms) → síť přes oceán (100+ ms); RAM a mezikontinentální síť se liší zhruba o pět řádů (~stotisíckrát). Pozor: nemusíš biflovat čísla, ale měj cit pro řády — proto minimalizuj round-tripy, drž cache blízko a nevolej zbytečně přes půl světa.
🧠 Otázky & odpovědi
Proč je stateless klíč k horizontálnímu škálování?
Když instance nedrží žádný lokální stav (přihlášení v paměti), může request obsloužit kterákoli z nich — stačí přidat instance a load balancer rozhodí provoz. Stav se drží venku (databáze, Redis). Stavová instance naopak váže uživatele na konkrétní server, takže přidávání instancí nepomůže a pád serveru znamená ztrátu přihlášení.
Jaký je rozdíl mezi latencí a průtokem?
Latence = jak dlouho trvá jeden request (důležité pro pocit rychlosti). Průtok = kolik requestů za sekundu zvládneš celkem. Nejsou totéž: sdružování (batching) zvedne průtok, ale jednomu requestu může latenci zhoršit (čeká na dávku). Zlepšení jednoho nemusí pomoct druhému — vždy věz, který zrovna řešíš.
Kde hledat úzké hrdlo výkonu jako první?
Skoro vždy databáze (chybějící index, N+1, pomalé dotazy), pak volání po síti, pak chybějící cache, a CPU obvykle až nakonec. Ale neřeš to od oka — měř (profiluj). „Mně se zdá, že je pomalé X" bývá skoro vždy vedle; pravdu řekne až měření.
Vertikální, nebo horizontální škálování?
Vertikální (silnější stroj) je jednoduché a bez změny kódu, ale má strop a je jediným bodem selhání. Horizontální (víc strojů) škáluje skoro neomezeně a je odolné, ale potřebuje stateless služby a load balancer. Začni vertikálně (jednodušší), na velkou a odolnou škálu jdi horizontálně.
Proč je dobré mít cit pro řády latencí?
Abys věděl, kde se reálně pálí čas. RAM je v nanosekundách, dotaz do databáze v milisekundách, síť přes kontinenty 100+ ms — to je zhruba stotisíckrát pomalejší než RAM. Proto: omez cesty tam a zpět, drž cache blízko a nevolej zbytečně přes půl světa. Konkrétní čísla biflovat nemusíš, řády ano.
Proč myslet při škálování i na náklady?
Protože každá instance, replika, gigabajt úložiště i přenesený traffic něco stojí — „funguje to" je málo, otázka je „funguje to a kolik to měsíčně stojí". Navíc výkon a náklady jdou ruku v ruce: zrychlíš dotazy nebo přidáš cache → potřebuješ míň serverů → nižší účet. Nepředimenzovávej „pro jistotu" (platíš za nečinné zdroje) a pamatuj, že nejlevnější je práce, kterou neuděláš (cache, méně round-tripů).
