Node.js: Principy fungování a architektura event-loopu

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í:

  1. Timers: zpracování setTimeout / setInterval, jejichž threshold vypršel.
  2. Pending Callbacks: nízkoúrovňová systémová zpětná volání (například některé chyby v TCP).
  3. Idle/Prepare: interní fáze libuv.
  4. 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).
  5. Check: vykonává setImmediate callbacky.
  6. 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 Promise reakce a process.nextTick. nextTick má 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…). setTimeout a setImmediate mohou 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ěhne setTimeout(..., 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řes try/catch. Pozor na paralelizaci: await v cyklu provádí serializaci; použijte Promise.all nebo allSettled.

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.