Cómo decidimos optimizar las imágenes y, en el proceso, rediseñamos el sitio web, el panel de administración y el enfoque de la interfaz.

Boris Goryachev, director técnico de Meduza, informa.



Nuestro producto principal, el sitio web, está adquiriendo constantemente nuevas funciones, pero debido a esto se vuelve gradualmente más pesado y difícil de manejar. Por ello, nuestro departamento técnico está ocupado de vez en cuando para hacerlo más fácil y rápido.







Uno de los principales elementos que afectan la velocidad de carga de casi cualquier sitio, especialmente un sitio de medios, son las imágenes. Hay muchas imágenes en Meduza, y esta es una forma valiosa para que los editores cuenten historias. Los requisitos de nuestro servicio de fotografía se pueden formular de la siguiente manera:



  • la imagen debe cargarse en el CMS (lo llamamos "Monitor") lo más rápido posible
  • la imagen debe seguir siendo hermosa y verse bien en todas las plataformas
  • el lector no debe esperar a que se cargue esta imagen




Primer enfoque



Cuando lanzamos en 2014, el proceso de trabajar con imágenes cargadas en el Monitor se veía así: el archivo se cargó en una aplicación Rails, usando Paperclip e Imagemagick, se borraron los metadatos, se comprimieron con la calidad seleccionada (para JPEG era alrededor de 75) y cortado en tres tamaños: pequeño para teléfonos, más grande para tabletas y teléfonos con pantallas grandes y muy grande para computadoras. Los archivos divididos se colocaron en el almacenamiento en la nube de AWS junto con el original. Se proporcionaron (y se proporcionan) no directamente desde AWS, sino a través de nuestro CDN, que lo almacena en caché en sus servidores de borde.



La API que genera JSON para el sitio y las aplicaciones, además de los atributos simples, como los títulos de los materiales, también proporcionó una gran cantidad de código HTML, que se insertó en la parte de "contenido" del material y se colgó con estilos CSS. Y la API en sí era la misma para todos los clientes: el sitio, las aplicaciones y los servicios de apoyo como RSS y búsqueda.







Inmediatamente nos quedó claro que este enfoque no resistiría la prueba del tiempo, pero era necesario lanzarlo rápidamente, probar una gran cantidad de hipótesis, experimentar, sobrevivir. Concientemente, al menos creemos en ello, elegimos "muletas", y no la belleza del código y las soluciones. Pasó el tiempo, nuestra audiencia y nuestro producto crecieron. Y junto con esto, creció el apetito de los editores. Los editores querían cada vez más técnicas y "trucos" en sus materiales. Al mismo tiempo, por supuesto, el diseño también cambió.



El departamento técnico tuvo que evocar los estilos de las imágenes, los métodos para exhibirlas, los tamaños: cambiaron junto con los cambios en el diseño y los nuevos elementos en el sitio. Añadimos y apoyamos soluciones cada vez más extrañas.



Aqui esta uno de ellos




Con el tiempo, el Monitor enfureció cada vez más a todos los que se encontraban con él: el personal editorial sufría errores, porque la corrección de estos errores llevó mucho tiempo, los desarrolladores estaban furiosos y las limitaciones de la arquitectura inventada no se adaptaban a los jefes; no pudimos desarrollar rápidamente el producto.



Empezamos a rediseñar el Monitor. Reescribimos la parte principal del CMS responsable de trabajar en los materiales durante aproximadamente un año, distrayéndonos regularmente con errores en la versión anterior. Entonces apareció un meme interno en "Meduza": "Estará en el nuevo Monitor". Así respondimos a la mayoría de las solicitudes de los editores, aunque, por supuesto, hicimos algunas cosas simultáneamente en el viejo y el nuevo CMS: una mala versión por ahora y una buena versión para después.



Pronto escribiré una publicación separada en nuestro blog en detalle sobre la alteración de "Monitor" .



Reiniciar



Después de reiniciar y reconstruir todo Meduza, la API siguió siendo el mismo en formato, pero ya no estaba formada por el propio CMS, sino que fue procesada por un servicio separado. Y finalmente decidimos dividir la API en varias, para diferentes clientes.



Decidimos comenzar con aplicaciones móviles. En ese momento, ya tenían preguntas sobre diseño, UX y velocidad de trabajo, así que arreglamos las aplicaciones e hicimos la API de la forma en que nuestros desarrolladores de iOS y Android querían.



Antes de la separación de la API, mostramos la parte del contenido de los materiales a través de WebView, por lo que no podíamos mostrar algo exclusivamente en la aplicación o exclusivamente en el sitio, todo se mostraba en todas partes. Ahora tenemos la oportunidad de administrar el contenido de manera más flexible: dar solo lo que se necesita a las aplicaciones móviles, mostrar elementos pesados ​​de una manera diferente (como inserciones de video de YouTube) y, finalmente, hacer Lazy Load, que le permite cargar gradualmente elementos pesados ​​en el material. imágenes e incrustaciones.



