Co je Node.js a proč event-loop
Node.js je běhové 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ákna JavaScriptu. Výsledkem je vysoká propustnost při nízké latenci u I/O-těžký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 JS, optimalizuje a JIT kompiluje horké úseky 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: lepidlo mezi JavaScriptem a C/C++ vrstvou (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á libuv, která postupně prochází 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ř. některé chyby z TCP).
- Idle/Prepare: interní fáze libuv.
- Poll: hlavní část – přijímá I/O události, obsluhuje sockety, čte/zapisuje data; pokud je fronta prázdná, může blokovat do příchodu události (s výjimkami).
- Check: provádí se
setImmediatecallbacky. - Close Callbacks: například
'close'události socketů/handlů.
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: vykonají 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 vyhladovět event-loop. - Macrotasks: položky ve frontách jednotlivých fází (timers, I/O, check…).
setTimeoutasetImmediatemohou mít různé pořadí podle toho, v jaké fázi 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ěží v timers fázi po vypršení prahu a až po microtasks.setImmediate(fn): callback v check fázi hned po poll. Pokud se naplánuje z I/O callbacku, typicky předběhnesetTimeout(..., 0).process.nextTick(fn): microtask s nejvyšší prioritou; používejte střídmě (např. pro kompatibilitu API), jinak risk hladově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 OS (epoll/kqueue/IOCP). Filesystémové operace (část fs) se provádějí v libuv thread-poolu (výchozí velikost bývá 4, nastavit lze 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 batching.
Streams a zpětný tlak (backpressure)
stream.Readable/Writable implementují tokové API s řízením rychlosti. Klíčové je respektovat návratovou hodnotu write() a čekat na 'drain'. Tím se vyhnete přetečení bufferů a stabilizujete latenci. Pipes (readable.pipe(writable)) backpressure řeší automaticky.
Programovací model: callbacky, Promise a async/await
- Callbacky (historicky error-first kontrakt
(err, data)) – stále v některých API (fs). - Promise a async/await: moderní API (
fs/promises,timers/promises) s přehledným řízením chyb přestry/catch. Pozor na paralelizaci:awaitv cyklu serializuje; použijtePromise.all/allSettled.
Garbage collector a výkon
V8 používá generacionální GC. Dlouhé pauzy snižují propustnost. Opatř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 latenci kritické služby je vhodné sledovat event-loop delay a GC statistiky.
Moduly: CommonJS vs. ECMAScript Modules
Node podporuje CJS (require, module.exports) i ESM (import/export). Volba režimu vychází z package.json ("type": "module") a přípon (.mjs/.cjs). Míšení formátů vyžaduje edge pravidla (např. createRequire), proto preferujte jednotný styl v projektu a explicitní exportní mapy (exports).
Worker Threads a Cluster
- Worker Threads: skutečná paralelizace CPU-těžký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.
- Cluster: více procesů sdílejících jeden port (round-robin). Zvyšuje propustnost na vícejádrových serverech, ale každý proces má vlastní heap a event-loop.
Nativní doplňky (Addons) a N-API
Pro kritické úseky lze psát nativní moduly v C/C++ přes N-API, které stabilizuje ABI napříč verzemi Node. Hodí se pro obálky knihoven OS, kryptografii, parsování či těžké výpočty. Dohlédněte na přenositelnost, správu paměti a thread-safety.
HTTP server a síťové vzory
Server http/http2 je event-driven: každé spojení je socket s obsluhou request/response jako streamů. Optimalizace: keep-alive, connection reuse, header packing, podpora HTTP/2, compression (zlib/brotli), správné cache-control a ETag. Zvažte obranu WAF-em, rate limiting, časové limity (headersTimeout, requestTimeout), limit body a ochranu proti slow-loris.
Bezpečnost: runtime i závislosti
- Závislosti skenujte (audit), zamykejte verze (
lockfile), minimalizujte attack surface. - Chraňte se proti Prototype Pollution, SSRF, ReDOS (vyhnout se patologickým regexům), XSS v šablonách a path traversal.
- Aktivujte OpenSSL bezpečné křivky/šifry, validujte certifikáty a hostname při outbound spojeních.
Observabilita: metriky, logy a tracing
Měřte event-loop lag, počet otevřených handlerů, využití heapu/CPU, latence endpointů, chybovost. Pro tracing použijte OpenTelemetry a W3C Trace Context. Logy strukturovaně (JSON), s korelačními ID a ochranou osobních údajů.
Diagnostika a profilace
- Inspector (
--inspect, DevTools) pro step-debugging a profiling CPU/heap. - perf/0x/clinic pro plamenové grafy a hledání hotspotů.
- Async Hooks pro sledování životního cyklu asynchronních zdrojů (opatrně – výkonnostní dopad).
Správa procesu: signály, fronty a vypínání
Implementujte graceful shutdown: odchytávejte SIGTERM, uzavřete příjem nových spojení, počkejte na rozpracované požadavky, uvolněte zdroje a ukončete proces s kódem 0. V kontejnerovém prostředí zohledněte liveness/readiness sondy a back-pressure od ingress vrstvy.
Výkonnostní zásady a anti-patterny
- Neprovádějte CPU-těžké smyčky v hlavním vlákně – použijte Worker Threads nebo offload na služby.
- Vyhněte se sync API (
fs.readFileSync) v request-pathu. - 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í metrika.
Správa balíčků a nasazení
package.json definuje skripty (start, build), exportní mapy, typ modulu, engines a bin. Pro rychlé starty používejte prebuild, tree-shaking, minimalizujte runtime transpilační kroky. V produkci zamkněte lockfile, zapněte NODE_ENV=production, hlídejte velikost image a nastavte limity paměti.
Kompatibilita a verze Node
Volte LTS řadu pro produkci. Sledujte semver major změny (např. default ESM chování, nová API v fetch, stabilizace test runner). Testujte s více verzemi CI maticí a definujte minimální podporované verze v engines.
Typické architektonické vzory
- API Gateway/Backend-for-Frontend: agregace dat, cache, rate limit, autorizace.
- Realtime: WebSocket/SSE, pub-sub, škálování přes Redis/Cloud pubsub.
- Job workers: fronty (BullMQ, RabbitMQ), idempotence, deduplikace a plánování.
Závěr
Node.js kombinuje jednovláknový event-loop s neblokujícím I/O a mocnou standardní knihovnou. Pochopení fází smyčky, rozdílu microtasks/macrotasks, role thread-poolu a práce se streamy je zásadní pro stabilní a rychlé služby. Správně navržená architektura – s observabilitou, bezpečností, worker threads pro CPU, a pečlivým shutdownem – umožní škálovat od jednoduchých API po globální realtime platformy.