Quan s’executa Puppeteer en producció durant setmanes, el patró és previsible: la memòria resident del contenidor puja lentament fins que el kernel invoca l’OOM killer. Molts equips culpen una fuga concreta de Node.js, però la realitat és més complexa. La fuga de memòria no està al teu procés Node, sinó als processos fills de Chromium que no es tanquen correctament, a l’estat acumulat d’un navegador que viu massa i a processos orfes que el sistema operatiu continua comptant. Aquest article aborda les tres causes, les solucions pràctiques i, sobretot, el moment en què té més sentit delegar l’execució del navegador a un servei extern, alliberant la teva infraestructura d’aquesta càrrega. A Q2BSTUDIO, empresa de desenvolupament de programari a mida, hem vist aquest escenari en desenes de projectes i sabem que l’enfocament correcte combina higiene de codi, límits de concurrència i una avaluació honesta de si realment necessites Chromium.
Les tres capes del problemaLa primera trampa és mesurar només process.memoryUsage(). Veus el heap de Node pla i creus que no hi ha fuga, mentre la RSS del contenidor puja sense control. Aquesta diferència és la història completa. Chromium executa un arbre de processos: el navegador principal, els renderitzadors per pàgina i els processos de GPU. Quan no tanques una pàgina —perquè una excepció, un timeout o un retorn prematur se salten el finally— aquell procés renderitzador queda retingut. Multiplica això per milers de captures i tindràs una fuga evident. La segona capa és el navegador de llarga durada. Mantenir una instància única de Chromium durant tota la vida del contenidor sembla eficient, però després de milers de navegacions acumula memòria cau, nodes DOM desconnectats i listeners que mai s’alliberen del tot. La tercera capa són els processos zombie: una captura que penja o crasheja deixa un procés Chromium orfe que el cgroup continua comptant, encara que la teva aplicació no ho sàpiga. El resultat és un contenidor que diu 'sense memòria' mentre la teva app afirma utilitzar molt poca.
Solucions pràctiques: tancar, reciclar, acotar i reaparèixerLa correcció més immediata és embolicar cada captura en un bloc try/finally. Tant si la funció llança una excepció, arriba a un timeout o retorna amb èxit: la pàgina s’ha de tancar. El finally assegura que page.close() s’executi sempre. El segon pas és tractar el navegador com a desechable. En lloc de mantenir-ne un d’immortal, recicla’l després d’un nombre fix de treballs (per exemple, 200 captures). Així amortitzes el cost d’arrencada sense deixar que l’estat s’acumuli. El tercer grup de mesures acota el radi d’explosió: llança Chromium amb --disable-dev-shm-usage per evitar el petit /dev/shm dels contenidors, limita el heap de V8 amb --js-flags='--max-old-space-size=512', i posa un límit de concurrència d’acord amb la RAM disponible. Finalment, executa el teu procés sota un init (--init a Docker o tini) perquè els orfes siguin reaped, i força un kill després d’un timeout global perquè cap pàgina penjada deixi un Chromium viu per sempre.
Com identificar l’origen realDeixa d’endevinar. Mesura la RSS total del contenidor i el nombre de processos chrome. Si ambdues pugen mentre el heap de Node es manté estable, el problema està en pàgines sense tancar, en el navegador no reciclat o en zombies. Un comptador simple de pàgines obertes vs. tancades et dirà si estàs perdent pàgines. També pots prendre dos snapshot del heap d’un context de navegador abans i després d’una hora de trànsit representatiu i buscar nodes DOM desconnectats o listeners retinguts. Aquesta traça et porta directe a un manejador page.on(...) que vas oblidar eliminar o a la pàgina web que estàs capturant. En entorns cloud com AWS o Azure, on cada servei competeix per recursos, una fuga així pot disparar els costos d’escalat. Per això a Q2BSTUDIO integrem pràctiques de cloud AWS/Azure i monitorització de processos per detectar aquestes fugues abans que arribin a producció.
I si no necessites Chromium en absolut?Aquí arriba la pregunta incòmoda. Si el teu únic objectiu és obtenir una imatge d’una URL, tot aquest esforç —higiene de memòria, reciclatge de processos, reaping de zombies, tuning de contenidors— és infraestructura per a una tasca que es podria resoldre amb una petició HTTP a una API de captura de pantalla. Els serveis allotjats executen Chromium per tu i et tornen la imatge directament, sense que hagis de llançar, tancar ni vigilar res. El cost operatiu desapareix: no hi ha processos a reaper, no hi ha gràfics de memòria a mirar, no hi ha pàgines sense tancar. Per a equips que desenvolupen aplicacions a mida, intel·ligència artificial o agents d’IA que necessiten entendre visualment un web, externalitzar la captura permet centrar-se en la lògica de negoci i en la integració amb sistemes de BI/Power BI o ciberseguretat. A Q2BSTUDIO hem ajudat clients a migrar des d’un clúster de Puppeteer autogestionat a una arquitectura on la captura és un recurs més, orquestrat des de plataformes d’IA o automatització. La decisió no és binària: Puppeteer continua sent l’eina correcta quan necessites interactuar amb la pàgina (fer clic, omplir formularis, extreure dades estructurades). Però si només vols una foto, la solució més duradora és no executar el navegador tu mateix.
Conclusió: tres problemes, un abric, i una sortidaLes fugues de memòria de Puppeteer en producció no són un únic bug: són pàgines sense tancar, un navegador que no es recicla i processos zombies. Es corregeixen amb finally, reciclatge, límits de concurrència i un init process. Però després d’aplicar tot això, encara val la pena preguntar-se si l’esforç de mantenir una flota de Chromium és proporcional al valor que aporta. Quan la resposta és 'només necessito una captura', la solució més robusta és delegar. Així deixes de vigilar la RSS i comences a construir sobre una base que no es fuig.




