En Memoria de las Dificultades

This ad is not shown to multipass and full ticket holders
JSNation US
JSNation US 2025
November 17 - 20, 2025
New York, US & Online
See JS stars in the US biggest planetarium
Learn More
In partnership with Focus Reactive
Upcoming event
JSNation US 2025
JSNation US 2025
November 17 - 20, 2025. New York, US & Online
Learn more
Bookmark
Rate this content

Dos aspectos de los resolvers tienen una influencia desproporcionada en su rendimiento: el tamaño del contexto de ejecución y la forma en que calculamos su valor. En la implementación de Node.js de graphql, las promesas que envuelven valores primitivos son especialmente disruptivas, ya que añaden una gran sobrecarga computacional. El tamaño del contexto crea una línea base de uso de memoria que puede aumentar muy rápidamente incluso con pequeñas adiciones al contexto, cuando hay muchos contextos concurrentes. La ejecución puede crear objetos temporales, aumentando el uso de memoria. Los resolvers que se ejecutan con frecuencia, como aquellos responsables de completar grandes matrices de objetos, pueden convertirse en cuellos de botella de rendimiento.

En Auction.com, nuestra página de resultados de búsqueda (SRP) solicita hasta 500 elementos de aproximadamente 80 campos cada uno. La consulta que resuelve estos campos estaba sufriendo una alta latencia. Examinaremos las herramientas para instrumentar nuestro código e identificar los cuellos de botella en el uso de memoria y la utilización de CPU.

Nuestros elementos en tiempo real (por ejemplo, actualizaciones en tiempo real del estado de las propiedades actualmente vistas) se implementan utilizando una traducción de mensajes kafka a actualizaciones graphql. Presentaremos las herramientas y procedimientos para reducir el uso de memoria y CPU al distribuir tales mensajes.

This talk has been presented at JSNation US 2024, check out the latest edition of this JavaScript Conference.

Gabriel Schulhof
Gabriel Schulhof
28 min
21 Nov, 2024

Comments

Sign in or register to post your comment.
Video Summary and Transcription
Hola, mi nombre es Gabriel. Trabajo en auction.com y voy a hablar sobre cómo mejoramos el rendimiento de los resolvers de GraphQL para nuestro servicio de GraphQL. Tuvimos un problema con nuestra implementación de suscripción, donde experimentábamos un alto número de reinicios debido a fallos en la asignación de memoria. Esto nos llevó a investigar y optimizar el consumo de memoria de nuestros resolvers. Para evaluar el rendimiento, configuramos un entorno local con Kafka, Graph y un cliente que conectó 4000 WebSockets a Graph. Después de realizar la prueba, descubrimos que solo podíamos procesar y distribuir 16 mensajes a nuestros clientes. Yay. El gráfico de consumo de memoria mostró picos y valles a medida que se entregaban los mensajes. Se observaron tres fases distintas: inactivo, Sockets conectados sin mensajes y mensajes siendo procesados. Decidimos optimizar el contexto, que contiene información específica de la solicitud y detalles del backend. Dado que las suscripciones implican principalmente el envío de mensajes Kafka, nos dimos cuenta de que el mensaje en sí a menudo tiene toda la información necesaria. Por lo tanto, solo creamos backends cuando se realiza una llamada a ellos. Optimizamos nuestro proceso de creación de backends utilizando el objeto proxy, lo que nos permite crear backends de manera perezosa solo cuando se acceden. Esto resultó en un menor consumo de memoria sin cambiar el código o el esquema. El menor consumo de memoria es evidente en la segunda fase de la grabación, donde el plateau formado por tener múltiples contextos es significativamente más bajo. La mayoría de los ahorros se lograron reduciendo objetos temporales y utilizando iteradores nativos en lugar de llamadas Lodash para convertir nombres de claves a Snakecase. De repente, el rendimiento aumentó en un 18%, lo que resultó en un mayor consumo de memoria. Actualizar a versiones más nuevas de GraphQL Redis subscriptions no tuvo un impacto significativo en el uso de memoria. Sin embargo, optimizar la conversión de nombres de claves a snake case mediante el uso de memoización mejoró la eficiencia computacional. Nuestro rendimiento mejoró significativamente después de implementar snake case. Sin embargo, el consumo de memoria seguía siendo alto. Para abordar la fuga de memoria, introdujimos autoescalado y reiniciamos el servicio cada noche. Además, optimizamos el proceso de generación de código para mejorar el consumo de memoria. Exploramos el uso de instantáneas de heap en Google Dev tools para analizar y reducir el consumo de memoria. Al identificar objetos innecesarios y eliminarlos, pudimos liberar memoria y mejorar el rendimiento. Corregimos el objeto de ubicación para mejorar el rendimiento y reducir el consumo de memoria. También optimizamos los cargadores de datos para evitar consultas N+1 y mejorar la eficiencia.
Available in English: In Memory of Travails

1. Introduction

Short description:

