El material, cuya traducción publicamos hoy, está dedicado a la historia sobre la optimización de la nueva versión del cliente de escritorio
Slack , una de cuyas características centrales fue la aceleración de la carga.

Antecedentes
Al comienzo del trabajo en la nueva versión del cliente de escritorio Slack, se creó un prototipo, llamado "botas rápidas". El objetivo de este prototipo, como puede suponer, era acelerar la descarga tanto como sea posible. Al usar el archivo HTML de la caché CDN, los datos del repositorio de Redux almacenados con anticipación y el trabajador del servicio, pudimos descargar la versión liviana del cliente en menos de un segundo (en ese momento, el tiempo de carga habitual para usuarios con 1-2 espacios de trabajo era de aproximadamente 5 segundos ) El trabajador de servicio fue el centro de esta aceleración. Además, allanó el camino para las oportunidades que a menudo pedíamos a los usuarios de Slack que implementaran. Estamos hablando del modo fuera de línea del cliente. El prototipo nos permitió ver literalmente con un ojo de lo que el cliente rehecho puede ser capaz. Con base en las tecnologías anteriores, comenzamos a procesar el cliente Slack, imaginando aproximadamente el resultado y enfocándonos en acelerar la carga e implementar el modo de operación del programa fuera de línea. Hablemos sobre cómo funciona el núcleo del cliente actualizado.
¿Qué es un trabajador de servicio?
Service Worker (
Service Worker ) es, de hecho, un poderoso objeto proxy para solicitudes de red, que permite al desarrollador, utilizando una pequeña cantidad de código JavaScript, controlar cómo el navegador procesa las solicitudes HTTP individuales. Los trabajadores de servicio admiten una API de almacenamiento en caché avanzada y flexible, que está diseñada para que los objetos de solicitud se usen como claves y los objetos de respuesta se usen como valores. Los trabajadores de servicios, como los trabajadores web, se ejecutan en sus propios procesos, fuera del hilo principal de ejecución del código JavaScript de cualquier ventana del navegador.
Service Worker es un seguidor de
Application Cache , que ahora está en desuso. Era un conjunto de API representadas por la interfaz
AppCache
, que se usaba para crear sitios que implementan funciones sin conexión. Al trabajar con
AppCache
, se
AppCache
un archivo de manifiesto estático que describe los archivos que el desarrollador desea almacenar en caché para su uso sin conexión. En general,
AppCache
características de
AppCache
limitaron a esto. Este mecanismo era simple, pero no flexible, lo que no le daba al desarrollador un control especial sobre el caché. En W3C, esto se tuvo en cuenta al desarrollar
la especificación de Service Worker . Como resultado, los trabajadores del servicio le permiten al desarrollador administrar muchos detalles con respecto a cada sesión de interacción de red realizada por una aplicación web o sitio web.
Cuando comenzamos a trabajar con esta tecnología, Chrome era el único navegador que lo soportaba, pero sabíamos que no había mucho tiempo para esperar un amplio soporte para los trabajadores del servicio. Ahora esta tecnología está en
todas partes , es compatible con todos los principales navegadores.
Cómo usamos los trabajadores de servicios
Cuando un usuario inicia un nuevo cliente Slack por primera vez, descargamos un conjunto completo de recursos (HTML, JavaScript, CSS, fuentes y sonidos) y los colocamos en la memoria caché del trabajador del servicio. Además, creamos una copia de la tienda Redux ubicada en la memoria y escribimos esta copia en la base de datos IndexedDB. Cuando se inicia el programa la próxima vez, verificamos la presencia de los cachés correspondientes. Si es así, los usamos al descargar la aplicación. Si el usuario está conectado a Internet, descargamos los datos más recientes después de iniciar la aplicación. Si no, el cliente permanece operativo.
Para distinguir entre las dos opciones anteriores para cargar el cliente, les dimos los nombres: descarga en caliente (en caliente) y fría (en frío). El arranque en frío de un cliente ocurre con mayor frecuencia cuando el usuario inicia el programa por primera vez. En esta situación, no hay recursos en caché o datos de Redux almacenados. Con un arranque en caliente, tenemos todo lo que necesita para ejecutar el cliente Slack en la computadora del usuario. Tenga en cuenta que la mayoría de los recursos binarios (imágenes, archivos PDF, videos, etc.) se procesan utilizando la memoria caché del navegador (estos recursos se controlan mediante encabezados de almacenamiento en caché regulares). Un trabajador de servicio no debe procesarlos de manera especial para que podamos trabajar con ellos sin conexión.
La elección entre carga en frío y en caliente.Ciclo de vida del trabajador de servicio
Los trabajadores de servicio pueden manejar tres eventos del ciclo de vida. Estos son
instalar ,
buscar y
activar . A continuación, hablaremos sobre cómo respondemos a cada uno de estos eventos, pero primero debemos hablar sobre la descarga y el registro del trabajador de servicio. Su ciclo de vida depende de cómo maneje el navegador las actualizaciones de archivos del trabajador de servicio. Esto es lo que puede leer al respecto en
MDN : “La instalación se realiza si el archivo descargado se reconoce como nuevo. Este puede ser un archivo que difiere del existente (la diferencia en los archivos se determina comparándolos por byte) o un archivo de trabajador de servicio que el navegador encontró por primera vez en la página que se está procesando ".
Cada vez que actualizamos el archivo JavaScript, CSS o HTML correspondiente, pasa por el complemento Webpack personalizado, que crea un manifiesto con una descripción de los archivos correspondientes con hashes únicos (
aquí hay un ejemplo abreviado de un archivo de manifiesto). Este manifiesto está incrustado en el código del trabajador del servicio, lo que hace que el trabajador del servicio se actualice en el próximo arranque. Además, esto se hace incluso cuando la implementación del trabajador del servicio no cambia.
▍ Evento evento
Cada vez que se actualiza un trabajador de servicio, recibimos un evento de
install
. En respuesta a esto, revisamos los archivos cuyas descripciones están contenidas en el manifiesto integrado en el trabajador de servicio, cargamos cada uno de ellos y los colocamos en el bloque de caché correspondiente. El almacenamiento de archivos se organiza utilizando la nueva API de
caché , que forma parte de la especificación de Service Worker. Esta API almacena objetos de
Response
utilizando objetos de
Request
como claves. Como resultado, resulta que el almacenamiento es increíblemente simple. Va bien con la forma en que los eventos de los trabajadores de servicio reciben solicitudes y devuelven respuestas.
Las claves para los bloques de caché se asignan en función del tiempo de implementación de la solución. La marca de tiempo está incrustada en el código HTML, como resultado, se puede enviar, como parte del nombre del archivo, en la solicitud para descargar cada recurso. El almacenamiento en caché por separado de los recursos de cada implementación es importante para evitar compartir recursos incompatibles. Gracias a esto, podemos estar seguros de que el archivo HTML descargado inicialmente solo descargará recursos compatibles, y esto es cierto tanto cuando se descargan a través de la red como cuando se descargan del caché.
▍Fecha de eventos
Después de que el trabajador de servicio esté registrado, comenzará a procesar todas las solicitudes de red que pertenezcan a la misma fuente. Un desarrollador no puede hacer que algunas solicitudes sean procesadas por un trabajador de servicio, mientras que otras no. Pero el desarrollador tiene control total sobre lo que debe hacerse exactamente con las solicitudes recibidas por el trabajador del servicio.
Al procesar una solicitud, primero la examinamos. Si lo que se solicita está presente en el manifiesto y está en el caché, devolvemos la respuesta a la solicitud tomando los datos del caché. Si el caché no tiene lo que necesita, le devolvemos una solicitud de red real que accede al recurso de red real como si el trabajador del servicio no estuviera involucrado en este proceso. Aquí hay una versión simplificada de nuestro controlador de eventos de
fetch
:
self.addEventListener('fetch', (e) => { if (assetManifest.includes(e.request.url) { e.respondWith( caches .open(cacheKey) .then(cache => cache.match(e.request)) .then(response => { if (response) return response; return fetch(e.request); }); ); } else { e.respondWith(fetch(e.request)); } });
En realidad, dicho código contiene mucha más lógica específica de Slack, pero el núcleo de nuestro controlador es tan simple como en este ejemplo.
Al analizar las interacciones de red, las respuestas devueltas por el trabajador del servicio pueden reconocerse mediante la marca ServiceWorker en la columna que indica la cantidad de datos▍Event activar
El evento de
activate
se genera después de la instalación exitosa de un trabajador de servicio nuevo o actualizado. Lo usamos para analizar recursos en caché e invalidar bloques de caché que tienen más de 7 días. Esta es una buena práctica para mantener el sistema en orden y, además, le permite asegurarse de que no se utilicen recursos demasiado antiguos al cargar el cliente.
El código del cliente va a la zaga de la última versión
Es posible que haya notado que nuestra implementación implica que cualquier persona que inicie el cliente Slack después del primer inicio del cliente no recibirá los últimos recursos almacenados en caché cargados durante el registro anterior del trabajador del servicio. En la implementación original del cliente, intentamos actualizar el trabajador de servicio después de cada descarga. Sin embargo, un usuario típico de Slack puede, por ejemplo, descargar un programa solo una vez al día, por la mañana. Esto puede llevar al hecho de que trabajará constantemente con un cliente cuyo código durante todo el día va a la zaga de la última versión (lanzamos nuevas versiones varias veces al día).
A diferencia de un sitio web típico, que, al visitarlo, se va rápidamente, el cliente Slack en la computadora del usuario está abierto durante horas y en estado abierto. Como resultado, nuestro código tiene una vida útil bastante larga, lo que requiere que usemos enfoques especiales para mantener su relevancia.
Al mismo tiempo, nos esforzamos por garantizar que los usuarios trabajen con las últimas versiones del código, para que reciban las últimas características, correcciones de errores y mejoras de rendimiento. Poco después de lanzar un nuevo cliente, implementamos un mecanismo que nos permite reducir la brecha entre lo que los usuarios están trabajando y lo que hemos lanzado. Si, después de la última actualización, se implementó una nueva versión del sistema, cargamos recursos nuevos que se utilizarán la próxima vez que se inicie el cliente. Si no se puede encontrar nada nuevo, entonces no se carga nada. Después de realizar este cambio en el cliente, el tiempo promedio de vida de los recursos con los que se cargó el cliente se redujo a la mitad.
Las nuevas versiones del código se descargan regularmente, pero al descargar el programa, solo se usa la última versiónNueva sincronización de banderas de funciones
Con la ayuda de indicadores de nuevas características (Indicadores de características) marcamos en la base de código que funcionan en los que aún no se ha completado. Esto nos permite incluir nuevas características en el código antes de sus lanzamientos públicos. Este enfoque reduce el riesgo de errores en la producción debido al hecho de que las nuevas características se pueden probar libremente junto con el resto de la aplicación, lo que hace mucho antes de que se complete el trabajo en ellas.
Las nuevas funciones en Slack generalmente se lanzan cuando realizan cambios en las API correspondientes. Antes de comenzar a usar trabajadores de servicio, teníamos la garantía de que las nuevas características y los cambios en la API siempre se sincronizarían. Pero después de que comenzamos a usar el caché, que puede no contener la última versión del código, resultó que el cliente puede estar en una situación en la que el código no está sincronizado con las capacidades de back-end. Para hacer frente a este problema, almacenamos en caché no solo recursos, sino también algunas respuestas API.
El hecho de que los trabajadores de servicios procesen absolutamente todas las solicitudes de red ha simplificado la solución. Con cada actualización del trabajador de servicio, nosotros, entre otras cosas, ejecutamos solicitudes de API, almacenando en caché las respuestas en el mismo bloque de caché que los recursos correspondientes. Esto conecta capacidades y funciones experimentales con los recursos correctos, potencialmente obsoletos, pero garantizados para ser consistentes entre sí.
Esto, de hecho, es solo la punta del iceberg de oportunidades disponibles para el desarrollador gracias a los trabajadores del servicio. Un problema que no se pudo resolver utilizando el mecanismo
AppCache
, o uno que requeriría la
AppCache
mecanismos del cliente y del servidor, se resuelve de manera simple y natural utilizando los trabajadores de servicio y la API de caché.
Resumen
El trabajador de servicio aceleró la carga del cliente Slack al organizar el almacenamiento local de recursos que están listos para usar la próxima vez que se inicie el cliente. La red, la principal fuente de retrasos y ambigüedades que nuestros usuarios pueden encontrar, ahora prácticamente no tiene ningún efecto sobre la situación. Nosotros, por así decirlo, lo eliminamos de la ecuación. Y si puede eliminar la red de la ecuación, resulta que puede implementar la funcionalidad fuera de línea en el proyecto. Nuestro soporte para el modo fuera de línea es muy sencillo en este momento. El usuario puede descargar el cliente y puede leer mensajes de conversaciones descargadas. El sistema al mismo tiempo se prepara para las marcas de sincronización en los mensajes leídos. Pero ahora tenemos una base para la futura implementación de mecanismos más avanzados.
Después de muchos meses de desarrollo, experimentación y optimización, aprendimos mucho sobre cómo trabajan los trabajadores de servicio en la práctica. Además, resultó que esta tecnología es muy adecuada para proyectos a gran escala. En menos de un mes desde el lanzamiento público de un cliente con un trabajador de servicio, atendemos con éxito decenas de millones de solicitudes diarias de millones de trabajadores de servicio instalados. Esto condujo a una reducción de aproximadamente el 50% en el tiempo de carga de los nuevos clientes en comparación con los antiguos, y al hecho de que la carga en caliente es aproximadamente un 25% más rápida que en frío.
De izquierda a derecha: cargar un cliente antiguo, cargar en frío un nuevo cliente, cargar en caliente un nuevo cliente (cuanto menor sea el indicador, mejor)Estimados lectores! ¿Utiliza trabajadores de servicio en sus proyectos?
