El salto a los 100.000 usuarios

Texto original de Alex Pareto, publicado en Inglés y traducido aquí en Español, sobre los cambios y mejoras que requiere un proyecto web al crecer y alcanzar los 100.000 usuarios.

escalabilidadinformaticatraduccionweb
22 marzo, 2021 La escalabilidad, clave del éxito de un proyecto web
22 marzo, 2021 La escalabilidad, clave del éxito de un proyecto web

A translation into Spanish of the article «Scaling to 100k Users» by Alex Pareto

Artículo escrito por Alex Pareto y publicado el 3 Febrero de 2020 en alexpareto.com

***

Muchas startups han pasado por eso: lo que parece ser una legión de nuevos usuarios que se registran todos los días y el equipo de ingeniería se apresura a mantener las cosas en funcionamiento.

Es un buen problema, pero la información sobre cómo llevar una aplicación web de 0 a cientos de miles de usuarios puede ser escasa. Por lo general, las soluciones provienen de la aparición de incendios masivos o de la identificación de cuellos de botella (y, a menudo, de ambos).

Dicho esto, me he dado cuenta de que muchos de los principales patrones para llevar un proyecto secundario a algo altamente escalable son relativamente formulistas.

Este es un intento de destilar por escrito los fundamentos de esa fórmula. Vamos a llevar nuestro nuevo sitio web para compartir fotos, Graminsta, de 1 a 100 000 usuarios.

1 usuario: 1 máquina

Casi todas las aplicaciones, ya sean sitios web o aplicaciones móviles, tienen tres componentes clave: una API, una base de datos y un cliente (normalmente una aplicación o un sitio web). La base de datos almacena datos persistentes. La API atiende las solicitudes de esos datos y en torno a ellos. El cliente muestra esos datos al usuario.

En el desarrollo de aplicaciones modernas, he descubierto que pensar en el cliente como una entidad completamente separada de la API hace que sea mucho más fácil razonar sobre el escalado de la aplicación.

Cuando empezamos a construir la aplicación, está bien que las tres cosas se ejecuten en un servidor. En cierto modo, esto se asemeja a nuestro entorno de desarrollo: un ingeniero ejecuta la base de datos, la API y el cliente en el mismo ordenador.

En teoría, podríamos desplegar esto en la nube en un solo Droplet de DigitalOcean o en una instancia de AWS EC2 como la siguiente:

Dicho esto, si esperamos que Graminsta sea utilizado por más de una persona, casi siempre tiene sentido dividir la capa de la base de datos.

10 usuarios: dividir la capa de la base de datos

Dividir la base de datos en un servicio gestionado como RDS de Amazon o Managed Database de Digital Ocean nos servirá durante mucho tiempo. Es un poco más caro que el autoalojamiento en una sola máquina o instancia EC2, pero con estos servicios obtienes un montón de complementos fáciles fuera de la caja que serán útiles más adelante: redundancia multirregional, réplicas de lectura, copias de seguridad automatizadas y más.

Este es el aspecto actual del sistema Graminsta:

100 usuarios: dividir los clientes

Por suerte para nosotros, a nuestros primeros usuarios les encanta Graminsta. Ahora que el tráfico empieza a ser más estable, es el momento de repartir el cliente. Una cosa que hay que tener en cuenta es que la división de las entidades es un aspecto clave para construir una aplicación escalable. A medida que una parte del sistema recibe más tráfico, podemos dividirla para poder gestionar el escalado del servicio en función de sus propios patrones de tráfico específicos.

Por eso me gusta pensar que el cliente está separado de la API. Hace que sea muy fácil razonar sobre la construcción para múltiples plataformas: web, web móvil, iOS, Android, aplicaciones de escritorio, servicios de terceros, etc. Todos son clientes que consumen la misma API.

En la misma línea, la mayor respuesta que estamos recibiendo de los usuarios es que quieren Graminsta en sus teléfonos. Así que vamos a lanzar una aplicación móvil mientras estamos en ello.

Este es el aspecto actual del sistema:

1.000 usuarios: añadir un equilibrador de carga

Las cosas están mejorando en Graminsta. Los usuarios suben fotos a diestro y siniestro. Estamos empezando a recibir más inscripciones. Nuestra solitaria instancia de la API está teniendo problemas para mantener todo el tráfico. ¡Necesitamos más potencia de cálculo!

Los equilibradores de carga son muy potentes. La idea clave es que colocamos un equilibrador de carga delante de la API y este dirigirá el tráfico a una instancia de ese servicio. Esto permite el escalado horizontal (aumentar la cantidad de peticiones que podemos atender añadiendo más servidores que ejecuten el mismo código).

