Výkon jako konkurenční výhoda
Node.js je postaven na event loopu, neblokujícím I/O a enginu V8. K dosažení vysoké propustnosti a nízké latence nestačí jen „psát asynchronně“. Je nutné rozumět mechanismům libuv, plánování úloh v event loopu, správě paměti, nástrojům pro profilování, řízení zpětného tlaku (backpressure) a správnému návrhu architektury, která rozlišuje mezi úlohami závislými na I/O a úlohami závislými na CPU. Tento článek shrnuje zásady optimalizace výkonu a asynchronního zpracování v Node.js – od kódu přes runtime až po provoz.
Model běhu: event loop, fronty a libuv
- Fáze event loopu: timers, pending callbacks, idle/prepare, poll, check, close callbacks. Rozlišujte, kdy použít
setImmediate(po fázi poll) a kdyprocess.nextTick(mikroúloha před dalším tickem). - Thread pool libuv: některé operace (fs, crypto, zlib, DNS) využívají thread pool. Jeho velikost lze řídit pomocí
UV_THREADPOOL_SIZE; nevhodné nastavení vede k tvorbě front a zvýšení latence. - Mikroúlohy vs. makroúlohy: callbacky
Promise(mikroúlohy) se vykonávají před další fází loopu; jejich nadměrné řetězení může zdržovat I/O.
Asynchronní vzory: od callbacků k async/await
- Promises a async/await: zlepšují čitelnost; dbejte na paralelní zpracování pomocí
Promise.alla nepoužívejte sekvenčníawait, pokud na sobě úlohy nezávisí. - Řízení souběžnosti: používejte semafory a limity (např.
p-limit) k dávkování požadavků na externí systémy. - Časové limity a rušení: používejte
AbortControllerke zrušení požadavků fetch a streamů; při překročení SLA postupujte podle principu fail-fast. - Odolnost: používejte opakování požadavků s náhodným rozptylem (retry with jitter), circuit breaker, bulkhead a rate limiting s ohledem na idempotenci.
Optimalizace I/O: proudy, backpressure a zero-copy
- Streamy: používejte
Readable.from,pipelinea režim objektových streamů uvážlivě;pipelinesprávně předává chyby a respektuje backpressure. - Backpressure: kontrolujte návratové hodnoty
write()a čekejte na událost'drain'; zabráníte tak nadměrnému nárůstu využití paměti. - Zero-copy: využijte
fs.createReadStream→resastream.pipeline; minimalizujte kopírování bufferů a serializaci. - Komprese: používejte
zlib/brotlivpipelines vhodnou úrovní komprese; vyvažujte nároky na CPU a úsporu přenášených dat.
Úlohy závislé na CPU: worker_threads a škálování
- Přesunutí úloh náročných na CPU: pro hashování, zpracování obrázků a PDF nebo parsování použijte
worker_threadsči externí službu, nikoli event loop. - Cluster vs. load balancer:
clustermůže využít více jader, modernějším řešením je však upřednostnit více procesů spravovaných systémem (systemd/PM2/kontejnery) za reverzní proxy. - Předávání zpráv: místo kopírování předávejte objekty Transferable (ArrayBuffer); ušetříte tak prostředky GC i snížíte latenci.
Správa paměti a GC ve V8
- Limity haldy: v kontejneru nastavte
--max-old-space-sizena odpovídající hodnotu; sledujte fragmentaci a GC pauses. - Alokace: vyhýbejte se častým alokacím v úzkých smyčkách; recyklujte objekty a využívejte sdružování objektů
Buffer. - Úniky: pro cache s životností navázanou na objekty používejte slabé mapy (
WeakMap/WeakRef); profilujte snímky haldy.
Optimalizace kódu: inline cache V8 a „tvary“ objektů
- Stabilní tvary objektů: nepřidávejte nové vlastnosti až po vytvoření objektu; inicializujte všechny vlastnosti v konstruktoru.
- Horké cesty: vyhýbejte se rozsáhlým funkcím s polymorfními vstupy; pro inlining dbejte na monomorfismus.
- Výjimky vs. výkon: nepoužívejte výjimky k řízení toku v horkých smyčkách.
HTTP stack: latence, multiplexování a cache
- Keep-Alive a opakované použití spojení: pro odchozí HTTP(S) použijte
agent.keepAlive; snižuje počet navazování spojení TCP/TLS. - HTTP/2: multiplexování, server push (v omezené míře), priorita streamů; pozor na režii TLS a spotřebu paměti.
- Cache a podmíněné požadavky: správně nastavujte
ETag,Last-ModifiedaCache-Control; používejte krátkou dobu TTL s opětovným ověřováním. - Statický obsah mimo Node: statické soubory poskytujte přes CDN nebo edge proxy; Node ponechte pro dynamický obsah.
Databáze a datová vrstva
- Pooly a limity: nastavte rozumné hodnoty poolSize, queueing a časových limitů; omezte počet souběžných dotazů na službu.
- Dávkové zpracování a N+1: v resolverech a vrstvách API používejte dávkově zpracovávané dotazy a cachování (např. DataLoader).
- Indexy a plány: sledujte protokoly pomalých dotazů a plány jejich provádění; u kritických dotazů omezte používání skrytých funkcí ORM.
- Idempotence: při opakování požadavků navrhujte idempotentní operace (klientské tokeny/klíče pro deduplikaci).
Fronty a asynchronní zpracování na pozadí
- Zprostředkovatelé zpráv: Redis (BullMQ), RabbitMQ, Kafka, NATS – oddělují příjem od zpracování a vyrovnávají špičky.
- Plánování a odpověď: uživateli vraťte stav 202 a nabídněte polling hook nebo webhook; dlouhé úlohy se zpracovávají mimo vlákno obsluhující požadavek.
- At-least-once vs. exactly-once: obvykle zvolte přístup at-least-once s idempotentním zpracováním; exactly-once je nákladné.
- Observabilita front: sledujte metriky délky fronty, stáří zpráv, podílu opakovaných pokusů, dead-letter queue a důvodů selhání.
Bezpečná paralelizace a koordinace
- Zámky a deduplikace: používejte distribuované zámky (Redlock s pečlivou konfigurací), leasing a tokeny idempotence.
- Omezování toku: na vstupu do služby použijte algoritmus token bucket/leaky bucket; ochráníte tak databázi a závislé služby.
Profilování a diagnostika
- Profil CPU: používejte
node --prof,inspectora flame grafy; hledejte horké cesty. - Halda a GC: použijte
--inspectpro snímky haldy a--trace-gck analýze pauz. - Diagnostika výkonu: nástroje jako Clinic.js (doctor, flame, bubbleprof) pomohou analyzovat latenci a souběžnost.
- perf_hooks a telemetrie: měřte vlastní metriky; pro sledování prodlevy event loopu a zásahů GC použijte
PerformanceObserver. - Zátěžové testy: připravte scénáře pomocí nástrojů jako Autocannon nebo k6; sledujte latenci p95/p99 a chování při degradaci.
Observabilita: logy, metriky, trasování
- Strukturované logy: používejte JSON s korelačním ID; režii minimalizujte rychlými nástroji pro logování.
- Metriky: exportujte data do Promethea (latence, chybovost, propustnost, prodleva event loopu, velikost haldy, délky front).
- Trasování: používejte OpenTelemetry s předáváním kontextu přes AsyncLocalStorage; sledujte závislosti a kořenovou příčinu problémů.
Časové limity, omezení a ochranné mechanismy
- Časové limity všude: nastavte je pro HTTP klienty, databáze, fronty i externí API; nedovolte nekonečné čekání.
- Limity požadavků: omezte payload size, počet souběžných požadavků a velikost hlaviček; chraňte paměť i CPU.
- Validace vstupů: používejte schémata (JSON Schema) zkompilovaná předem; při vysoké zátěži se tím minimalizují nároky na CPU.
Návrh API a sériové formáty
- Streamování odpovědí: pro rozsáhlé výsledky používejte NDJSON nebo chunked transfer; snížíte latenci do přijetí prvního bajtu.
- Efektivní formáty: vyhýbejte se nadbytečným polím; pro binární data zvažte Protobuf/Avro, pokud to dává smysl.
- Stránkování a filtrování: zvažte keyset pagination namísto stránkování pomocí offsetu; při zátěži nabízí vyšší výkon a konzistenci.
Konfigurace runtime a kontejnerů
- Verze Node: aktualizujte na verzi LTS s nejnovějšími optimalizacemi V8 a stabilními API.
- Kontejnery: hodnoty
--max-old-space-sizea--initial-old-space-sizepřizpůsobte limitům cgroups; nastavte ulimits. - Spuštění a vypnutí: zajistěte rychlé spouštění a graceful shutdown při signálu
SIGTERM, včetně ukončení příjmu požadavků, vyprázdnění front a odeslání logů.
Bezpečnost a výkon
- CSP a hlavičky: chraňte se proti XSS bez nadbytečných kontrol na serveru; minimalizujte zbytečné cykly šifrování a opětovného navazování spojení.
- Omezování počtu požadavků a ochrana proti DoS: podporují stabilitu a předvídatelné využití zdrojů.
- Deserializace: nikdy nespouštějte
evalani nepoužívejte nebezpečné parsery na neověřená data – ušetříte tak prostředky CPU a snížíte rizika.
Testování výkonu a sledování regresí
- Benchmarky funkcí: provádějte mikroměření horkých cest (stabilní vstupy, zahřátí); sledujte odchylky mezi verzemi.
- Smoke test zátěže v CI: po sestavení spusťte krátké zátěžové testy; odhalí výrazné regrese p95.
- Chaos a degradace: simulujte pomalé závislé služby, výpadky sítě a limity databáze; ověřte, že se systém při zhoršení podmínek chová řízeně.
Typické antipatterny a jak se jim vyhnout
- CPU v event loopu: synchronní transformace rozsáhlých dat JSON, šifrování nebo komprese v hlavním vlákně – vždy tyto úlohy přesuňte mimo ně.
- Neomezená paralelizace: stovky souběžných požadavků fetch vedou k zahlcení klienta i serveru; používejte limity.
- Ignorování backpressure: zápis do streamů bez kontroly
write()→ vyčerpání paměti. - Chybějící časové limity: vedou k nečinným socketům a blokování zdrojů; zaveďte globální pravidla.
- Velké objekty v cache: neomezené LRU a nekonečná TTL; měřte míru zásahů a velikost cache.
Praktický kontrolní seznam pro rychlé služby
- Má každé externí volání nastavený časový limit, opakování s jitterem a zajištěnou idempotenci?
- Probíhá práce náročná na CPU mimo event loop (workers/proxy služba)?
- Respektují streamy backpressure a používáte
pipeline? - Je pro odchozí HTTP(S) nastaveno
agent.keepAlive? - Jsou pooly DB/Redis omezené a monitorované?
- Měříte v produkci latenci p95/p99, prodlevu event loopu a pauzy GC?
- Máte flame grafy, snímky haldy a automatické zátěžové smoke testy v CI?
- Probíhá graceful shutdown se zpracováním rozpracovaných úloh?
Závěr: výkon jako disciplína, ne jednorázové ladění
Optimalizace Node.js vyžaduje systematický přístup: správný asynchronní návrh, oddělení úloh závislých na CPU, práci se streamy a backpressure, promyšlené časové limity a omezení, robustní observabilitu a pravidelné profilování. Kombinace těchto principů přináší stabilně nízkou latenci, vysokou propustnost a předvídatelné chování při zátěži – a umožňuje týmu dodávat řešení rychleji bez kompromisů v kvalitě.