Hola, mi nombre es Gabriel. Trabajo en auction.com y voy a hablar sobre cómo mejoramos el rendimiento de los resolvers de GraphQL para nuestro servicio de GraphQL. Tuvimos un problema con nuestra implementación de suscripción, donde experimentábamos un alto número de reinicios debido a fallos en la asignación de memoria. Esto nos llevó a investigar y optimizar el consumo de memoria de nuestros resolvers. Para evaluar el rendimiento, configuramos un entorno local con Kafka, Graph y un cliente que conectó 4000 WebSockets a Graph. Después de ejecutar la prueba, descubrimos que solo podíamos procesar y distribuir 16 mensajes a nuestros clientes.

Hola, mi nombre es Gabriel. Trabajo en auction.com y voy a hablar sobre cómo mejoramos el rendimiento de los resolvers de GraphQL para nuestro servicio de GraphQL.

Entonces, el problema que encontramos fue que en el caso de nuestra implementación de suscripción, estábamos teniendo muchos reinicios. Así que este es el número de reinicios y como pueden ver, estamos en el orden de 200, 300 reinicios en nuestro clúster de Kubernetes y todos los reinicios estaban ocurriendo debido a este mensaje de error mágico que decía reached heap limit allocation failed. Y esto es básicamente el motor de JavaScript diciéndole a Node.js que se quedó sin memoria. Node.js muere y se acabó el juego. Tienes que reiniciar el pod. Y entonces comenzamos a investigar cómo podemos mejorar el consumo de memoria de nuestros resolvers.

Así que veamos un poco de contexto. ¿Cómo hacemos GraphQL en auction.com, verdad? Entonces usamos para nuestras suscripciones, usamos temas de Kafka y suscripción de GraphQL, Redis para PubSub. Entonces llega el mensaje de Kafka, entra en Redis, sale de Redis donde necesita. Y luego se envía a los clientes a través de WebSockets.

Así que para hacer la prueba de consumo de memoria y rendimiento en general, lo que hice fue ejecutar Kafka localmente, ejecuté Graph localmente y ejecuté un cliente localmente que produciría un gran número de WebSockets y los conectaría a Graph. Así que conecté 4000 WebSockets y usé KCAT para básicamente enviar mensajes al broker local de Kafka, que los envió a Graph, que los envió a Redis, que los envió a esos 4000 Sockets. Así que esa fue la configuración. Y este fue el resultado inicial en, ¿qué es? Veamos 250 segundos, eso son cuatro minutos y 10 segundos, supongo. Sí, algo así. Pudimos procesar y distribuir a nuestros clientes la asombrosa cantidad de 16 mensajes.

2. Optimizing the Context

Short description:

Yay. El gráfico de consumo de memoria mostró picos y valles a medida que se entregaban los mensajes. Se observaron tres fases distintas: inactivo, Sockets conectados sin mensajes y mensajes siendo procesados. Decidimos optimizar el contexto, que contiene información específica de la solicitud y detalles del backend. Dado que las suscripciones principalmente implican el envío de mensajes de Kafka, nos dimos cuenta de que el mensaje en sí a menudo tiene toda la información necesaria. Por lo tanto, solo creamos backends cuando se realiza una llamada a ellos.

Yay. Así que como puedes imaginar, eso no es exactamente un rendimiento estelar y puedes ver el consumo de memoria aquí. Tiene todos estos como picos y valles, picos y valles. Así que puedes ver que para cualquier mensaje dado, asignaría un montón de memoria y luego finalmente entregaría el mensaje y luego haría eso 15 veces más. Y puedes ver abajo los pequeños picos verdes en la parte inferior donde está el eje X, ahí es cuando los mensajes fueron realmente entregados.

Así que un poco más sobre la configuración experimental. Así que puedes ver que hay como tres fases distintas en el gráfico de consumo de memoria. Una de ellas es cuando todo el proceso está simplemente inactivo. Eso es solo, ya sabes, para que puedas tener como una línea base de consumo de memoria. Y luego la segunda es donde tenemos 4000 Sockets conectados, pero no hay absolutamente ningún mensaje. Así que hay cero tráfico, solo los Sockets. Y finalmente, la última fase del gráfico es donde tienes los mensajes entrando y el gráfico intentando procesarlos.

Así que veamos. Una de las cosas que inmediatamente pensamos en hacer fue atacar el contexto, por así decirlo. Así que el contexto, como puedes o no saber, es la cosa que cada solicitud o suscripción de GraphQL o lo que sea tiene para que se ejecute de una manera específica de la solicitud. Así que cualquier cosa que sea específica para esa solicitud, como las credenciales del usuario, la solicitud en sí, como qué es lo que el usuario quería, todo eso está adjunto al contexto. Correcto. Y en nuestro caso, dado que podríamos estar accediendo a cosas detrás del servidor de Graph, como backends, tenemos esas cosas adjuntas al contexto también. Correcto. Y cada backend tiene como, ya sabes, cinco métodos diferentes, ya sabes, get, put, post, delete y patch. Y así para cada uno de esos backends y para cada uno de esos métodos, teníamos un pequeño envoltorio que, ya sabes, encapsularía los detalles específicos del backend de ese backend. Y así, ya sabes, cosas como URL y demás. Así que podrías simplemente, ya sabes, llamar al backend, llamar al método, ya sabes, y simplemente obtener los datos sin tener que poner cosas como el servidor o el dominio en como un millón de lugares en tu código. Y ponerlo en un solo lugar y eso es todo. Así que si cambiamos el backend, podríamos simplemente cambiarlo en un solo lugar. Pero el problema es que todo esto requiere que los objetos envoltorios se almacenen en el contexto. Y así estábamos como, bueno, las suscripciones, apenas hacen llamadas al backend porque realmente son solo mensajes de Kafka siendo enviados. Ya hay toda la información que la gente podría querer en el mensaje en sí. Rara vez acceden a los backends.

