1. Demystificando las fugas de memoria en JavaScript
Hola a todos. Mi nombre es Ruden Britschwurter. Soy miembro del TC de NodeJS. Trabajo como arquitecto de software principal en MyConvolt, y estoy feliz de estar aquí hoy en la conferencia de Node. Hoy voy a hablar sobre cómo desmitificar las fugas de memoria. ¿Son realmente tan difíciles de resolver? ¿Y cómo podemos facilitar el proceso? Una fuga de memoria ocurre cuando un programa informático administra incorrectamente la memoria de acceso aleatorio de manera que la memoria que ya no se necesita no se libera. Y la línea azul claramente muestra una fuga de memoria porque con el tiempo se asigna más y más memoria sin liberarla. Y esto es malo. Entonces, ¿cómo manejamos la memoria en JavaScript en particular? ¿No tenemos que preocuparnos por eso, verdad? Todo esto se hace de forma transparente y perfecta. Y la memoria se libera automáticamente. ¿Por qué debería haber una fuga de memoria en primer lugar?
Mi nombre es Ruden Britschwurter. Soy miembro del TC de NodeJS. Trabajo como arquitecto de software principal en MyConvolt, y estoy feliz de estar aquí hoy en la conferencia de Node.
Y hoy voy a hablar sobre cómo desmitificar las fugas de memoria. A menudo se considera que las fugas de memoria son algo difícil y complicado de resolver. ¿Pero es eso realmente así? ¿Y cómo podemos facilitar el proceso?
Para responder a eso, primero me gustaría responder a la pregunta de qué es realmente una fuga de memoria. Y estoy consultando Wikipedia para responder a esa pregunta. Una fuga de memoria ocurre cuando un programa informático administra incorrectamente la memoria de acceso aleatorio. De manera que la memoria que ya no se necesita no se libera. Con el tiempo, acumulamos más y más memoria. Y en el peor de los casos, el programa puede llegar a bloquearse porque no hay más memoria para asignar. Y ese es realmente el peor escenario. O tal vez estés en un entorno en la nube y tengas que pagar mucho más dinero porque tienes un escalado automático activo y se asigna más y más memoria.
Aquí tenemos un gráfico que muestra claramente cómo se vería una fuga de memoria en comparación con el uso de memoria y el tiempo de ejecución del programa. Y la línea amarilla representa un programa perfecto en su mayoría. Comienza y se asigna algo de memoria, luego hay altibajos y esto es perfecto para cada programa en su mayoría. A veces los picos pueden ser más altos o bajar un poco, pero en promedio es una línea plana. Y la línea azul claramente muestra una fuga de memoria porque con el tiempo se asigna más y más memoria sin liberarla. Y esto es malo.
Entonces, ¿cómo manejamos la memoria en JavaScript en particular? ¿No tenemos que preocuparnos por eso, verdad? Todo esto se hace de forma transparente y perfecta. Y la memoria se libera automáticamente. ¿Por qué debería haber una fuga de memoria en primer lugar? La memoria se divide en una memoria de pila y una memoria de montón. La memoria de montón es una memoria asignada dinámicamente y la pila la realiza el sistema operativo en un caso típico. Cada hilo tiene algo de memoria de pila. Es un algoritmo de último en entrar, primero en salir, también llamado LIFO. Es muy simple, muy rápido y la memoria asignada en la pila se recupera automáticamente cuando la función sale. Cuando comparamos eso con la memoria de montón, hay muchas cosas sucediendo porque la memoria de pila normalmente solo contiene punteros a la función que se está ejecutando actualmente. Y si empujamos en la pila el puntero que se está ejecutando actualmente, llegamos al montón, que es la memoria asignada dinámicamente. Y aquí tenemos una buena descripción general de V8 que se hizo para el Times of India.
2. Asignación de memoria y recolección de basura
Hay muchas cosas sucediendo. Se asignan JSObjects, tenemos código fuente de JavaScript, código optimizado, código de expresiones regulares, cadenas y mucho más. La memoria del montón, en particular en V8, se divide en tres áreas. Tenemos la generación joven, la generación intermedia y la generación antigua. El recolector de basura se encarga de liberar automáticamente la memoria que asignamos, pero a veces puede volverse inestable.
Hay muchas cosas sucediendo. Se asignan JSObjects, tenemos código fuente de JavaScript, código optimizado, código de expresiones regulares, cadenas y mucho más. Esta es la memoria principal de nuestro programa en este caso.
Y la memoria del montón, en particular en V8, se divide en tres áreas. Nuevamente, tenemos la generación joven. Entonces, tan pronto como asignamos una nueva variable, digamos que decimos que let pool es la cadena 'test', entonces se asignará la memoria 'test' y se colocará en la generación joven. Esta memoria es relativamente pequeña. En su mayoría, JavaScript tendrá variables intermedias que se utilizan para calcular el siguiente valor inmediatamente. Y no necesitas la variable tan pronto como calculas tu próximo valor. Todo esto se hace de forma síncrona. Por lo tanto, queremos desechar toda esa memoria que no necesitamos lo antes posible. Entonces, esta generación joven solo sobrevivirá a una ejecución de 'scavenge'. Esta es la primera ejecución en la que nuestro programa crea una memoria y trata de deshacerse de las variables que ya no se utilizan. Y si alguna variable sobrevive a esa ejecución, se empuja a la generación intermedia. Y si sobrevive a la segunda ejecución, entonces se empuja a la generación antigua. Esta es la parte más grande de la aplicación. Normalmente se utiliza para variables largas y utilizadas. Cosas que reutilizas en cada aplicación que pueden contener punteros a muchas cosas. Y utilizamos un algoritmo diferente para liberar la memoria en este caso. El algoritmo utilizado se llama MarkSweep. Comenzamos desde nuestro objeto raíz. El objeto raíz en un navegador sería el objeto window, y en Node.js es global. Mientras que en JavaScript moderno estaría en ambos, solo globalmente disponible. Y utilizamos un algoritmo similar a un algoritmo recursivo donde comenzamos con el objeto raíz y simplemente conectamos cada punto. Intentamos conectar cada nodo que esté de alguna manera conectado a su objeto raíz. Todos los demás nodos, variables o asignaciones se liberarán. Y esto debería ser ideal para liberar toda la memoria que no se utiliza.
Ya hablé de que como desarrolladores no tenemos que preocuparnos por la memoria que asignamos porque se liberará automáticamente. Lo que se hace en segundo plano es que se ejecuta un recolector de basura y ese recolector de basura tiene mucho que hacer y en algún momento incluso puede volverse inestable y no funcionar como anticipamos. Así que tenemos que analizar eso un poco más de cerca.
3. Problemas y fugas de memoria típicos
Un tipo de dato incorrecto y ejecutar demasiadas cosas en paralelo pueden causar problemas de memoria. Los escuchadores de eventos y los closures también pueden contribuir a las fugas de memoria. Los descriptores de archivos, cuando no se cierran correctamente, también pueden causar problemas. Es crucial comprender los detalles de implementación de JavaScript.
¿Cuáles son las fugas de memoria o problemas típicos? Esto no está en un orden específico y no todos son fugas de memoria reales, por ejemplo, los primeros dos. Entonces, un tipo de dato incorrecto sigue siendo algo en lo que tenemos que investigar porque esto es algo muy típico que encuentro con frecuencia. A menudo, los programas utilizan tipos de memoria que no son eficientes para la tarea que se desea resolver. Y luego, cuando manejas un data grande como este, vas a asignar una gran cantidad de memoria, aunque no necesitarías eso, simplemente usando un tipo de data diferente.
Usar el tipo de data correcto también puede ayudarte a evitar que tu programa se bloquee en caso de que encuentres un data más grande en algún momento. Y el segundo es que también intentamos ejecutar muchas cosas en paralelo. Probablemente a todos nos encantan las promesas y el async/await, y usamos Promise.all para mejorar el rendimiento de la aplicación, porque normalmente, como con Node, las cosas se ejecutan en un solo hilo y aún queremos hacer varias cosas al mismo tiempo, principalmente para tener múltiples llamadas remotas a procedimientos. Pero cuando simplemente usas demasiadas de esas al mismo tiempo, también tenemos que asignar memoria para todas estas etapas. Y cuando aún no se han completado, también pueden causar una excepción en tu programa porque no queda más memoria. Así que esto también es importante tener en cuenta.
Ahora vamos a la primera fuga de memoria real, los escuchadores de eventos. Algo que mucha gente sabe es que en los navegadores, hace algún tiempo, era típico adjuntar escuchadores de eventos inmediatamente a un elemento DOM específico en una lista a la que te importaría que se desencadenara un evento. Y luego a veces tenemos que agregar muchos y muchos escuchadores de eventos a todos estos elementos. En su lugar, quieres tener un solo escuchador de eventos en un nodo superior, que luego escuche todos los eventos que provienen de todos estos nodos inferiores. Y luego no tienes que adjuntar tantos escuchadores porque los escuchadores de eventos también ocupan memoria. Y a veces agregamos un nuevo escuchador de eventos para cada evento entrante. Esto sería un error de programación, pero ocurre con frecuencia.
Y más difícil aún son los closures. Porque a veces los closures evitan que el recolector de basura sepa cuándo una variable ya no se utiliza. Y cuando podemos liberar esa memoria. Así que este es un caso complicado. Luego, los descriptores de archivos también pueden causar problemas. Porque cuando abres un archivo, hay una cantidad limitada de archivos que puedes abrir al mismo tiempo. Y cuando ejecutamos nuestro programa, normalmente funciona, aunque no cerremos el archivo después de abrirlo, eso también puede causar problemas. Y siempre debemos asegurarnos de cerrarlo tanto en caso de éxito como en caso de no éxito. Por lo tanto, siempre debes tener un enlace de archivo que, sin importar si abres el archivo con éxito o no. Porque a veces puede haber algo intermedio y aún lo abres. Pero hubo un error en otro lugar. Y aún tienes que cerrarlo. La parte más complicada es que en JavaScript, y cuando miramos en Chrome o cualquier navegador basado en Chromium y Node.js también se ejecuta con V8, entonces tenemos que conocer algunos detalles de implementación.
4. Lectura de datos en fragmentos
Leer demasiados datos en un solo fragmento puede ser ineficiente y provocar problemas de asignación de memoria. En cambio, transmitir datos en fragmentos pequeños es más rápido y elimina la necesidad de una asignación excesiva de memoria.
Voy a profundizar en algunos de ellos en un momento. Aquí hay un ejemplo de lo malo que puede ser si leemos demasiados data en un solo fragmento también. Entonces, tenemos un servidor HTTP y leemos un archivo grande, el usuario solicita un archivo. Esto va a llevar tiempo y no va a ser eficiente y va a asignar mucha memoria porque primero tenemos que leer toda esa memoria en la aplicación y una vez que esté completamente cargada, comenzaremos a enviar esos data al usuario, no antes. Y cuando no hay un solo usuario sino muchos usuarios que solicitan la misma ruta al mismo tiempo, también podríamos tener un problema de memoria porque entonces simplemente la memoria total disponible ya no será suficiente y la aplicación se bloqueará. En cambio, solo debemos preocuparnos por transmitir los data en fragmentos pequeños. Será mucho más rápido y no tendremos que preocuparnos por la asignación de memoria más. Simplemente obtenemos la primera parte del archivo que queremos leer y no importa si tiene varios gigabytes de tamaño, aún podemos enviarlo de inmediato en, digamos, unos pocos kilobytes. Y tan pronto como termine, habrá terminado. La memoria en línea será muy plana. No tenemos que asignar más que los pequeños fragmentos que queremos leer.
5. Manejo de Descriptores de Archivos y Event Listeners
Y con los descriptores de archivos, también nos preocupamos por manejar el error. Abrimos el archivo, queremos escribir algo en él, pero nunca cerramos ese archivo nuevamente. Los event listeners podrían verse así. Imaginemos que tenemos una base de datos basada en eventos y queremos escribir algo en ella. Las cadenas son, como mencioné, tenemos esta cadena muy larga, de un millón de caracteres, y solo nos importan los primeros 20 caracteres. Aún es difícil prevenirlo, pero deberíamos preocuparnos por ello. De lo contrario, la aplicación podría colapsar y esta es la peor situación. Y si sucede, bueno, tenemos que lidiar con esa situación y tratar con esas fugas de memoria puede ser muy problemático. Tenemos que saber cómo identificar los puntos que causan esa fuga de memoria. Y esta es la parte más importante para mí. Así que aquí es donde nos vamos a enfocar ahora, cómo detectar y solucionar esas fugas de memoria.
Y con los descriptores de archivos, también nos preocupamos por manejar el error. Abrimos el archivo, queremos escribir algo en él, pero nunca cerramos ese archivo nuevamente. Y si lo hacemos muchas veces, la aplicación también tendría un problema. Siempre debemos asegurarnos de cerrar ese descriptor de archivo. Y eso se hace en el blog de la última semana, como mencioné antes.
Los event listeners podrían verse así. Imaginemos que tenemos una database basada en eventos y queremos escribir algo en ella. Y tenemos un event listener en data. Entonces hay un servidor HTTP y quiere conectarse a una database y quiere escribir algo en ella. Escribimos en ella y luego habrá una respuesta en el evento data. Pero en este caso, cometimos un error porque en cada solicitud de usuario, agregamos un nuevo event listener a esa database en lugar de solo uno al principio. Y luego tenemos muchos event listeners al final que son todos muy pequeños, pero terminará como una línea azul que viste antes. Y esta es la peor, prácticamente. Es muy frecuente verlo reportado tanto en los informes de problemas de Node.js como en los de V8 porque esta es una parte específica de V8 que simplemente debes conocer.
Las cadenas son, como mencioné, tenemos esta cadena muy larga, de un millón de caracteres, y solo nos importan los primeros 20 caracteres. Entonces cortamos los 20 y normalmente podrías imaginar que el resto simplemente se leería porque ya no lo usamos. En cambio, solo tenemos una referencia porque internamente en V8 hay muchas optimizaciones en marcha y trata de ser muy rápido y eficiente en memoria al mismo tiempo. Es una heurística que se utiliza que puede que no siempre funcione como se anticipa. Imaginemos que tenemos la cadena 'test' y la cadena 'ABC' y quieres compararlas. En lugar de asignar un nuevo fragmento de memoria que consistiría en 'test A, B, C', simplemente asignaría algo nuevo y muy pequeño que apunta a la cadena 'test' y a la cadena 'ABC' y diría, soy la combinación de ambas. Es como una estructura de árbol que podrías imaginar. Cuando queremos cortar algo de nuevo, también simplemente apuntará a la cadena original y dirá, hey, tengo este punto de inicio y este punto final de esa cadena, pero no liberará el resto de la cadena aunque ya no se use. Y eso podría terminar en una situación difícil donde ensuciamos nuestro programa una y otra vez y como todos sabemos, no debemos ensuciar nuestro entorno. Aún es difícil prevenirlo, pero deberíamos preocuparnos por ello. De lo contrario, la aplicación podría colapsar y esta es la peor situación. Y si sucede, bueno, tenemos que lidiar con esa situación y tratar con esas fugas de memoria puede ser muy problemático. Incluso podríamos tener problemas de ira y pasar mucho tiempo investigando algo sin saber realmente qué buscar. Tenemos que saber cómo identificar los puntos que causan esa fuga de memoria. Y esta es la parte más importante para mí. Así que aquí es donde nos vamos a enfocar ahora, cómo detectar y solucionar esas fugas de memoria.
6. Herramientas y Técnicas para Identificar Fugas de Memoria
Existen herramientas y banderas para identificar fugas de memoria en Node.js, como 'inspect' y 'trace GC'. La bandera 'inspect' te permite conectarte al inspector de Chrome u otras herramientas. El comando 'trace GC' muestra lo que hace el recolector de basura en diferentes momentos. La bandera 'abort on uncaught exception' provoca un volcado de memoria cuando la aplicación se bloquea. También se pueden utilizar herramientas como LM node. Para obtener más información, visita la guía de depuración de nodes.js.org. Ahora te mostraré algunos ejemplos de código, incluido un programa que confunde al recolector de basura. Podemos usar la bandera 'inspect' para ver el objetivo remoto y explorar capturas de memoria.
Existen muchas herramientas y también banderas para identificar fugas de memoria en Node.js. La primera es '--inspect', que se puede pasar al tiempo de ejecución de Node.js. Luego puedes conectarte, por ejemplo, al inspector de Chrome, pero también a otras herramientas. También tenemos una bandera llamada 'trace GC'. Con el comando 'trace GC', es posible ver lo que hace el recolector de basura en diferentes momentos. También es interesante saber sobre esto.
A veces es muy difícil identificar realmente el error. En ese caso, es posible que desees usar la bandera '--abort-on-uncaught-exception', porque esto automáticamente provocará un volcado de memoria. Un volcado de memoria es el estado de la memoria en ese punto de tiempo actual. Entonces, cuando la aplicación se bloquea debido a esa excepción, podemos ver cómo era la aplicación en términos de memoria en ese estado. También hay más herramientas como LM node para investigar esto. Si te interesa más herramientas, simplemente visita el sitio web nodes.js.org/guides/debugging/getting-started y encontrarás mucha información al respecto.
Y quiero mostrarte algo de código en realidad. Por ejemplo, para la cadena uno. En este caso, tengo un pequeño programa y estoy usando el módulo V8 de Node.js y tenemos una variable llamada 'count' que simplemente se inicializa en cero, y tenemos una variable 'pool'. Tenemos la función 'run', que hará algunas cosas que veremos en un momento. Tenemos este intervalo que se ejecuta cada milisegundo. La función 'run' se activa cada milisegundo y luego creamos capturas de memoria cada dos segundos para conocer el estado de la aplicación en ese punto de tiempo. Aquí tenemos esta función interna que hace referencia a la original, mientras que la original hace referencia a 'pool' y 'pool' se actualiza en cada ejecución. Así que aquí simplemente confundimos al recolector de basura debido a la forma en que todas estas variables están conectadas entre sí. Veamos qué sucede en este caso. Esto sería esto. Quiero revisar ese programa. Ahora abro 'drone inspect' y gracias a la bandera '--inspect', ahora puedo ver el objetivo remoto. Voy a inspeccionarlo. Y aquí ves esta vista general, selecciona el perfil de tubería, captura de memoria, muestreo de asignación, etc. Quiero ver algunas de estas capturas de memoria.
7. Análisis de la Asignación de Memoria
Así que voy a cargar uno. El programa crea nuevos a lo largo del tiempo. Podemos comparar uno de estos montones y ver dónde se fue toda tu memoria. Por ejemplo, hay una gran diferencia en el delta de tamaño de 2.4 megabytes. Vemos que hay muchas cadenas. Ahora sabemos que tiene algo que ver con funk. Definitivamente sabemos que asignamos demasiadas de estas cadenas porque las puedes ver todas. Son muy grandes y no están libres. Tienes que investigar eso. También es posible usar esto en un navegador. Puedes iniciarlo.
Y como puedes ver, el programa crea nuevos a lo largo del tiempo. Así que vamos a abrir el primero. Y vamos a abrir el último. Y también voy a detener el programa. Ahora tenemos estos dos, porque aquí puedes investigar y hay muchas data sucediendo. Y no está muy claro qué buscar. Pero podemos compararlo simplemente haciendo clic en comparación. Y ahora podemos comparar uno de estos montones, no solo instantáneas con el otro. Y luego es mucho más fácil ver dónde se fue toda tu memoria. Por ejemplo, aquí podemos ver que hay una gran diferencia en el delta de tamaño de 2.4 megabytes. Así que claramente este es un punto en el que quieres investigar. Y cuando investigamos, vemos que hay muchas cadenas. Estas cadenas, nuevamente, cuando las miramos, están en esta variable, en esta cadena. Y la cadena está en un objeto específico en ese punto de memoria. Y esto está en el contexto original y del sistema. De acuerdo, el contexto está en funk. Ah, esto es algo que conocemos en nuestro programa. Así que sabemos que funk estuvo aquí. Este es nuestro funk. Y luego simplemente puedes identificar la causa real. Ahora sabemos que tiene algo que ver con funk. Aunque no lo veas de inmediato, definitivamente sabemos que asignamos demasiadas de estas cadenas porque las puedes ver todas aquí. Y son muy grandes. Y no están libres. Así que sabes que tienes que investigar eso.
El tiempo se me está acabando, así que tengo que apurarme un poco. Pero quiero decir que también es posible usar esto en un navegador. Porque esta tooling también está disponible allí. Y por ejemplo, puedes iniciarlo.
8. Solucionando Problemas de Fugas de Memoria y Herramientas
Haz clic en Inspeccionar, ve a memoria y mira la descripción general. Otro programa, StringCrash, ejemplifica el problema. Pasos para solucionar la fuga de memoria: monitorear la memoria, perfilar con herramientas como Inspeccionar y tomar capturas de pantalla. Ten cuidado, las capturas de pantalla son costosas. Compara las capturas de pantalla para identificar la fuga y solucionarla. Hay numerosas herramientas disponibles para ayudar, pero recuerda que la memoria es costosa y puede ralentizar tu sistema.
Y al hacer clic en Inspeccionar, y luego ir a memoria. Y aquí está también la descripción general de eso. Quiero mostrarte un poco más de otro programa rápidamente. Por ejemplo, esto es StringCrash. Así que aquí, tengo ese problema del que había hablado con el V8. Podemos ver el código. Esto está haciendo exactamente lo que había mencionado antes. Y el programa se bloquea muy rápido porque no puede asignar más memoria.
Muy bien. Así que los pasos para solucionar la fuga de memoria. En primer lugar, por favor, monitorea tu memoria. Usa, por ejemplo, un APM o algo así. Y luego sabrás cómo se ve tu perfil de memoria. Perfílalo con herramientas como Inspeccionar. Y toma muchas capturas de pantalla. Pero ten en cuenta que son muy costosas. No querrás hacerlo en producción en la mayoría de los casos. Siempre intenta hacerlo en un entorno controlado que sea el aeropuerto de testing. Si tienes que hacerlo en un entorno de producción, entonces puedes hacerlo con una configuración muy estricta en un tiempo de espera, solo en un contenedor específico o algo así, para hacerlo muy poco frecuente. Y luego puedes comparar esas muchas capturas de pantalla. Esa es una de las formas más eficientes de identificar la fuga desde mi perspectiva. Y luego simplemente puedes identificarla y solucionar el problema tan pronto como sepas de qué se trata.
Hay muchas herramientas que pueden ayudarte. Puedes consultar en línea en el sitio web del que había hablado. La memoria es costosa, no solo en términos de costo cuando tienes que pagar dólares o cualquier otra moneda por ella, sino también en términos de performance, porque cuando asignas más y más memoria, tu sistema se ralentizará. Así que realmente quieres evitar eso. Las capturas de pantalla de los montones son costosas por una razón similar. Será como una aplicación congelada en ese punto del tiempo. No puede hacer nada más mientras recopila toda la memoria de la aplicación.
9. Solucionando Problemas de Fugas de Memoria y Reiniciando Aplicaciones
Elige cuidadosamente tu tipo de datos y tu estructura de datos. Intenta solucionar el problema real en lugar de reiniciar la aplicación. Uno de mis días más productivos fue deshacerme de 1,000 líneas de código. El 71% es un problema común que la gente encuentra. Reiniciar la aplicación no es una solución duradera.
Elige cuidadosamente tu tipo de datos y tu estructura de datos, y solo reinicia tu aplicación como último recurso. Intenta solucionar el problema real en su lugar.
Así que muchas gracias por estar aquí. Quiero terminar con una de mis citas favoritas de Ken Thompson. Uno de mis días más productivos fue deshacerme de 1,000 líneas de código, y puedo relacionarme muy bien con eso. Que tengas un buen día.
Hola. Hola, Ruben. Es un placer verte aquí en el escenario conmigo. Es un honor tenerte aquí, por supuesto. ¿Qué opinas? ¿El 71% es el porcentaje que esperabas? Más o menos. Es realmente algo común que la gente encuentra. Y es algo que a menudo las personas simplemente intentan solucionar en lugar de profundizar en el problema y arreglarlo. He visto aplicaciones que se reinician al menos una vez al día porque tenían una fuga de memoria que no podían encontrar, o que intentaron simplemente reducir el uso de memoria en general o aumentar la memoria que podían usar, y cosas así. Y antes de tener que reiniciar la aplicación, eso es algo que obviamente no es agradable. Tienes que pagar mucho dinero por eso. Así que solucionar esos problemas es realmente importante. Sí, realmente lo es. Y sí, reiniciar no es realmente una solución duradera, por supuesto.
10. Manejo del Ejemplo de Recorte de Cadena
Para evitar mantener la cadena original en memoria al usar el ejemplo de recorte de cadena, es necesario comprender los detalles internos de V8. Ciertas operaciones, como aplanar cadenas, pueden asignar un nuevo fragmento de memoria y liberar la memoria original. La biblioteca 'flat string' en NPM puede ayudar con esto al crear internamente una nueva cadena como un tipo de datos diferente. Es importante tener precaución al asignar cadenas para evitar problemas de memoria.
Quiero responder a la primera pregunta de uno de nuestros miembros de la audiencia. Es una pregunta de I Am. La pregunta es, ¿cómo manejarías mejor el ejemplo de recorte de cadena para evitar que la cadena original se mantenga en memoria? En este caso, debes conocer los detalles internos de V8, y comprender cómo funcionan estas cosas. Y hay algunas operaciones que aplanarían las cadenas. Por lo tanto, asignarían un nuevo fragmento de memoria completo para la ruta que se requiere en tu caso de uso. Y luego no habría una referencia sólida a la memoria original que asignaste, y luego se liberaría. Hay una biblioteca que en realidad hace eso como una operación muy simple. Originalmente, simplemente convertía la cadena a un número en el medio. Luego se convertiría internamente. Y trata de crear una nueva cadena internamente en V8. Simplemente usa un truco para hacer que esa cadena internamente sea un tipo de datos diferente. Y se llama flat string, escrito como F-L-A-T-S-T-R. Y puedes echarle un vistazo en NPM. Sí, exactamente. Puedes verlo allí y simplemente revisar algunos benchmarks. Pero en su mayoría, debes tener cuidado en cómo asignas cadenas para evitarlo en primer lugar. Trata de evitarlo, por supuesto. Pero en lugar de parchearlo más adelante.
Identificación de Fugas de Memoria y Herramientas
Existen herramientas específicas para identificar fugas de memoria, que se pueden encontrar en el sitio web de Node.js. Al identificar una fuga de memoria, es importante monitorear tus recursos y utilizar herramientas como un APM para detectar las fugas. Para inspeccionar e identificar fugas de memoria en las funciones de AWS Lambda o Google Firebase, debes ejecutar el código en un entorno donde tengas control total y puedas crear volcados de montón. Se recomienda hacer esto en un entorno de preparación. Las pruebas automatizadas para fugas de memoria no son comunes, pero el monitoreo puede ayudar a identificar cuando el uso de memoria excede los límites esperados. Git bisect se puede utilizar para rastrear cambios que pueden haber causado fugas de memoria.
La siguiente pregunta es de CreoZot. ¿Existen herramientas específicas para identificar fugas de memoria que puedas recomendar? Así que mencioné la página web a la que podrías ir. Y hay muchas herramientas diferentes que puedes usar. Está en el sitio web de Node.js. Y te recomiendo encarecidamente que le eches un vistazo.
De acuerdo. La siguiente pregunta es de Alexey. ¿Cómo inspeccionar e identificar fugas de memoria en las funciones de AWS, Lambda, o Google Firebase? Debería ser similar, de todos modos. Siempre que tengas acceso a ello, debería serlo. Bueno, en primer lugar, debes identificar la fuga, que tienes una fuga. Eso es normalmente lo que se hace con el monitoreo también. Normalmente monitoreas tus recursos. Esa es la primera parte. Utilizas, por ejemplo, un APM para eso. Y tan pronto como identifiques que hay una fuga, quieres ejecutar el código en cualquier entorno en el que tengas control total y puedas introspectar. Y puedes, por ejemplo, crear siempre volcados de montón en cualquier entorno. Esa sería una forma de hacerlo. Esa sería una de las formas que podrías usar. Personalmente, prefiero los volcados de montón, y obviamente debes intentar no hacerlo en producción. Siempre intenta hacerlo en un entorno de preparación, si es posible. De acuerdo. La siguiente pregunta es de André Calazans. ¿Has visto o realizado pruebas automatizadas para fugas de memoria? ¿Y sabes si hay alguna forma de diferenciar fugas en vivo de fugas de memoria reales? No he visto pruebas reales para fugas de memoria, pero si lo monitoreas, normalmente deberías recibir una notificación tan pronto como en cualquier parte de tu aplicación la memoria se descontrole, porque normalmente establecerías un límite para la memoria que se espera que uses. Y tan pronto como alcance ese punto, deberías echar un vistazo más de cerca. Y luego tal vez volver a tu historial de Git y ver qué ha cambiado allí. Es un proceso manual a partir de ahí, pero al menos tienes una marca de tiempo. Algo salió mal después de esa implementación. Entonces, en este caso, podrías usar fácilmente git bisect. Y git bisect es una característica muy poderosa.
Identificación y Solución de Fugas de Memoria
Escribe una prueba para desencadenar la fuga de memoria e identificar el código que la causa. Utiliza el método bisect para encontrar la confirmación que introdujo la fuga.
Entonces, escribe una prueba que desencadene la fuga de memoria. Así sabrás al menos qué la está causando en algún lugar, incluso si aún no la has identificado. Y luego quieres identificar el código. Así sabrás, bien, cuando ejecuto este código, y luego en algún momento, por ejemplo, la aplicación se bloquea después de unos segundos o algo así. Y entonces sabes que la fuga está ahí. Luego la bisectas, y eso es algo rítmico donde simplemente avanzas desde ambos lados. Y si no se bloquea, entonces sabrías que este código no es una fuga. Y así puedes bisectarlo hasta encontrar la confirmación exacta que introdujo la fuga. Inteligente.
Grandes Objetos JSON y Límites de Memoria
¿Pueden los grandes objetos JSON almacenados en memoria causar fugas de memoria? Existe un límite máximo para cada tipo de dato. Para las cadenas de texto, es aproximadamente 2 elevado a la potencia de 28 caracteres. Superar este límite resultará en un error. Sin embargo, no tengo conocimiento de ningún límite para los objetos JSON. Afortunadamente, normalmente no alcanzo esos límites.
La siguiente pregunta es de Alexei nuevamente. ¿Se pueden almacenar grandes objetos JSON en memoria? Perdón. ¿Los grandes objetos JSON almacenados en memoria pueden causar fugas de memoria? La estructura JSON no es particularmente más propensa a causar fugas de memoria que cualquier otra estructura de datos. Así es. Pero creo que Alexei se refiere a si es un problema si un objeto JSON se vuelve demasiado grande. En general, hay un límite máximo para cada tipo de dato. Muy cierto. Por ejemplo, para las cadenas de texto, creo que, si recuerdo correctamente, es aproximadamente 2 elevado a la potencia de 28 caracteres que una cadena puede tener. Y tan pronto como se supera ese límite, se producirá un error. Y probablemente tendríamos ese caso si tienes el JSON como una cadena de texto. Y si lo tienes como un objeto. No conozco ningún límite en este caso. Puede que exista uno. Y definitivamente hay uno para algunos tipos de datos. Pero afortunadamente, normalmente no alcanzo esos límites.
Experiencia de Ruben con las Fugas de Memoria
Ruben comparte su experiencia con las fugas de memoria, recordando una vez en la que tuvo que reiniciar una aplicación todos los días debido a una fuga de memoria. Menciona que en ese momento no pudo depurar la aplicación. Ruben también menciona que ha tenido la suerte de no encontrarse con ninguna fuga de memoria personal en los últimos años.
Qué suerte tienes. La última pregunta de la que tenemos tiempo. Y es una pregunta de George Turley. Dado que estamos hablando de fugas de memoria, ¿cuál fue la peor fuga de memoria con la que te encontraste? Y si pudiste encontrarla, ¿cómo la solucionaste si pudiste? La peor que encontré personalmente... ya fue hace un tiempo. Probablemente sea la que mencioné antes en la que tenía que reiniciar la aplicación todos los días. Estaba usando la aplicación, la estaba desarrollando, y no tenía la posibilidad de debugearla. Pero definitivamente fue la que más me molestó. Yo... Ya no estoy seguro de cuál fue mi propia fuga de memoria que solucioné. Tuve suerte en los últimos años y no me encontré personalmente con ninguna de esas. Mantengámoslo así, entonces, Ruben. Entonces, Ruben, quiero agradecerte mucho por tu charla y esta sesión informativa de preguntas y respuestas. Si las personas tienen más preguntas y quieren hablar sobre fugas de memoria, entonces estarás en tu sala de oradores de chat espacial, ¿verdad? Sí. Genial. Y espero con ansias eso. Muy bien, lo escucharon aquí primero, gente. Ruben va a hablar con ustedes en su chat espacial. Espero verte pronto en persona, tal vez, Ruben, y que tengas un buen día. Gracias. Igualmente. Adiós. Adiós. Adiós. Adiós. Adiós. Adiós.
Comments