Co je Node.js a proč event-loop
Node.js je runtime prostředí pro JavaScript mimo prohlížeč, postavené na V8 (JIT kompilátor od Googlu) a nativní knihovně libuv pro asynchronní I/O. Klíčovým principem je jednovláknový event-loop, který multiplexuje tisíce I/O operací bez blokování vláken JavaScriptu. Výsledkem je vysoká propustnost při nízké latenci u I/O-intenzivních služeb (API, proxy, realtime push), při zachování jednoduchého programovacího modelu.
Stavební kameny: V8, libuv, bindings a standardní knihovna
- V8: parsuje JavaScript, optimalizuje a JIT kompiluje horké části kódu, spravuje garbage collector (GC) a heap.
- libuv: poskytuje smyčku událostí, neblokující síťové I/O, časovače, filesystem přes thread-pool, signály a IPC.
- Bindings: spojují JavaScript a C/C++ vrstvu (N-API, node-addon-api) pro volání nativních funkcí.
- Node standardní knihovna: moduly jako
fs,net,http/https,crypto,stream,worker_threads, postavené nad libuv a V8.
Event-loop: fáze a pořadí zpracování
Event-loop v Node.js je logická smyčka řízená knihovnou libuv, která postupně prochází jednotlivé fáze. Každá fáze má frontu úloh (macrotasks). Zjednodušené pořadí:
- Timers: zpracování
setTimeout/setInterval, jejichž threshold vypršel. - Pending Callbacks: nízkoúrovňová systémová zpětná volání (například některé chyby v TCP).
- Idle/Prepare: interní fáze libuv.
- Poll: jádro – přijímá I/O události, obsluhuje sockety, čte a zapisuje data; pokud je fronta prázdná, může čekat na příchozí událost (s výjimkami).
- Check: vykonává
setImmediatecallbacky. - Close Callbacks: například
'close'události socketů a handleů.
Mezi jednotlivými fázemi se vždy vyprazdňuje mikrofronta (microtasks), tj. process.nextTick a Promise .then() / await pokračování.
Microtasks vs. macrotasks: jemná pravidla pořadí
- Microtasks: vykonávají se po každém callbacku a před přechodem do další fáze. Patří sem
Promisereakce aprocess.nextTick.nextTickmá navíc přednost i před Promise microtasks – nadměrné používání může způsobit vyhladovění event-loop. - Macrotasks: položky ve frontách jednotlivých fází (timers, I/O, check…).
setTimeoutasetImmediatemohou být vykonány v různém pořadí v závislosti na fázi, ve které byly naplánovány.
Časování: setTimeout vs. setImmediate vs. nextTick
setTimeout(fn, 0): minimální zpoždění je orientační (clamping), callback poběží ve fázi timers po vypršení prahu a až po mikroúlohách.setImmediate(fn): callback ve fázi check hned po fázi poll. Pokud je naplánován v rámci I/O callbacku, obvykle předběhnesetTimeout(..., 0).process.nextTick(fn): microtask s nejvyšší prioritou; používejte střídmě (například pro kompatibilitu API), jinak riskujete vyhladovění.
Asynchronní I/O a thread-pool libuv
Sockety a většina síťového I/O jsou skutečně neblokující a řízené pollerem operačního systému (epoll/kqueue/IOCP). Filesystémové operace (část fs) jsou prováděny v rámci libuv thread-poolu (výchozí velikost bývá 4, lze ji nastavit přes UV_THREADPOOL_SIZE). Proto může intenzivní práce s diskem blokovat dostupná vlákna a zvyšovat latenci – sledujte fronty a případně navyšujte velikost poolu nebo používejte streaming a dávkování (batching).
Streams a zpětný tlak (backpressure)
stream.Readable a Writable implementují tokové API s řízením průtoku dat. Klíčové je respektovat návratovou hodnotu write() a čekat na událost 'drain'. Tím se vyhnete přetečení bufferů a stabilizujete latenci. Pipes (readable.pipe(writable)) řeší backpressure automaticky.
Programovací model: callbacky, Promise a async/await
- Callbacky (historicky error-first kontrakt
(err, data)) – stále používané v některých API (fs). - Promise a async/await: moderní API (
fs/promises,timers/promises) s přehledným zpracováním chyb přestry/catch. Pozor na paralelizaci:awaitv cyklu provádí serializaci; použijtePromise.allneboallSettled.
Garbage collector a výkon
V8 používá generacionální garbage collector. Dlouhé pauzy snižují propustnost. Doporučení: vyhýbejte se masivní alokaci v horkých smyčkách, recyklujte objekty tam, kde dává smysl, preferujte Buffer pooling u I/O, měřte heap pomocí inspector a heap snapshots. Pro latencí kritické služby je vhodné sledovat event-loop delay a statistiky GC.
Moduly: CommonJS vs. ECMAScript Modules
Node podporuje jak CJS (require, module.exports), tak ESM (import/export). Volba režimu vychází z package.json ("type": "module") a přípon (.mjs / .cjs). Mísení formátů vyžaduje edge pravidla (například createRequire), proto preferujte jednotný styl v projektu a explicitní exportní mapy (exports).
Worker Threads a Cluster
- Worker Threads: skutečná paralelizace CPU-náročných úloh v rámci jednoho procesu s izolovanými heapy, sdílenou pamětí (SharedArrayBuffer) a message-passing. Používejte pro kompresi, šifrování, transformace dat.
- Cluster: více procesů sdílí jeden port (round-robin). Zvyšuje propustnost na vícejádrových serverech, ale každý proces běží ve vlastním heapu a event-loopu.
Nativní doplňky (Addons) a N-API
Pro kritické úseky lze psát nativní moduly v jazyce C/C++ přes N-API, které stabilizuje ABI napříč verzemi Node.js. Hodí se pro obaly knihoven operačního systému, kryptografii, parsování či výpočty s vysokým výkonem. Dbejte na přenositelnost, správu paměti a bezpečnost vůči vláknování (thread-safety).
HTTP server a síťové vzory
Server http / http2 je event-driven: každé spojení je socket s obsluhou request/response ve formě streamů. Optimalizace zahrnují: keep-alive, connection reuse, header packing, podporu HTTP/2, kompresi (zlib/brotli), správnou správu cache-control a ETag. Zvažte obranu pomocí WAF, rate limiting, časové limity (headersTimeout, requestTimeout), limit velikosti body a ochranu proti slow-loris útokům.
Bezpečnost: runtime i závislosti
- Skenujte závislosti (audit), zamykejte verze (
lockfile), minimalizujte attack surface. - Chraňte se proti Prototype Pollution, SSRF, ReDOS (vyhněte se patologickým regulárním výrazům), XSS v šablonách a path traversal.
- Aktivujte bezpečné křivky a šifry v OpenSSL, validujte certifikáty a hostname u vycházejících spojení.
Observabilita: metriky, logy a tracing
Měřte event-loop lag, počet otevřených handlerů, využití heapu a CPU, latenci endpointů a chybovost. Pro tracing použijte OpenTelemetry a W3C Trace Context. Logy pište strukturovaně (JSON), s korelačními ID a ochranou osobních údajů.
Diagnostika a profilace
- Inspector (
--inspect, DevTools) pro krokové ladění a profilování CPU/heapu. - perf, 0x, clinic pro flame graphs a hledání výkonových úzkých míst.
- Async Hooks pro sledování životního cyklu asynchronních zdrojů (s opatrností kvůli dopadu na výkon).
Správa procesu: signály, fronty a vypínání
Implementujte graceful shutdown: zachytávejte signál SIGTERM, ukončete příjem nových spojení, počkejte na dokončení probíhajících požadavků, uvolněte zdroje a ukončete proces s kódem 0. V kontejnerovém prostředí zohledněte liveness a readiness sondy a back-pressure ze strany ingress vrstvy.
Výkonnostní zásady a anti-patterny
- Nevykonávejte CPU-intenzivní smyčky v hlavním vlákně – použijte Worker Threads nebo offload do externích služeb.
- Vyvarujte se používání synchronních API (
fs.readFileSync) v cestě requestu. - Preferujte streamy před načítáním celého obsahu do paměti.
- Batchujte a coalesce I/O, využívejte connection pooling.
- Měřte, profilujte, iterujte – odhad není náhradou za metriku.
Správa balíčků a nasazení
package.json definuje skripty (start, build), exportní mapy, typ modulu, engines a spustitelné binárky. Pro rychlý start používejte prebuild, tree-shaking, minimalizujte runtime transpilační kroky. V produkci zamkněte lockfile, nastavte NODE_ENV=production, kontrolujte velikost image a nastavte limity paměti.
Kompatibilita a verze Node
Zvolte LTS řadu pro produkční nasazení. Sledujte semver major verze (například výchozí chování ESM, nová API v fetch, stabilizace test runner). Testujte v rámci CI na více verzích a definujte minimální podporované verze v engines.
Typické architektonické vzory
- API Gateway/Backend-for-Frontend: agregace dat, cache, rate limiting, autorizace.
- Realtime: WebSocket/SSE, pub-sub, škálování přes Redis nebo cloudové pubsub systémy.
- Job workers: fronty (BullMQ, RabbitMQ), idempotentnost, deduplikace a plánování úloh.
Závěr
Node.js kombinuje jednovláknový event-loop s neblokujícím I/O a robustní standardní knihovnou. Pochopení fází event-loopu, rozdílů mezi microtasks a macrotasks, role thread-poolu a práce se streamy je nezbytné pro stabilní a výkonné služby. Správně navržená architektura – včetně observability, bezpečnosti, využití worker threads pro CPU-intenzivní úlohy a pečlivého shutdownu – umožňuje škálovat od jednoduchých API až po globální realtime platformy.