3. Optimizing Backend Creation

Short description:

Optimizamos nuestro proceso de creación de backends utilizando el objeto proxy, lo que nos permite crear backends de manera perezosa solo cuando se acceden. Esto resultó en un menor consumo de memoria sin cambiar el código o el esquema.

Y así, e incluso en el caso de nuestro servicio de solicitud-respuesta, sabes, no vas a necesitar como 40 backends para cada solicitud, ¿verdad? Así que dijimos, está bien, mantengamos la sintaxis que tenemos para acceder a nuestros backends, pero no creemos los backends a menos que alguien realmente intente hacer una llamada a esos backends.

Y así usamos este maravilloso objeto de JavaScript desarrollado recientemente, bueno, no tan recientemente, pero relativamente recientemente desarrollado llamado proxy, ¿verdad? Y así creamos todos nuestros backends de manera perezosa ahora, lo que significa que el contexto pretende que todos los backends existen, pero en realidad no crea el envoltorio para ningún backend dado hasta que alguien intenta recuperar esa propiedad. Y así, lo bueno fue que pudimos mantener el código tal como estaba para todos nuestros backends y para todo nuestro esquema. No tuvimos que cambiar ninguno de los resultados, pero de repente hubo un menor consumo de memoria.

4. Memory Consumption and Kafka Message Conversion

Short description:

El menor consumo de memoria es evidente en la segunda fase de la grabación, donde el plateau formado por tener múltiples contextos es significativamente más bajo. La mayoría de los ahorros se lograron reduciendo objetos temporales y usando iteradores nativos en lugar de llamadas a Lodash para convertir nombres de claves a Snakecase. La degradación del rendimiento fue mínima, con una ligera disminución en el número de mensajes procesados, pero en resumen, se redujo el uso de memoria.

Así que aquí lo tienes. Este es el menor consumo de memoria, como puedes ver. Llamaré tu atención a la segunda fase de esta grabación, que es la inactividad más 4k sockets. Puedes ver que porque tenemos menos backends creados. Ahora este plateau que se forma al tener todos esos contextos alrededor es mucho más bajo. Así que esto es en realidad solo para darte un adelanto del resto de la presentación.

Esto es en realidad donde la mayoría de los ahorros terminaron siendo porque medir cuánto puedes ahorrar mientras hay toda esa actividad de recibir mensajes entrantes, analizarlos, construir la respuesta, etc. Es mucho más difícil evaluar qué es lo que puedes reducir de ese proceso porque hay muchos objetos temporales en juego. Y así, bien, nuestro rendimiento se degradó ligeramente. Pasamos de 16 mensajes a 13 mensajes, pero eso apenas es significativo. Pero en resumen, estábamos usando menos memoria. Así que esperábamos menos reinicios, pero todavía estaba en su infancia, este tipo de esfuerzo.

Muy bien. Entonces, ¿qué más hicimos? Bueno, esto es un poco de información sobre lo que hacemos con esos mensajes de Kafka. En el lado izquierdo, puedes ver el mensaje de Kafka. Así es como lo recibimos. Y lo único que tenemos que hacer para enviarlo y resolver completamente la suscripción es simplemente convertir todos esos nombres de claves a Snakecase. Eso es todo. Y así nuestra implementación para hacer eso se veía como lo hace en el lado izquierdo aquí. Teníamos muchas llamadas a Lodash para lograr esto. Y esto es de los días históricos cuando el motor de JavaScript no tenía todos esos maravillosos iteradores sobre objetos y sobre arrays. Y así Lodash es genial y todo, y cumplió su propósito. Pero pensé que podría ser más rápido si simplemente dejamos que el motor haga lo suyo y use esos nuevos iteradores compilados just-in-time para arrays y para objetos. Y así convertí el código. Mantuve algunas de las cosas de Lodash, pero la mayoría es simplemente usando los métodos nativos y estándar ahora, los iteradores. Y así, ups, retrocedí un poco. Sí, así que este fue el resultado. Bien, así que el plateau todavía está allí hasta cierto punto. El consumo de memoria volvió a subir, pero eso probablemente se deba a que también lo hizo el rendimiento.

5. Optimizing Performance and Key Conversion

Short description:

De repente, el rendimiento aumentó un 18%, lo que resultó en un mayor consumo de memoria. Actualizar a versiones más nuevas de GraphQL Redis subscriptions no tuvo un impacto significativo en el uso de memoria. Sin embargo, optimizar la conversión de nombres de claves a snake case mediante el uso de memoization mejoró la eficiencia computacional.

