Objavljeno
13. maj 2026
Avtor: Simon Zajdela
Problem ni bil Mermaid. Problem je bil hladen zagon.
Za diagram rendering sem najprej naredil nekaj zelo običajnega: server sprejme Mermaid kodo, jo zapiše v začasno datoteko in pokliče mermaid-cli. Delovalo je. Bilo je enostavno. Bilo je tudi počasno kot tank, ki ga za vsak strel posebej pripelješ iz skladišča.
Meritev je bila dovolj jasna: približno tri sekunde za en SVG. Za interni ali redko uporabljen endpoint bi bilo to mogoče še sprejemljivo. Za javni diagram service, kjer želiš hitro povratno informacijo, pa ne. Ne zato, ker Mermaid ne zna renderirati hitro, ampak zato, ker je vsak request plačal ceno novega procesa in novega Chromium zagona.
Prva verzija: uradna pot, uradna bolečina
Klasičen pristop je zelo direkten: HTTP request pride na server, server zažene mmdc, mmdc zažene Chromium, Chromium nariše diagram, rezultat se vrne uporabniku, proces pa se konča. To je lepo izolirano, ampak zelo drago, če to delaš za vsak request posebej.
Docker je dodal še svojo zabavo: Chromium sandbox, crashpad, sistemske knjižnice, non-root user in vse male Linux posebnosti, zaradi katerih se človek začne pogajati z apt-get kot z razvajenim printerjem. Ko je zadeva končno delovala, je bila funkcionalna, ne pa elegantna.
Diagram
Optimizacija: Chromium naj ostane živ
Ključna sprememba je bila preprosta: ne zaganjaj rendererja za vsak request. Ob zagonu NestJS aplikacije odpremo en headless Chromium proces, ustvarimo eno stran, vanjo naložimo Mermaid runtime iz node_modules in inicializiramo Mermaid samo enkrat.
Ko pride request, server ne kliče več CLI-ja. Namesto tega pošlje Mermaid kodo v že odprt browser context in pokliče mermaid.render. Za SVG niti screenshot ni potreben. Browser vrne SVG string, server ga zapakira kot image/svg+xml in pošlje nazaj.
Kaj to pomeni za concurrency
Ker je prva produkcijska verzija uporabljala eno browser page instanco, sem dodal enostavno interno queue. Če prideta dva requesta istočasno, se ne pomešata. Drugi počaka, prvi konča, potem se izvede drugi. Pri renderju pod 200 ms je to čisto sprejemljiv kompromis.
Naslednja stopnja bi bil pool več pageov. En Chromium proces lahko drži več izoliranih page contextov, recimo dva ali štiri. To omogoča vzporedno renderiranje, hkrati pa ne odpira novega browserja za vsak diagram. Ampak prva verzija z queue je bolj mirna, bolj predvidljiva in dovolj hitra za majhen javni service.
Spomin, Docker in produkcijska higiena
Pri long-running Chromium procesu je glavno vprašanje RAM. Zato je smiselno po renderju počistiti DOM, posebej če Mermaid ali browser context sčasoma zadrži elemente. To je majhen cleanup, ki skoraj nič ne stane, lahko pa prepreči počasno nabiranje smeti.
Docker image ostane razumen, če uporabljamo sistemski Chromium in ne prenašamo dodatnega Puppeteer browserja. Pomembno je nastaviti non-root userja, writable tmp direktorije za Chromium profile/crashpad in minimalen nabor sistemskih knjižnic. Ni glamurozno, je pa točno tisti del, ki loči demo od deployane storitve.
Rezultat
Praktični rezultat je bil preskok iz približno treh sekund na manj kot 200 ms za SVG render. CPU se pri osnovnih diagramih skoraj ne premakne, ker najdražji del — zagon browserja — ni več del request patha.
To je ena tistih optimizacij, kjer ni bilo treba dodati kompleksnega cachea, Redis instance ali posebnega worker sistema. Dovolj je bilo premakniti težko inicializacijo iz requesta v startup. Včasih production-ready ne pomeni več infrastrukture, ampak manj ponavljanja neumnosti. Tank naj ostane na igrišču.
