- Al diseñar sistemas, en la práctica es muy difícil satisfacer al mismo tiempo consistencia perfecta, alta disponibilidad, baja latencia y alto rendimiento
- Es importante encontrar un diseño adecuado buscando un punto de equilibrio que se ajuste a la aplicación
- En el diseño del feed/línea de tiempo de seguidos de Bluesky, aplicaron un trade-off de sacrificar parcialmente la consistencia para mejorar el rendimiento de escritura
- Como resultado, lograron reducir en más de 96% la latencia P99 sin generar un impacto negativo en los usuarios
Fanout de la línea de tiempo
- Cuando un usuario publica algo en Bluesky, el sistema lo indexa, lo guarda en la base de datos y luego lo entrega como respuesta de la API
- Al mismo tiempo, pasa por un proceso de “fanout” en el que esa publicación se inserta en la tabla de línea de tiempo de cada seguidor
- Para ello, primero se consulta la lista de seguidores y luego se realiza una inserción en orden inverso en la tabla de línea de tiempo de cada uno
- La tabla de línea de tiempo está particionada por usuario y se almacena en una base de datos distribuida (ScyllaDB), replicada en múltiples shards para alta disponibilidad
- Cada usuario puede estar asignado a un shard distinto
- Para ahorrar espacio de almacenamiento, las líneas de tiempo que superan cierta longitud eliminan periódicamente referencias a publicaciones antiguas
El problema de los shards calientes
- Bluesky tiene alrededor de 32 millones de usuarios, y la base de datos de líneas de tiempo está dividida en cientos de shards
- En un sistema usado por millones de personas, puede haber usuarios con una cantidad extremadamente alta de relaciones de seguimiento
- Ejemplo: usuarios que siguen a cientos de miles de cuentas
- En un mismo shard se almacenan muchas líneas de tiempo de usuarios
- Si un usuario específico provoca demasiadas escrituras, ese shard entra en sobrecarga (“hot shard”)
- Esos hot shards concentran operaciones de escritura o lectura, y el retraso termina afectando también a otros usuarios del mismo shard
Acumulación de latencia
- Si un usuario tiene 2,000,000 de seguidores, escribir secuencialmente puede tardar más de 20 minutos
- Para reducirlo, se puede paralelizar el fanout, lo que acorta la latencia promedio
- Sin embargo, la latencia P99 (de alrededor de 15 milisegundos o más) puede ocurrir muchas veces y retrasar todo el trabajo en paralelo
- Cuando hay muchísimos seguidores, la latencia P99 o P99.9 puede hacer que el tiempo total de fanout, en el peor caso, aumente de varios minutos a decenas de minutos
Línea de tiempo con pérdidas (Lossy)
- Para usuarios que siguen a demasiadas cuentas, en la práctica es imposible mostrar todas las publicaciones exactamente en orden
- Además, a una persona le resulta difícil consumir realmente todas esas publicaciones
- Por eso, para líneas de tiempo de usuarios cuyo número de seguidos supera cierto umbral (por ejemplo,
reasonable_limit), se introdujo un método que “descarta” probabilísticamente algunas escrituras
- Se usa la fórmula
loss_factor = min(reasonable_limit / num_follows, 1)
- Durante el fanout, se genera un valor aleatorio y, si es mayor que
loss_factor, se omite la escritura en la línea de tiempo
- Con esto se limita la cantidad excesiva de escrituras hacia líneas de tiempo específicas y se evita la degradación del rendimiento de todo el shard
Sobre el caché
- Como las escrituras de líneas de tiempo superan el millón por segundo, consultar directamente en la base de datos cuántas cuentas sigue un usuario en cada escritura generaría una carga muy alta
- En su lugar, almacenan en caché en Redis las cuentas con altos volúmenes de seguidos usando un sorted set
- Las instancias del servicio de fanout cargan esta información en memoria cada 30 segundos
- Como resultado, incluso durante el proceso de fanout se puede consultar rápidamente la información de usuarios con muchos seguidos
- No es necesario que la información en caché esté perfectamente actualizada, así que aceptan un pequeño grado de imperfección para mejorar rendimiento y escalabilidad
Resultados
- Tras introducir la línea de tiempo con pérdidas, los hot shards prácticamente desaparecieron de la base de datos Timelines
- La latencia P99 para procesar una página de fanout se redujo en más de 90%
- Viendo el trabajo completo de fanout, tareas que bajo P99 tardaban entre 5 y 10 minutos se acortaron a menos de 10 segundos
- Esto muestra que incluso sacrificando parte de la consistencia se pueden satisfacer plenamente las expectativas de los usuarios del servicio y mantener la escalabilidad a gran escala
- La arquitectura de la línea de tiempo de Bluesky todavía tiene margen de mejora, pero este cambio aumentó significativamente el rendimiento de escritura y la escalabilidad
1 comentarios
Opiniones en Hacker News
Como alguien aficionado a los sistemas, este tipo de artículos me encantan. Es fácil caer en la forma de pensar de que "tiene que ser perfecto"
Me pregunto por qué no implementan el timeline de forma híbrida según la popularidad de la cuenta
Una solución interesante para un problema interesante. Gracias por compartirla
Me da curiosidad esta estrategia de sacrificar consistencia. Me pregunto si hay ideas sobre otros enfoques que no sean fanout completo en lectura o escritura
No hace falta entregar de manera perfectamente cronológica todo lo que publican los miles de usuarios que sigue cada persona, pero sí parece razonable entregar suficiente contenido para que siempre haya contenido nuevo en el timeline
Me pregunto cómo funcionaría limitar la cantidad de seguidores para evitar el problema de los hot shards
AWS tiene un enfoque general muy bueno para este problema
Una cuenta que sigue a cientos de miles de usuarios claramente es un bot que está raspando contenido. Yo la bloquearía y asunto arreglado
Cuando entro directamente al perfil de un usuario para ver todas sus publicaciones, a veces hay publicaciones que deberían estar en el timeline pero no aparecen ahí
Me pregunto por qué implementaron el fanout de una manera en la que cada "página" bloquea la obtención de la siguiente