De repente, subió un 18%. Y así, sabes, ahora tienes 19 mensajes, por lo que estás manejando más cosas y potencialmente más concurrencia. Y así, en realidad puedes terminar aumentando tu consumo de memoria, aunque tu rendimiento haya aumentado. Esto demuestra que el consumo de memoria y la utilización de la CPU no son exactamente ortogonales ni siquiera exactamente inversamente proporcionales. Tienen una relación muy compleja.

Pero de todos modos, así que el siguiente paso, pensé, está bien, veamos si podemos actualizar algunos de los paquetes. Y uno de los paquetes que tenemos es GraphQL Redis subscriptions, que básicamente puedes ver en este diagrama toma las cosas de Kafka. Y esto también revela cómo configuramos nuestro servicio de graph. Puedes ver que cada uno, como todos los pods de GraphQL, G1, G2 y G3, son todos parte del mismo grupo de consumidores. Así que Kafka básicamente particiona los mensajes y envía un subconjunto de ellos a G1, un subconjunto de ellos a G2 y un subconjunto de ellos a G3. Y por eso necesitamos Redis. Porque, está bien, ¿qué pasa si alguien está suscrito a G1, digamos que C1 está suscrito a G1, pero lo siento, en realidad un ejemplo concreto sería C2 está suscrito a G1, ¿verdad? Pero G1 nunca recibe el mensaje verde, ¿verdad? Entonces, ¿cómo va a decir G1 a C2 que, ya sabes, mensaje verde? Y por eso necesitamos Redis. Redis dirá, como que transmitirá el mensaje a todos los servicios de graph relevantes.

Así que pensé, está bien, tal vez las versiones más nuevas de GraphQL Redis subscriptions tengan un mejor consumo de memoria. Sabes, han recibido optimizaciones solo porque todos las están usando y todos necesitan este tipo de optimización. Así que lo actualicé y, bueno, el rendimiento aumentó. Así que ahora estamos como en 20 mensajes de unos impresionantes 19 mensajes, pero por lo demás no pasó mucho. Correcto. Así que pensé, está bien, bueno, probablemente ese no sea el cuello de botella entonces. Pero está bien, al menos estábamos actualizados y, ya sabes, tal vez menos vulnerabilidades de seguridad, quién sabe. Siempre es beneficioso, a menos que encuentres una regresión muy fuerte, lo cual no hicimos.

6. Optimizing Key Name Conversion

Short description:

Decidimos optimizar la conversión de nombres de claves a snake case implementando memoization usando la función lodash memoize.

Así que estamos listos para empezar. Entonces, lo siguiente que hice fue, está bien, tenemos esta cosa, tenemos esta función que les mostré antes, convertir el objeto a snake case, que básicamente solo toma todos los, ¿cómo se llama eso? Los nombres de las claves en los mensajes entrantes de Kafka y simplemente los convierte. Yo estaba como, ¿sabes, cuáles son esos nombres de claves? Correcto. Como si estuviéramos dirigiendo un negocio. Entonces, ya sabes, tenemos datos que son pares clave-valor, ya sabes, y los nombres de las claves son cosas como, ya sabes, property ID, listing ID, primary property ID, property photo ID y ese tipo de cosas. Así que esas palabras, property ID y listing ID y demás, tenemos que seguir convirtiéndolas a snake case para enviar sus mensajes. Pero estoy como, bueno, solo tenemos tal vez como, no sé, como máximo 300 de esas palabras, ¿verdad? Y después de haber convertido como la palabra listing ID a snake case, ¿por qué gastarías el poder computacional para hacerlo de nuevo cuando ya lo has hecho, verdad? En resumen, vamos a memoizar este snake case. Así que todas las claves que alguna vez adjuntamos a objetos, ya sabes, si ya hemos calculado la "snake caseificación", por así decirlo, de una clave como listing ID, ahora simplemente tomémosla del caché. Y he aquí, lodash, viejo y venerable como puede ser, resultó útil con la función memoize.

7. Improving Performance and Memory Consumption

Short description:

Nuestro rendimiento mejoró significativamente después de implementar snake case. Sin embargo, el consumo de memoria seguía siendo alto. Para abordar la fuga de memoria, introdujimos autoescalado y reiniciamos el servicio cada noche. Además, optimizamos el proceso de generación de código para mejorar el consumo de memoria.

Y redoble de tambores, nuestro rendimiento aumentó de un 18% a casi 3X solo porque comenzamos a usar snake. Así que, por supuesto, el consumo de memoria, no tanto, todavía estamos bastante altos, pero al menos ahora tenemos un buen rendimiento, ¿verdad? Y puedes ver el gráfico, la última parte del gráfico donde estamos procesando mensajes es mucho más suave ahora, como que no tienes esos como tropiezos que tenías antes, porque ahora realmente estás procesando mensajes.

Así que, está bien, tenemos rendimiento, consumo de memoria, no tanto, ya sabes, la búsqueda continúa. Entonces, una de las cosas que hicimos no tenía nada que ver con GraphQL, ¿verdad? Simplemente miramos el servicio y dijimos, bueno, ya sabes, tenemos una fuga de memoria lenta si dejamos el servicio funcionando por mucho tiempo, tenemos estos picos de memoria que terminan matando nuestros pods. Entonces, ¿qué hacer, qué hacer? Bueno, ¿por qué no introducimos autoescalado, verdad? Así que, porque no lo habíamos hecho en ese momento, solo teníamos, creo que como 25 pods, que es mucho y aún así se reiniciaban.