Habiendo separado las aplicaciones, nos enfocamos en el sitio. En este punto, ya se decidió que no haríamos cambios en el código del sitio existente, sino que escribiríamos todo desde cero (sí, nos volvimos a involucrar en un proyecto que duró más de un año). Al mismo tiempo, se reemplazó a nuestro director técnico, y en mi nuevo cargo tuve que auditar proyectos, decidir qué se podía cerrar y coordinarlo con mis superiores. A pesar de todas las dificultades, el equipo decidió completar el nuevo sitio. Pero antes de eso, decidí que necesitábamos otro proyecto.



Así es como apareció el UI-kit de Medusa.



Para ser flexible en la entrega de contenido, tenía que ser en la forma más adecuada para las transformaciones. Los límites semánticos de las unidades de contenido (imágenes, párrafos de texto, encabezados) deben estar bien alineados. Un contenido no debe ser demasiado simple ni demasiado complejo. Si es complejo, lo más probable es que tenga más de un significado. Si es demasiado simple, lo más probable es que al usarlo, tendrá que ponderarse con una lógica complicada, y cuando necesite reutilizar dicha unidad, tendrá que copiarla en diferentes partes del código del proyecto.



Tome los juegos de Medusa, por ejemplo .... Te permiten contar una gran cantidad de historias de forma lúdica. Se pueden realizar específicamente para la agenda o a petición del anunciante. O el juego se puede hacer sobre la base de las llamadas mecánicas, formatos que se usan repetidamente (por ejemplo, estas son pruebas).



Juegos en Meduza: cómo diseñamos, fabricamos y reutilizamos

El código de estos juegos no se encuentra en el código del sitio. Cada uno de ellos se crea como un microservicio independiente, integrado en el sitio a través de un iframe y comunicándose con el sitio mismo a través de postMessage. Y al sitio realmente no le importa qué mostrar en el lugar donde estará el juego. Además, el juego en sí debería ser visualmente inseparable del sitio: la tipografía y los elementos de la interfaz deberían ser los mismos.







Cuando empezamos a hacer juegos, copiamos estilos, botones y otras cosas, y por supuesto era rápido y se veía bien por fuera, pero era terrible por dentro.



El equipo decidió que esto debería cambiarse. Detuvimos la correspondencia del sitio y comenzamos a hacer nuestro propio kit de interfaz de usuario, una biblioteca que incluía todos los elementos y estilos repetidos. Intentamos no perder la perspectiva a largo plazo: el sitio, los juegos y la mecánica; todo tenía que empezar a usar la misma biblioteca.



Todos los proyectos de Meduza en la web están escritos en React, y el UI-kit es un módulo npm que ahora se conecta a casi todo lo que desarrollamos. Y el desarrollador, cuando necesita renderizar algo, escribe algo como esto: render blocks.







¿Qué hace?



  1. El desarrollador de front-end no piensa exactamente cómo renderizar algo.
  2. Todo ya ha sido revisado por el departamento de diseño.
  3. : UI-kit, , , . , «», .
  4. — - UI-kit ( ) .


UI-kit



Hay dos grupos de componentes: contenido y front-end. Estos últimos son componentes de React muy simples, como botones e íconos.











Los componentes de contenido son un poco más complejos, pero siguen siendo componentes de React muy simples con estilos que representan una unidad de contenido. Por ejemplo, así es como se ve un componente de párrafo:







Un componente que "captura" bloques simples







Y este es un componente que renderiza una imagen: la







API de la que el sitio toma datos, dentro de cada material contiene una serie de componentes que eventualmente se "renderizan" a través del UI-kit ... Con los juegos, todo funciona de la misma manera, solo buscan sus datos en sus versiones de la API.



¡Hurra, hemos reiniciado!



A continuación se muestra un gráfico que muestra el tiempo promedio de carga de la página. Este es un indicador muy inexacto, se tienen en cuenta todas las páginas de todos los dispositivos, pero incluso da una idea de la tendencia.







El gráfico muestra cómo ha cambiado la velocidad del sitio durante toda la existencia de Meduza. Cuando lanzamos por primera vez con un sitio muy simple en 2014, fue muy rápido. Pero cuando comenzamos a agregar nuevas funciones, la velocidad de descarga disminuyó.



Y aquí está el mismo calendario, pero durante los últimos dos años. Muestra cómo se redujeron los tiempos de carga de la página después de reiniciar el sitio.







Entonces finalmente llegó el momento de las fotos.



Imagenes



El esquema de trabajar con imágenes fue entonces. La imagen llegó a través de la API, donde tenía una dirección como /images/attachments/.../random.jpg . El archivo en sí se entregó desde el almacenamiento en la nube de AWS a través de nuestro CDN.







