Výkon jako konkurenční výhoda
Node.js je postaven na event loopu, neblokujícím I/O a V8 enginu. Pro dosažení vysoké propustnosti a nízké latence nestačí pouze „psát asynchronně“. Je nutné rozumět mechanikám libuv, plánování úloh v event loopu, správě paměti, profilačním nástrojům, řízení tlaku dat (backpressure) a správnému návrhu architektury, která odlišuje I/O-bound a CPU-bound práci. 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 poll) a kdyprocess.nextTick(mikroúloha před dalším tickem). - Worker pool libuv: některé operace (fs, crypto, zlib, DNS) používají thread-pool. Velikost lze řídit přes
UV_THREADPOOL_SIZE; neadekvátní nastavení vede k frontám a latenci. - Mikroúlohy vs. makroúlohy:
Promisecallbacks (mikro) běží před další fází loopu; 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 paralelizaci pomocí
Promise.alla nevyužívejte sekvenčníawait, pokud úlohy nejsou závislé. - Řízení souběžnosti: používejte semafory/limity (např.
p-limit) k dávkování požadavků na externí systémy. - Time-boxing a zrušení:
AbortControllerpro rušení fetch/streamů; fail-fast při překročení SLA. - Odolnost: retry with jitter, circuit breaker, bulkhead a rate limiting s ohledem na idempotenci.
I/O optimalizace: proudy, backpressure a zero-copy
- Streams: používejte
Readable.from,pipelinea režim objektových proudů uvážlivě;pipelinesprávně propaguje chyby a respektuje backpressure. - Backpressure: kontrolujte návratové hodnoty
write(), čekejte na'drain'; vyhnete se nárůstu paměti. - Zero-copy: využijte
fs.createReadStream→resastream.pipeline; minimalizujte kopírování bufferů a serializaci. - Komprese:
zlib/brotlivpipelines vhodným levelem; vyvažujte CPU náklad vs. úspora přenosu.
CPU-bound práce: worker_threads a škálování
- Offload CPU: pro hashování, obrázky, PDF, parsování – použijte
worker_threadsnebo externí službu, nikoli event loop. - Cluster vs. load-balancer:
clustermůže využít více jader, ale moderněji preferujte více procesů spravovaných systémem (systemd/PM2/kontejnery) za reverzní proxy. - Message passing: předávejte Transferable (ArrayBuffer) místo kopírování; ušetříte GC i latenci.
Správa paměti a GC ve V8
- Heap limity: v kontejneru nastavte
--max-old-space-sizeadekvátně; sledujte fragmentaci a GC pauses. - Alokace: vyhýbejte se „horkým“ alokacím v tight-loop; recyklujte objekty a využívejte
Bufferpooling. - Úniky: slabé mapy (
WeakMap/WeakRef) pro cache s životností objektů; profilujte heap snapshots.
Optimalizace kódu: V8 inline cache a „shape“ objektů
- Stabilní tvary objektů: neinjektujte nové vlastnosti po vytvoření; inicializujte všechny v konstruktoru.
- Horké cesty: vyhněte se megafunkcím s polymorfními vstupy; dbejte na monomorfismus pro inlining.
- Výjimky vs. výkon: nepoužívejte výjimky k řízení toku v horkých smyčkách.
HTTP stack: latence, multiplexing a cache
- Keep-Alive a reuse:
agent.keepAlivepro odchozí HTTP(S); snižuje TCP/TLS handshaky. - HTTP/2: multiplexing, server push (omezeně), priorita streamů; pozor na TLS overhead a paměť.
- Cache a conditionals: korektní
ETag,Last-Modified,Cache-Control; krátké TTL s revalidací. - Statika mimo Node: servujte statické soubory přes CDN nebo edge proxy; Node ponechte pro dynamiku.
Databáze a datová vrstva
- Pools a limity: nastavte rozumné poolSize, queueing a timeouts; omezte souběžné dotazy na službu.
- Batching a N+1: použijte batched dotazy a kešování (např. DataLoader) pro resolvery a API vrstvy.
- Indexy a plán: sledujte slow-query logy, explain plány; omezte ORM magii u kritických dotazů.
- Idempotence: při retrys navrhujte idempotentní operace (klientské tokeny/klíče deduplikace).
Fronty a asynchronní zpracování na pozadí
- Message brokery: Redis (BullMQ), RabbitMQ, Kafka, NATS – oddělí příjem od zpracování, vyhladí špičky.
- Plánování a odpověď: pro uživatele vraťte 202 + polling hook nebo webhook; dlouhé úlohy běží mimo request thread.
- At-least-once vs. exactly-once: obvykle realizujte at-least-once s idempotentní aplikací; exactly-once je drahé.
- Observabilita front: metriky délky fronty, stáří zpráv, % retry, dead-letter queue a důvody selhání.
Bezpečná paralelizace a koordinace
- Zámky a deduplikace: distribuované zámky (Redlock s pečlivou konfigurací), leasing, tokeny idempotence.
- Škrcení toků: token bucket/leaky bucket na vstupu služby; chráníte DB a závislosti.
Profilace a diagnostika
- CPU profil:
node --prof,inspector, flamegraphy; hledejte horké cesty. - Heap a GC:
--inspectheap snapshots,--trace-gcpro analýzu pauz. - Klinika výkonu: nástroje typu Clinic.js (doctor, flame, bubbleprof) pomohou s latencí a konkurencí.
- perf_hooks a telemetrie: měřte vlastní metriky,
PerformanceObserverpro event-loop lag a zásahy GC. - Zátěžové testy: scénáře s nástroji jako Autocannon nebo k6; sledujte p95/p99 latenci a chování při degradaci.
Observabilita: logy, metriky, trasování
- Strukturované logy: JSON s korelačním ID; minimální overhead pomocí rychlých loggerů.
- Metriky: Prometheus export (latence, chybovost, průtok, event-loop delay, velikost heapu, délky front).
- Traces: OpenTelemetry s kontextem přes AsyncLocalStorage; sledujte závislosti a root cause.
Timeouty, limity a ochranné zábrany
- Timeouty všude: HTTP klienti, DB, fronty, externí API; žádné nekonečné čekání.
- Limity požadavků: payload size, počet souběžných požadavků, hlavičky; chraňte paměť i CPU.
- Validace vstupů: schémata (JSON Schema) s kompilací dopředu; minimalizuje CPU náklady při vysoké zátěži.
API návrh a sériové formáty
- Streaming odpovědí: pro velké výsledky používejte NDJSON nebo chunked transfer; snižujete latenci první bajt.
- Efektivní formáty: vyhněte se nadbytečným polím; u binárních dat zvažte Protobuf/Avro tam, kde dává smysl.
- Paginace a filtrace: keyset pagination vs. offset; lepší výkon a konzistence pod zátěží.
Konfigurace runtime a kontejnerů
- Verze Node: aktualizujte na LTS s nejnovějšími optimalizacemi V8 a stabilními API.
- Kontejnery:
--max-old-space-sizea--initial-old-space-sizepřizpůsobte limitům cgroups; nastavte ulimits. - Start a shutdown: rychlé starty, graceful shutdown na
SIGTERMs ukončením příjmu, odvodem front a flush logů.
Bezpečnost a výkon
- CSP a hlavičky: ochrana proti XSS bez nadbytečných kontrol na serveru; minimalizujte zbytečná šifrovací kola a re-handshaky.
- Rate limit a ochrana proti DoS: podporuje stabilitu a predikovatelné využití zdrojů.
- Deserializace: nikdy nespouštějte
evalani nebezpečné parsery na neověřených datech – šetříte CPU i rizika.
Testování výkonu a regresní hlídání
- Benchmarky funkcí: mikroměření horkých cest (stabilní vstupy, warm-up); sledujte odchylky mezi verzemi.
- Smoke load v CI: krátké zátěžové testy po build-time; zachytí hrubé regrese p95.
- Chaos a degradace: simulujte pomalé závislosti, výpadky sítě a limity DB; ověřte, že se systém degraduje řízeně.
Typické antipatterny a jak se jim vyhnout
- CPU v event loopu: synchronní JSON transformace obřích dat, šifrování nebo komprese v hlavním vlákně – vždy offload.
- Neomezená paralelizace: stovky souběžných fetchů vedou k zahlcení klienta i serveru; používejte limity.
- Ignorování backpressure: zápisy do streamů bez kontroly
write()→ out-of-memory. - Chybějící timeouty: visící sockety a držení zdrojů; implementujte globální politiky.
- Velké objekty v cache: nehlídané LRU a nekonečná TTL; měřte hit-rate a velikost.
Praktický kontrolní seznam pro rychlé služby
- Má každé externí volání timeout, retry s jitterem a je idempotentní?
- Je CPU-bound práce mimo event loop (workers/proxy služba)?
- Respektují streamy backpressure a používáte
pipeline? - Je nastaveno
agent.keepAlivepro odchozí HTTP(S)? - Jsou pooly DB/redis limitované a monitorované?
- Měříte p95/p99, event-loop lag a GC pauzy v produkci?
- Máte flamegraphy, heap snapshoty a automatický smoke load v CI?
- Probíhá graceful shutdown se zpracováním rozdělaných úloh?
Závěr: výkon jako disciplína, ne jednorázový tuning
Optimalizace Node.js vyžaduje systematický přístup: správný asynchronní design, oddělení CPU-bound úloh, práce se streamy a backpressure, promyšlené timeouty a limity, robustní observabilitu a pravidelnou profilaci. Kombinace těchto principů přináší stabilní nízkou latenci, vysokou propustnost a předvídatelné chování pod zátěží – a umožňuje týmu doručovat rychleji bez kompromisů v kvalitě.