Pero si había algún pico entrante, no reaccionarían en absoluto. Simplemente morirían. Y entonces pensé, está bien, bueno, tenemos una especie de indicación temprana de que va a haber un pico entrante porque se acumula durante varios minutos. Así que estoy como, está bien, intentemos ver si podemos agregar como un HPA, que comenzará nuevos pods cuando el consumo de memoria comience a subir. Y así es como lo estamos haciendo. Y para la fuga de memoria, estábamos como, está bien, bueno, simplemente vamos a, ya sabes, usar el mazo en todo y simplemente reiniciarlo cada noche. Porque, ¿por qué no? Sabes, realmente no va a dañar a nuestros clientes si reiniciamos a las 2 a.m. Y además nuestro software cliente. Así que el otro lado de estas suscripciones está configurado para reconectarse unos segundos después.

Así que no van a perder mucho. Y no hay subastas de ningún tipo a las 2 a.m. Así que, ya sabes, no van a perder, ya sabes, el cierre de una subasta o no van a perder la capacidad de saber si alguien ofertó o algo así. Me refiero a nuestros clientes. Así que deberíamos estar bien para reiniciar a las 2 a.m. Así es como abordamos la fuga de memoria, por así decirlo. Bien, de vuelta a intentar ahorrar memoria. Correcto. Para que no tengamos problemas de consumo de memoria. Una de las cosas que teníamos nuevamente históricamente es que el código que estábamos generando era como. Era como hecho para el navegador esencialmente porque este servicio GraphQL evolucionó de un servicio que serviría como componentes de front end. Correcto. Y así tenemos este paso de convertir TypeScript a JavaScript seguido de un empaquetado con Babel. Y ese paso estaba configurado para el denominador común más bajo para el navegador. Correcto. Y así no podía no haría cosas como async await como async await nativo.

8. Optimizing Code Generation and Memory Consumption

Short description:

Optimizamos la generación de código para soportar el motor nativo de JavaScript, resultando en un mejor rendimiento. Además, cambiamos el código para convertir snake case para iterar y mutar arrays en su lugar, reduciendo el consumo de memoria y mejorando el rendimiento.

Muchos de los iteradores simplemente se reimplementaban y rellenaban. Y entonces pensé, bueno, espera un minuto, no estamos trabajando con todos los navegadores. No necesitamos el denominador común más bajo. Sabemos exactamente con qué estamos trabajando y eso es Node 20. Correcto. Así que pensé, está bien, vamos a añadir un pequeño, vamos a añadir un fork en nuestra generación de código que diga, ¿estás interesado en ejecutar en Node 20? Bueno, vaya sorpresa. Entonces tal vez deberíamos generar un código adecuado que tenga todo el soporte del motor nativo de JavaScript. Y lo hicimos y cuando lo hicimos, el rendimiento aumentó, veamos, de 3.3 a 3.6, así que fue bueno. Puedes ver que la curva ahora es incluso más suave de lo que era antes. Así que antes tenía como estos picos y valles altos, pero ahora es mucho más suave. Correcto. Así que, ya sabes, es una pequeña mejora. Pero desafortunadamente, la altura de la curva sigue siendo prácticamente la misma. Correcto. Así que el consumo de memoria nuevamente con el rendimiento y el consumo de memoria en este caso siendo ortogonales, desafortunadamente. Pero ahora al menos estamos un poco mejor y el código es mucho más limpio y, ya sabes, estamos modernizando.

Muy bien. Así que veamos qué más podemos hacer. Así que con convertir obviamente snake case volví e hice otra iteración sobre ello para mejorarlo un poco más con la esperanza de que, ya sabes, crearíamos menos objetos temporales, etc. etc. Y así pensé, ya sabes, hacemos como cuando llamas a algo como dot map, esencialmente estás creando un nuevo objeto, ¿verdad? y estás desechando el viejo objeto. Pero, ¿por qué en un array puedes simplemente iterar sobre el array existente y no tenemos problemas con la re-entrada? Así que si mutas el array en su lugar, nadie se va a quejar. Correcto. Así que eso es, cambié el código para básicamente pasar de map a un bucle for iterando o mutando el array en su lugar en lugar de crear un nuevo array para objetos. Es un poco más difícil porque es la clave la que estás modificando en el caso de un array. Sabes, los índices 0 1 2 3 no los estás cambiando, así que puedes modificar en su lugar, pero el objeto es, quiero decir, podrías, pero es mucho más propenso a errores. Así que simplemente lo dejé como estaba, ya sabes, crear un nuevo objeto y adjuntarlo.

9. Using Heap Snapshots for Memory Optimization

Short description:

Exploramos el uso de heap snapshots en Google Dev tools para analizar y reducir el consumo de memoria. Al identificar objetos innecesarios y eliminarlos, pudimos liberar memoria y mejorar el rendimiento.