Formulamos los requisitos para el nuevo sistema de la siguiente manera:



  • la solución debería permitirnos cambiar rápidamente el tamaño y la calidad de las imágenes que se envían
  • no tiene que ser caro
  • debe poder manejar mucho tráfico


El plan por el que estábamos luchando resultó así. El backend generaría una URL que sería recogida por el cliente - navegador o aplicación. La URL contendría información sobre qué imagen se necesita, en qué calidad y qué tamaño debería tener.



Si la imagen en esta URL ya está en el servidor Edge, se le entregará inmediatamente al cliente. De lo contrario, el servidor Edge "llamaría" al siguiente servidor, que ya habría pasado la solicitud al servicio. Este servicio, habiendo recibido la URL de la imagen, la decodificaría y determinaría la dirección de la imagen original y la lista de operaciones en ella. Después de eso, el servicio serviría la imagen transformada para que se almacenara en la CDN y se entregara a pedido.







Meduza ya tiene soluciones similares. Por ejemplo, de manera similar, hacemos imágenes para fragmentos de nuestros materiales y juegos en las redes sociales; en este servicio, hacemos capturas de pantalla de páginas HTML a través de Headless Chrome.



Se suponía que el nuevo servicio podía trabajar con imágenes, aplicar efectos simples, ser rápido y resistente. Como nos encanta escribir todo nosotros mismos, originalmente se planeó escribir dicho servicio en el idioma Elixir. Pero nadie en el equipo tenía suficiente tiempo, y ciertamente nadie tenía el deseo de sumergirse en el maravilloso mundo de jpg, png y gif.



Todavía necesitábamos encontrar una biblioteca que se ocupara de la compresión de imágenes. Imagemagick, con el que ya teníamos experiencia, no era la solución más rápida, pero al menos sabíamos cómo cocinarlo. Todo lo demás era nuevo y habría mucho que comprobar.



También necesitábamos soporte para el formato de imagen webp.



Pasó el tiempo, nos preparamos mentalmente para sumergirnos en este proyecto. Pero luego uno de nuestros programadores leyó sobre la biblioteca imgproxy, que los chicos de Evil Martians cargaron en Open Source . Según la descripción, fue un éxito perfecto: Go, Libvps, imagen de Docker lista para usar, configuración a través de Env. El mismo día, implementamos la biblioteca en nuestras computadoras portátiles y le pedimos a nuestro DevOps que también jugara con ella. Su tarea era abrir el servicio e intentar eliminarlo, para que pudiéramos entender qué cargas soportaría en nuestros servidores. Durante este tiempo, el equipo de backend continuó incursionando con el proyecto en sus computadoras: escribimos scripts de Ruby y dominamos las funciones disponibles.







Cuando DevOps regresó con el veredicto de que la biblioteca podría usarse en producción, recopilamos una gran cantidad de imágenes que se pasaron a través de imgproxy (estábamos interesados ​​principalmente en webp) y tomamos sus directivas fotográficas. Los empleados del departamento técnico no pueden decidir por sí mismos si esta calidad nos conviene o no. Los editores de fotos nos enviaron sus comentarios, modificamos algo, nos aseguramos de que las imágenes no pesaran mucho y nos pusimos a escribir el código del backend.



Aquí todo resultó ser muy simple: dado que en la versión de la API para el sitio, las imágenes ya estaban separadas por componentes de todo lo demás, simplemente ampliamos su JSON y agregamos direcciones adicionales en diferentes tamaños y formatos.











El actualizado entró inmediatamente en producción, ya que no cambiamos nada, solo agregamos: los chicos de la interfaz podían desarrollar funciones en la misma versión de la API. Expandieron el componente de imagen en términos de funciones, agregaron conjuntos de imágenes para diferentes tamaños y una "muleta" especial para Safari. Cambiamos la tabla de tamaños un par de veces y comprobamos el resultado a través de los ojos de la redacción; para ello le dimos la versión actual del sitio y un duplicado con nuevas imágenes para su revisión.



Cuando dejaron de llegar los comentarios, finalmente implementamos, eliminamos la caché y lo implementamos en producción.



Salida



La esencia de nuestros cambios se puede resumir de la siguiente manera: cambiamos el lugar de la toma de decisiones sobre qué imagen dar y de qué forma al lugar donde se tiene mejor en cuenta el contexto y la implementación y el apoyo son más fáciles de implementar.



Ahora casi todas las imágenes que ves en Meduza son el resultado del trabajo de imgproxy, y en cada caso tienen diferentes tamaños y, a veces, diferente calidad. Esto está determinado por el contexto, ya sea que el contenido esté abierto en la web, la aplicación móvil o AMP, que conoce el servicio API que genera las respuestas.



All Articles