Vamos a colocar un equilibrador de carga independiente delante de nuestro cliente web y nuestra API. Esto significa que podemos tener múltiples instancias ejecutando nuestro código de la API y del cliente web. El equilibrador de carga dirigirá las peticiones a la instancia que tenga menos tráfico.

Lo que también conseguimos con esto es la redundancia. Cuando una instancia se cae (tal vez se sobrecarga o se bloquea), tenemos otras instancias que siguen funcionando para responder a las solicitudes entrantes, en lugar de que todo el sistema se caiga.

Un equilibrador de carga también permite el autoescalado. Podemos configurar nuestro equilibrador de carga para que aumente el número de instancias durante la Superbowl, cuando todo el mundo está conectado y disminuya el número de instancias cuando todos nuestros usuarios están dormidos.

Con un equilibrador de carga, nuestra capa de API puede escalar prácticamente hasta el infinito, simplemente iremos añadiendo instancias a medida que recibamos más peticiones.

Nota al margen: En este punto lo que tenemos hasta ahora es muy similar a lo que empresas de PaaS como Heroku o Elastic Beanstalk de AWS proporcionan de manera innovadora (y por qué son tan populares). Heroku pone la base de datos en un servidor separado, gestiona un equilibrador de carga con autoescalado y te permite alojar tu cliente web por separado de tu API. Esta es una gran razón para utilizar un servicio como Heroku para proyectos o startups en etapa inicial – todos los elementos básicos necesarios son innovadores.

10.000 usuarios: CDN

Probablemente deberíamos haber hecho esto desde el principio, pero nos movemos rápido aquí en Graminsta. Servir y subir todas estas imágenes está empezando a poner demasiada carga en nuestros servidores.

Deberíamos utilizar un servicio de almacenamiento en la nube para alojar contenido estático en este punto: piensa en imágenes, vídeos y más (S3 de AWS o Spaces de Digital Ocean). En general, nuestra API debe evitar manejar cosas como servir imágenes y subirlas.

La otra cosa que obtenemos de un servicio de almacenamiento en la nube es una CDN (en AWS se trata de un complemento llamado Cloudfront, pero muchos servicios de almacenamiento en la nube lo ofrecen de forma inmediata). Una CDN almacenará automáticamente nuestras imágenes en diferentes centros de datos de todo el mundo.

Aunque nuestro centro de datos principal esté alojado en Ohio, si alguien solicita una imagen desde Japón, nuestro proveedor de la nube hará una copia y la almacenará en su centro de datos en Japón. La siguiente persona que solicite esa imagen en Japón la recibirá mucho más rápido. Esto es importante cuando necesitamos servir archivos de mayor tamaño, como imágenes o vídeos, que tardan mucho en cargarse y enviarse a todo el mundo.

100.000 usuarios: escalar la capa de datos

El CDN nos ha ayudado mucho: las cosas están en auge en Graminsta. Una celebridad de YouTube, Mavid Mobrick, acaba de registrarse y nos ha publicado en su historia. El uso de la CPU y la memoria de la API es bajo en general —gracias a que nuestro equilibrador de carga ha añadido 10 instancias de la API al entorno—, pero estamos empezando a tener muchos tiempos de espera en las peticiones… ¿por qué tarda todo tanto?

Después de indagar un poco lo vemos: la CPU de la base de datos está rondando el 80-90 %. Estamos al máximo.

El escalado de la capa de datos es probablemente la parte más complicada de la ecuación. Mientras que en el caso de los servidores API que atienden peticiones sin estado, podemos simplemente añadir más instancias, no ocurre lo mismo con la mayoría de los sistemas de bases de datos. En este caso, vamos a explorar los populares sistemas de bases de datos relacionales (PostgreSQL, MySQL, etc.).

Almacenamiento en caché

Una de las formas más sencillas de sacar más partido a nuestra base de datos es introduciendo un nuevo componente en el sistema: la capa de caché. La forma más común de implementar una caché es utilizando un almacén de valores clave en memoria como Redis o Memcached. La mayoría de las nubes tienen una versión gestionada de estos servicios: Elasticache en AWS y Memorystore en Google Cloud.

La caché es muy útil cuando el servicio hace muchas llamadas repetidas a la base de datos para obtener la misma información. Esencialmente, ejecutamos la base de datos una vez, guardamos la información en la caché y no tenemos que volver a tocar la base de datos.