Así que hemos llegado a tener un rendimiento 6 veces mayor que al principio de este viaje. Correcto. Pero de nuevo con el consumo de memoria, como puedes ver, la altura de esa última parte es prácticamente la misma. Es mucho más suave, pero la altura es la misma. Así que bien, bien. Intentemos realmente, realmente, realmente reducir esto ahora. Correcto. ¿Y qué podemos hacer?

Una de las cosas que podemos hacer es usar Google Dev tools de Google Chrome para tomar estas cosas llamadas heap snapshots y estas cosas son increíbles. Me encantan estos heap snapshots no porque te digan cuántos objetos están asignados en el momento en que se tomó, sino porque puedes tomar dos heap snapshots en diferentes puntos de la ejecución y puedes compararlos y realmente hará un diff de los objetos por tipo de objeto. Y eso es increíble porque puedes decir, sabes, pasé de estar completamente inactivo a conectar estos 4000 sockets y de repente hay todos estos objetos allí. Quiero decir, ¿realmente los necesito? Correcto. Así que puedes ver, por ejemplo, aquí tenemos esto como la ubicación, tenemos más 261 mil novecientos sesenta y ocho de estos objetos de ubicación. ¿Qué es un objeto de ubicación? ¿Por qué está ahí? ¿Qué está haciendo? Y lo miré y es como, oh, esto me dice en qué fila y en qué columna está este token. Y yo estoy como, bueno, no estoy escribiendo una aplicación de resaltado de sintaxis. Estoy haciendo ejecución de GraphQL, así que no me importa la ubicación. Así que eliminemos la ubicación. Veamos si podemos ver si eso nos ahorrará algo de memoria. Bien, ¿cómo hacemos eso? Bueno, si miras GraphQL, sabes que cuando construyes el AST a partir de la consulta entrante tienes esta opción llamada no location que dice construye el AST como lo harías antes pero no adjuntes ningún dato de ubicación. Estoy como, oh sí, vamos a adjuntarlo o usar esta opción de inmediato. Y eso es exactamente lo que hicimos.

Primero edité esto, creé un parche para algo como subscription transport WS que es lo que usamos. Sí, sí, sé que es un paquete antiguo. Pero tenemos buenas razones para usarlo, es porque jugamos con el protocolo real de WebSockets para hacer un mejor mantenimiento y ese tipo de cosas. Así que estamos un poco atrapados con eso, pero funciona para nosotros.

10. Optimizing Location Object and Data Loaders

Short description:

Parcheamos el objeto de ubicación para mejorar el rendimiento y reducir el consumo de memoria. También optimizamos los data loaders para evitar consultas N+1 y mejorar la eficiencia.

De todos modos, básicamente lo parcheé para decir algo así como no location true. Como oh sí. ¿Y luego qué pasó? Así que el rendimiento se mantuvo más o menos igual. Y ahora veamos si puedo hacer una animación rápida aquí. Ahí está. Y ahí está. Como puedes ver, el consumo de memoria en la tercera fase disminuyó un poco. Así que, ya sabes, hizo su trabajo, nos ayudó un poco. Veamos si hay algo más que podamos recortar, por así decirlo.

