En los últimos años, han aparecido muchas
cosas nuevas en
instagram.com . Mucho Por ejemplo: herramientas para contar historias, filtros, herramientas creativas, notificaciones, mensajes directos. Sin embargo, a medida que el proyecto creció, todo esto produjo un efecto secundario triste, que fue que el rendimiento de instagram.com comenzó a disminuir. Durante el año pasado, el equipo de desarrollo de Instagram ha hecho esfuerzos continuos para solucionar esto. Esto llevó al hecho de que el tiempo total de carga del feed de Instagram (página de feed) disminuyó en casi un 50%.

Hoy publicamos una traducción del primer material de una serie de artículos dedicados a la historia de cómo se aceleró instagram.com.
Acerca de optimizar el rendimiento de proyectos web
Mejora del rendimiento durante el año pasado (feed de Instagram, métrica Display Done, ms)Uno de los enfoques más importantes para mejorar el rendimiento de las aplicaciones web es priorizar adecuadamente los recursos de carga y procesamiento y reducir el tiempo de inactividad del navegador durante la carga de la página. En nuestro caso, muchas de estas optimizaciones han demostrado ser más efectivas que reducir el tamaño del código. Como regla general, no tuvimos quejas sobre el tamaño del código. Era lo suficientemente compacto. Sus dimensiones comenzaron a molestarnos solo después de que se hicieron muchas pequeñas mejoras al proyecto (también planeamos informar sobre la optimización del tamaño del código). Dichas mejoras, además, tuvieron menos impacto en el proceso de desarrollo del proyecto. Requerían menos cambios de código y menos refactorización. Como resultado, inicialmente concentramos nuestros esfuerzos precisamente en esta área, comenzando con la precarga de recursos.
Una historia sobre la carga previa de imágenes, el código JavaScript y los materiales necesarios para completar las consultas, así como sobre dónde tener cuidado
El principio general de nuestras optimizaciones era informar al navegador lo antes posible sobre los recursos necesarios para cargar la página. Nosotros, como desarrolladores de proyectos, en muchos casos sabíamos de antemano qué se necesitaría exactamente para esto. Pero el navegador no podría tener idea hasta que una cierta parte de los materiales de la página se haya cargado y procesado. Los recursos en cuestión, en su mayor parte, incluyeron aquellos que se cargan dinámicamente usando JavaScript (por ejemplo, otros scripts, imágenes, materiales necesarios para ejecutar solicitudes XHR). El hecho es que el navegador no puede detectar estos recursos dependientes hasta que analiza y ejecuta algún código JavaScript.
En lugar de esperar hasta que el navegador mismo encuentre estos recursos, podríamos darle una pista, después de lo cual podría comenzar a descargarlos de inmediato. Lo hicimos usando los atributos HTML de
preload
. Se parece a esto:
<link rel="preload" href="my-js-file.js" as="script" type="text/javascript" />
Utilizamos sugerencias similares para dos tipos de recursos en rutas críticas de carga de páginas. Este es un código JavaScript cargado dinámicamente y materiales cargados dinámicamente de solicitudes de datos GraphQL XHR. Las secuencias de comandos cargadas dinámicamente son secuencias de comandos que se cargan utilizando construcciones del formulario de
import('...')
para rutas específicas del cliente. Mantenemos una lista de correspondencia de los puntos de entrada del servidor y los scripts de ruta del cliente. Como resultado, cuando nosotros, en el servidor, recibimos una solicitud para cargar la página, conocemos las secuencias de comandos para las rutas de cliente que necesita descargar. Como resultado, podemos, al generar el código HTML de la página, agregarle sugerencias apropiadas.
Por ejemplo, cuando trabaje con el punto de entrada
FeedPage
sabemos que el enrutador del cliente completará una solicitud para descargar
FeedPageContainer.js
. Como resultado, podemos agregar la siguiente construcción al código de página:
<link rel="preload" href="/static/FeedPageContainer.js" as="script" type="text/javascript" />
Del mismo modo, si sabemos que se planea ejecutar una consulta GraphQL para el punto de entrada de una página en particular, esto significa que debemos precargar los materiales para acelerar la ejecución de esta consulta. Esto es especialmente importante debido al hecho de que la ejecución de tales consultas GraphQL a veces lleva mucho tiempo, y la página no se puede procesar hasta que se devuelvan los resultados de la consulta. Debido a esto, debemos hacer que el servidor lo antes posible participe en la formación de respuestas a tales solicitudes.
<link rel="preload" href="/graphql/query?id=12345" as="fetch" type="application/json" />
Los cambios en las características de carga de la página son especialmente notables en conexiones lentas. Al simular una conexión 3G rápida (el primer gráfico en cascada a continuación, que ilustra la situación en la que no se usa la precarga de recursos), podemos ver que cargar
FeedPageContainer.js
y ejecutar la consulta GraphQL asociada solo comienza después de cargar
Consumer.js
. Sin embargo, en el caso de que se use la precarga, la carga del script
FeedPageContainer.js
y la ejecución de la consulta GraphQL pueden comenzar inmediatamente después de que la página HTML esté disponible. Esto, además, reduce el tiempo requerido para descargar cualquier script menor que use mecanismos de carga diferida. Aquí,
FeedSidebarContainer.js
y
ActivityFeedBox.js
(que dependen de
FeedPageContainer.js
) comienzan a cargarse casi inmediatamente después de procesar
Consumer.js
.
Precarga no utilizadaPrecarga utilizadaBeneficios de priorizar la precarga
Además de usar el atributo de
preload
para comenzar a cargar recursos más rápido, usar este mecanismo tiene otra ventaja. Consiste en aumentar la prioridad de red de la carga asíncrona de scripts. Esto se vuelve importante cuando se usan scripts cargados de forma asíncrona en rutas críticas para cargar páginas, ya que de manera predeterminada se cargan con baja prioridad. Como resultado, la prioridad de las solicitudes XHR e imágenes relacionadas con el área de la página visible para los usuarios será mayor que la de los materiales fuera del área de visualización. Pero esto puede conducir a situaciones en las que los scripts críticos necesarios para representar la página se bloquean o se ven obligados a compartir el ancho de banda con otros recursos. Si está interesado,
aquí hay una cuenta detallada de las prioridades de recursos de Chrome. El uso cuidadoso del mecanismo de precarga (hablaremos más sobre esto a continuación) le da al desarrollador un cierto nivel de control sobre cómo el navegador prioriza el proceso de carga inicial de la página. Esto es especialmente cierto para aquellos casos en que el desarrollador sabe qué recursos son importantes para la visualización correcta de la página.
Problemas de priorización de precarga
El problema de la precarga de recursos radica precisamente en el hecho de que le da al desarrollador una influencia adicional para influir en la prioridad de la carga de recursos. Esto significa que el desarrollador tiene más responsabilidad para la priorización adecuada. Por ejemplo, al probar un sitio en regiones en las que la velocidad de las redes móviles y WiFi es muy baja, y en las que se observa un gran porcentaje de pérdida de paquetes, notamos que la solicitud se ejecutó durante el procesamiento de
<link rel="preload" as="script">
obtiene una prioridad más alta que la solicitud que se ejecuta al procesar la
<script />
paquetes
<script />
JavaScript utilizados en las rutas críticas de representación de páginas. Esto lleva a un aumento en el tiempo total de carga de la página.
La fuente de este problema fue cómo colocamos las etiquetas de precarga en nuestras páginas. Es decir, agregamos sugerencias de precarga solo para paquetes, que son parte de la página actual, que íbamos a cargar de forma asincrónica con el enrutador del cliente.
<link rel="preload" href="SomeConsumerRoute.js" as="script" /> <link rel="preload" href="..." as="script" /> ... <script src="Common.js" type="text/javascript"></script> <script src="Consumer.js" type="text/javascript"></script>
Por ejemplo, en la página de cierre de sesión,
SomeConsumerRoute.js
en
Common.js
y
Consumer.js
, y dado que los recursos de precarga se cargan con una prioridad más alta, pero no se
Common.js
, esto bloquea
Common.js
y
Consumer.js
parsing
Consumer.js
. El equipo de desarrollo de Chrome Data Saver encontró un problema de precarga similar y
describió su solución a este problema. En su caso, se decidió colocar siempre construcciones para precargar recursos asíncronos después de las etiquetas
<script />
de esos recursos que usan estos recursos asíncronos. Decidimos precargar todos los scripts y colocar las construcciones correspondientes en el código en el orden en que serían necesarias. Esto nos da la oportunidad de comenzar a precargar todos los recursos de script de la página lo más rápido posible. Esto incluye etiquetas para cargar síncronos scripts que no se pueden agregar a HTML hasta que se coloquen datos específicos del servidor en la página. Esto nos permite controlar el orden de carga de los scripts.
Aquí está el marcado que precarga todos los paquetes de JavaScript.
<link rel="preload" href="Common.js" as="script" /> <link rel="preload" href="Consumer.js" as="script" /> <link rel="preload" href="SomeConsumerRoute.js" as="script" /> ... <script src="Common.js" type="text/javascript"></script> <script src="Consumer.js" type="text/javascript"></script> <script src="SomeConsumerRoute.js" type="text/javascript" async></script>
Precarga de imagen
Una de las principales áreas de trabajo de instagram.com es Feed. Es una página de imagen y video que admite desplazamiento sin fin. Llenamos esta página así. Primero, descargue el conjunto inicial de publicaciones y luego, a medida que el usuario se desplaza por la página, cargue conjuntos adicionales de materiales. Sin embargo, no queremos que el usuario espere a que se carguen nuevos materiales cada vez que llegue al final de la cinta. Como resultado, para facilitar el trabajo con esta página, cargamos nuevos conjuntos de materiales antes de que el usuario llegue al final de la cinta.
En la práctica, esto, por varias razones, no es una tarea fácil:
- Necesitamos descargar materiales que no son visibles para el usuario, para que no tomen recursos de red y procesador de los materiales que está viendo.
- No quisiéramos transmitir datos innecesarios a través de la red, tratando demasiado de precargar publicaciones que el usuario ni siquiera puede ver. Pero, por otro lado, si no precargamos una cantidad suficiente de materiales, esto a menudo significará el riesgo de que el usuario "se encuentre" con el extremo de la cinta.
- El proyecto instagram.com está diseñado para funcionar en varios dispositivos y en pantallas de varios tamaños. Como resultado, mostramos las imágenes en la cinta usando el atributo
srcset
de la <img>
. Este atributo permite al navegador, dado el tamaño de la pantalla, decidir qué resolución de imagen usar. Esto significa que no es tan fácil para nosotros determinar la resolución de las imágenes que deben descargarse por adelantado. Además, existe el riesgo de precargar imágenes que el navegador no utilizará.
El enfoque que utilizamos para resolver este problema fue crear una abstracción de la tarea de priorización, que es responsable de poner en cola las tareas asincrónicas (en este caso, estas son tareas para precargar el siguiente conjunto de publicaciones para la salida en la cinta). Inicialmente, una tarea similar se pone en cola con prioridad
idle
(aquí
requestIdleCallback
utiliza
requestIdleCallback
). Esto significa que la ejecución de dicha tarea no comenzará mientras el navegador esté ocupado con cualquier otro trabajo importante. Sin embargo, si el usuario desplaza la página lo suficientemente cerca del lugar donde finaliza el conjunto actual de publicaciones descargadas, la prioridad de esta tarea de precargar materiales cambia a
high
. Esto se hace cancelando la devolución de llamada en espera, después de lo cual el proceso de precarga comienza inmediatamente.
Al principio y en el medio de la cinta, la tarea de precarga de datos tiene prioridad inactiva, y al final de la cinta, la prioridad es altaDespués de completar la descarga de datos JSON para el próximo lote de publicaciones, ponemos en cola una tarea de fondo repetitiva para precargar las imágenes de este lote. La precarga de imágenes se realiza en el orden en que se muestran las publicaciones en el feed en lugar de en paralelo. Esto nos permite priorizar las tareas de carga de datos y mostrar imágenes de las publicaciones más cercanas al lugar de la página que ve el usuario. Para descargar imágenes del tamaño correcto, utilizamos un componente multimedia oculto, cuyos parámetros corresponden a los parámetros de la cinta actual. Dentro de este componente hay un elemento
<img>
que usa el atributo
srcset
, el mismo que se usa para mostrar publicaciones reales en el feed. Esto significa que podemos proporcionar al navegador la capacidad de tomar decisiones sobre qué imágenes precargar. Como resultado, el navegador usará la misma lógica cuando muestre las imágenes que usó al precargarlas. También significa que nosotros, utilizando un componente de medios similar, podemos precargar imágenes para otras áreas del sitio. Tales como páginas de perfil de usuario.
El efecto general de las mejoras anteriores resultó en una reducción del 25% en el tiempo requerido para cargar fotos. Estamos hablando del período de tiempo entre el momento en que el código de publicación se agrega al DOM y el momento en que se carga y muestra la imagen de la publicación. Además, esto condujo a una reducción del 56% en el tiempo que los usuarios, al llegar al final del feed, pasaban esperando para descargar nuevos materiales.
Estimados lectores! ¿Utiliza mecanismos de precarga de datos para optimizar sus proyectos web?