Por ejemplo, en Graminsta cada vez que alguien va a la página del perfil de Mavid Mobrick, la capa de la API solicita la información del perfil de Mavid Mobrick a la base de datos. Esto ocurre una y otra vez. Dado que la información del perfil de Mavid Mobrick no cambia en cada solicitud, esa información es una gran candidata a ser almacenada en caché.

Vamos a almacenar en caché el resultado de la base de datos en Redis bajo la clave user:id con un tiempo de expiración de 30 segundos. Ahora, cuando alguien vaya al perfil de Mavid Mobrick, comprobaremos primero en Redis y serviremos los datos directamente de Redis si existen. A pesar de que Mavid Mobrick es el más popular del sitio, solicitar el perfil apenas supone una carga para nuestra base de datos.

La otra ventaja de la mayoría de los servicios de caché es que podemos escalarlos más fácilmente que una base de datos. Redis ha incorporado en Redis Cluster un modo que, de forma similar a un equilibrador de carga 1, nos permite distribuir nuestra caché de Redis en varias máquinas (miles si uno lo desea).

Casi todas las aplicaciones altamente escaladas aprovechan ampliamente el almacenamiento en caché, es una parte absolutamente integral de hacer una API rápida. Unas consultas mejores y un código más eficaz forman parte de la ecuación, pero sin una caché nada de esto será suficiente para escalar a millones de usuarios.

Réspaldos de lectura

La otra cosa que podemos hacer ahora que nuestra base de datos ha empezado a ser ejecutada bastante, es añadir respaldos de lectura utilizando nuestro sistema de gestión de bases de datos. Con los servicios gestionados anteriormente, esto se puede hacer en un solo clic. Un respaldo de lectura se mantendrá actualizado con su BD maestra y podrá ser utilizado para los extractos SELECT.

Este es nuestro sistema ahora:

Más allá

A medida que la aplicación siga escalando, vamos a querer centrarnos en dividir los servicios que puedan escalarse de forma independiente. Por ejemplo, si empezamos a hacer uso de websockets, tendría sentido sacar nuestro código de manejo de websockets. Podemos poner esto en nuevas instalaciones detrás de su propio equilibrador de carga que puede escalar hacia arriba y hacia abajo en base a cuántas conexiones de websocket se han abierto o cerrado, independientemente de cuántas peticiones HTTP tenemos entrando.

También vamos a seguir chocando con las limitaciones de la capa de datos. Ahora es cuando vamos a querer empezar a buscar la partición y la fragmentación de la base de datos. Ambas requieren una mayor sobrecarga, pero permiten que la capa de datos se amplíe infinitamente.

Querremos asegurarnos de tener instalada la monitorización mediante un servicio como New Relic o Datadog. De este modo, nos aseguramos de que entendemos qué solicitudes son lentas y dónde hay que mejorar. A medida que vayamos escalando, querremos centrarnos en encontrar los cuellos de botella y solucionarlos, a menudo aprovechando algunas de las ideas de las secciones anteriores.

Esperemos que a estas alturas tengamos a otras personas en el equipo para que nos ayuden también.

Referencias

Esta publicación está inspirada en una de mis publicaciones favoritas sobre la alta escalabilidad. Quería dar más cuerpo al artículo para las primeras etapas y hacerlo un poco más independiente de la nube. Si te interesan este tipo de cosas, no dudes en consultarlo.

Traducciones

Esta publicación ha sido traducida a diversos idiomas, específicamente Chino y Coreano.

Notas a pie de página

  1. Aunque es similar en términos de permitirnos repartir la carga entre múltiples instancias, la implementación subyacente de Redis Cluster es muy diferente a la de un balanceador de carga.

[Nota del Traductor]

Translation into Spanish of an article by Alex Pareto about web scalability

The above text is a Spanish translation of a remarkable article by Alex Pareto, explaining the troubles and challenges that face many startups in the process of scaling their websites from just a few up to 100.000 users. This translation into Spanish about web architecture and scalability was carried out by Jose, a young translator specialized in technical & IT translations for different industries. This translation was produced and delivered free of charge as part of a trainning program for young translators, and therefore might include small mistakes or inaccuracies.

Rate this post

Articulos relacionados


Traducción de un articulo sobre páginas web, código Html y accesibilidad. Cómo programar una web sin perder de vista la accesibilidad, cómo mejorar el uso de etiquetas web para ofrecer a los usuarios mayor facilidad para navegar e interactuar.

Traducción al español de las expresiones de los departamentos de informática

Traducción de software de la página oficial de Rachota, una vieja gloria de los programas de gestión del tiempo, enmarcados en la categoría de software de productividad Descubre cómo eran los primeros programas diseñados para controlar cómo usas tu tiempo y ayudarte a no...