Muy bien. Así que otra cosa que tenemos, volví al contexto y pensé en algo más que sea agradable y jugoso que podamos recortar allí. Bueno, además de dos back ends donde llamamos cosas directamente, también tienes estas cosas llamadas data loaders que también son parte de GraphQL y básicamente te permiten evitar consultas N más uno al acumular piezas individuales de información que necesitas. Así que, por ejemplo, digamos que alguien, digamos que tienes que recuperar el ID de propiedad por separado del ID de listado. Un listado podría ser varias propiedades, ¿verdad? Y así, para cada una de esas propiedades, normalmente tendrías que llamar a un endpoint diciendo dame los detalles de esta propiedad. Y así llamarías a ese back end tantas veces como tengas IDs de listado. Pero si ese servicio proporciona otro endpoint que te permite recuperar todos los IDs de propiedad para cualquier ID de listado dado o, ya sabes, básicamente, entonces usarías un data loader para acumular todos estos IDs de propiedad que el usuario está pidiendo y hacer solo una solicitud al back end en lugar de N solicitudes al back end y luego tomas las respuestas y las distribuyes sobre la respuesta de GraphQL. Y así tenemos muchos de estos data loaders, de hecho tenemos más data loaders que back ends porque, ya sabes, para cualquier back end dado puedes tener un data loader para dos endpoints diferentes. Y también tenemos estas cosas llamadas primers que harán el trabajo duro de recuperar o configurar el data loader para recuperar los datos en caso de que los datos no estén en caché.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Una Guía del Comportamiento de Renderizado de React
React Advanced 2022React Advanced 2022
25 min
Una Guía del Comportamiento de Renderizado de React
Top Content
This transcription provides a brief guide to React rendering behavior. It explains the process of rendering, comparing new and old elements, and the importance of pure rendering without side effects. It also covers topics such as batching and double rendering, optimizing rendering and using context and Redux in React. Overall, it offers valuable insights for developers looking to understand and optimize React rendering.
Acelerando tu aplicación React con menos JavaScript
React Summit 2023React Summit 2023
32 min
Acelerando tu aplicación React con menos JavaScript
Top Content
Mishko, the creator of Angular and AngularJS, discusses the challenges of website performance and JavaScript hydration. He explains the differences between client-side and server-side rendering and introduces Quik as a solution for efficient component hydration. Mishko demonstrates examples of state management and intercommunication using Quik. He highlights the performance benefits of using Quik with React and emphasizes the importance of reducing JavaScript size for better performance. Finally, he mentions the use of QUIC in both MPA and SPA applications for improved startup performance.
Concurrencia en React, Explicada
React Summit 2023React Summit 2023
23 min
Concurrencia en React, Explicada
Top Content
React 18's concurrent rendering, specifically the useTransition hook, optimizes app performance by allowing non-urgent updates to be processed without freezing the UI. However, there are drawbacks such as longer processing time for non-urgent updates and increased CPU usage. The useTransition hook works similarly to throttling or bouncing, making it useful for addressing performance issues caused by multiple small components. Libraries like React Query may require the use of alternative APIs to handle urgent and non-urgent updates effectively.
How React Compiler Performs on Real Code
React Advanced 2024React Advanced 2024
31 min
How React Compiler Performs on Real Code
Top Content
I'm Nadia, a developer experienced in performance, re-renders, and React. The React team released the React compiler, which eliminates the need for memoization. The compiler optimizes code by automatically memoizing components, props, and hook dependencies. It shows promise in managing changing references and improving performance. Real app testing and synthetic examples have been used to evaluate its effectiveness. The impact on initial load performance is minimal, but further investigation is needed for interactions performance. The React query library simplifies data fetching and caching. The compiler has limitations and may not catch every re-render, especially with external libraries. Enabling the compiler can improve performance but manual memorization is still necessary for optimal results. There are risks of overreliance and messy code, but the compiler can be used file by file or folder by folder with thorough testing. Practice makes incredible cats. Thank you, Nadia!
Optimización de juegos HTML5: 10 años de aprendizaje
JS GameDev Summit 2022JS GameDev Summit 2022
33 min
Optimización de juegos HTML5: 10 años de aprendizaje
Top Content
PlayCanvas is an open-source game engine used by game developers worldwide. Optimization is crucial for HTML5 games, focusing on load times and frame rate. Texture and mesh optimization can significantly reduce download sizes. GLTF and GLB formats offer smaller file sizes and faster parsing times. Compressing game resources and using efficient file formats can improve load times. Framerate optimization and resolution scaling are important for better performance. Managing draw calls and using batching techniques can optimize performance. Browser DevTools, such as Chrome and Firefox, are useful for debugging and profiling. Detecting device performance and optimizing based on specific devices can improve game performance. Apple is making progress with WebGPU implementation. HTML5 games can be shipped to the App Store using Cordova.
El Futuro de las Herramientas de Rendimiento
JSNation 2022JSNation 2022
21 min
El Futuro de las Herramientas de Rendimiento
Top Content
Today's Talk discusses the future of performance tooling, focusing on user-centric, actionable, and contextual approaches. The introduction highlights Adi Osmani's expertise in performance tools and his passion for DevTools features. The Talk explores the integration of user flows into DevTools and Lighthouse, enabling performance measurement and optimization. It also showcases the import/export feature for user flows and the collaboration potential with Lighthouse. The Talk further delves into the use of flows with other tools like web page test and Cypress, offering cross-browser testing capabilities. The actionable aspect emphasizes the importance of metrics like Interaction to Next Paint and Total Blocking Time, as well as the improvements in Lighthouse and performance debugging tools. Lastly, the Talk emphasizes the iterative nature of performance improvement and the user-centric, actionable, and contextual future of performance tooling.

Workshops on related topic

Masterclass de Depuración de Rendimiento de React
React Summit 2023React Summit 2023
170 min
Masterclass de Depuración de Rendimiento de React
Top Content
Featured Workshop
Ivan Akulov
Ivan Akulov
Los primeros intentos de Ivan en la depuración de rendimiento fueron caóticos. Vería una interacción lenta, intentaría una optimización aleatoria, vería que no ayudaba, y seguiría intentando otras optimizaciones hasta que encontraba la correcta (o se rendía).
En aquel entonces, Ivan no sabía cómo usar bien las herramientas de rendimiento. Haría una grabación en Chrome DevTools o React Profiler, la examinaría, intentaría hacer clic en cosas aleatorias, y luego la cerraría frustrado unos minutos después. Ahora, Ivan sabe exactamente dónde y qué buscar. Y en esta masterclass, Ivan te enseñará eso también.
Así es como va a funcionar. Tomaremos una aplicación lenta → la depuraremos (usando herramientas como Chrome DevTools, React Profiler, y why-did-you-render) → identificaremos el cuello de botella → y luego repetiremos, varias veces más. No hablaremos de las soluciones (en el 90% de los casos, es simplemente el viejo y regular useMemo() o memo()). Pero hablaremos de todo lo que viene antes - y aprenderemos a analizar cualquier problema de rendimiento de React, paso a paso.
(Nota: Esta masterclass es más adecuada para ingenieros que ya están familiarizados con cómo funcionan useMemo() y memo() - pero quieren mejorar en el uso de las herramientas de rendimiento alrededor de React. Además, estaremos cubriendo el rendimiento de la interacción, no la velocidad de carga, por lo que no escucharás una palabra sobre Lighthouse 🤐)
Next.js 13: Estrategias de Obtención de Datos
React Day Berlin 2022React Day Berlin 2022
53 min
Next.js 13: Estrategias de Obtención de Datos
Top Content
Workshop
Alice De Mauro
Alice De Mauro
- Introducción- Prerrequisitos para la masterclass- Estrategias de obtención: fundamentos- Estrategias de obtención – práctica: API de obtención, caché (estática VS dinámica), revalidar, suspense (obtención de datos en paralelo)- Prueba tu construcción y sírvela en Vercel- Futuro: Componentes de servidor VS Componentes de cliente- Huevo de pascua de la masterclass (no relacionado con el tema, destacando la accesibilidad)- Conclusión
Construyendo una Aplicación de Shopify con React & Node
React Summit Remote Edition 2021React Summit Remote Edition 2021
87 min
Construyendo una Aplicación de Shopify con React & Node
Top Content
Workshop
Jennifer Gray
Hanna Chen
2 authors
Los comerciantes de Shopify tienen un conjunto diverso de necesidades, y los desarrolladores tienen una oportunidad única para satisfacer esas necesidades construyendo aplicaciones. Construir una aplicación puede ser un trabajo duro, pero Shopify ha creado un conjunto de herramientas y recursos para ayudarte a construir una experiencia de aplicación sin problemas lo más rápido posible. Obtén experiencia práctica construyendo una aplicación integrada de Shopify utilizando el CLI de la aplicación Shopify, Polaris y Shopify App Bridge.Te mostraremos cómo crear una aplicación que acceda a la información de una tienda de desarrollo y pueda ejecutarse en tu entorno local.
Depuración del Rendimiento de React
React Advanced 2023React Advanced 2023
148 min
Depuración del Rendimiento de React
Workshop
Ivan Akulov
Ivan Akulov
Los primeros intentos de Ivan en la depuración de rendimiento fueron caóticos. Veía una interacción lenta, probaba una optimización aleatoria, veía que no ayudaba, y seguía probando otras optimizaciones hasta que encontraba la correcta (o se rendía).
En aquel entonces, Ivan no sabía cómo usar bien las herramientas de rendimiento. Hacía una grabación en Chrome DevTools o React Profiler, la examinaba, intentaba hacer clic en cosas al azar, y luego la cerraba frustrado unos minutos después. Ahora, Ivan sabe exactamente dónde y qué buscar. Y en esta masterclass, Ivan te enseñará eso también.
Así es como va a funcionar. Tomaremos una aplicación lenta → la depuraremos (usando herramientas como Chrome DevTools, React Profiler, y why-did-you-render) → identificaremos el cuello de botella → y luego repetiremos, varias veces más. No hablaremos de las soluciones (en el 90% de los casos, es simplemente el viejo y regular useMemo() o memo()). Pero hablaremos de todo lo que viene antes - y aprenderemos cómo analizar cualquier problema de rendimiento de React, paso a paso.
(Nota: Esta masterclass es más adecuada para ingenieros que ya están familiarizados con cómo funcionan useMemo() y memo() - pero quieren mejorar en el uso de las herramientas de rendimiento alrededor de React. Además, cubriremos el rendimiento de interacción, no la velocidad de carga, por lo que no escucharás una palabra sobre Lighthouse 🤐)
Construye una sala de chat con Appwrite y React
JSNation 2022JSNation 2022
41 min
Construye una sala de chat con Appwrite y React
Workshop
Wess Cope
Wess Cope
Las API/Backends son difíciles y necesitamos websockets. Utilizarás VS Code como tu editor, Parcel.js, Chakra-ui, React, React Icons y Appwrite. Al final de este masterclass, tendrás los conocimientos para construir una aplicación en tiempo real utilizando Appwrite y sin necesidad de desarrollar una API. ¡Sigue los pasos y tendrás una increíble aplicación de chat para presumir!
Construyendo aplicaciones web que iluminan Internet con QwikCity
JSNation 2023JSNation 2023
170 min
Construyendo aplicaciones web que iluminan Internet con QwikCity
WorkshopFree
Miško Hevery
Miško Hevery
Construir aplicaciones web instantáneas a gran escala ha sido elusivo. Los sitios del mundo real necesitan seguimiento, análisis y interfaces y interacciones de usuario complejas. Siempre comenzamos con las mejores intenciones pero terminamos con un sitio menos que ideal.
QwikCity es un nuevo meta-framework que te permite construir aplicaciones a gran escala con un rendimiento de inicio constante. Veremos cómo construir una aplicación QwikCity y qué la hace única. El masterclass te mostrará cómo configurar un proyecto QwikCity. Cómo funciona el enrutamiento con el diseño. La aplicación de demostración obtendrá datos y los presentará al usuario en un formulario editable. Y finalmente, cómo se puede utilizar la autenticación. Todas las partes básicas para cualquier aplicación a gran escala.
En el camino, también veremos qué hace que Qwik sea único y cómo la capacidad de reanudación permite un rendimiento de inicio constante sin importar la complejidad de la aplicación.