16 puntos por GN⁺ 2025-02-20 | 1 comentarios | Compartir por WhatsApp
  • 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

 
GN⁺ 2025-02-20
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"

    • Construimos un índice "eventualmente consistente" en el backend del motor de búsqueda Blekko. Esto permite entregar actualizaciones más rápido a los usuarios, pero dos usuarios que hagan la misma consulta pueden obtener resultados ligeramente distintos
    • Aquí aplica bastante teoría de sistemas, y existe la posibilidad de oscilación cuando hay retroalimentación positiva. En un motor de búsqueda, el ranker que da peso a los enlaces en los que hacen clic los usuarios aporta esa retroalimentación positiva
    • Era importante mantener el sistema "críticamente amortiguado". Eso permite que converja rápido
    • La forma en que el timeline del usuario está shardeado y tiene bucles de retroalimentación, como "me gusta" o "reposts", parece un espacio de problemas interesante
  • Me pregunto por qué no implementan el timeline de forma híbrida según la popularidad de la cuenta

    • En el caso de cuentas de celebridades, en vez de propagar cada mensaje a millones de seguidores, sería más barato traer y mezclar las publicaciones de la celebridad al servir el timeline del seguidor
    • Si lo hacen millones de seguidores, debería ser barato traerlo en modo de solo lectura desde una hot cache
  • Una solución interesante para un problema interesante. Gracias por compartirla

    • Me costó entender la parte donde el autor pasa de "celebridad" a "bot"
    • Parecía que el autor estaba introduciendo un concepto completamente distinto llamado "timeline con pérdida"
  • 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

    • En vez de escribir en el timeline de cada usuario, imagino escribir una sola vez por shard que tenga al menos un seguidor
    • Al leer, se recupera el contenido del usuario dado y se filtran los seguidores reales
    • Como la lectura ocurre dentro del shard, la latencia es baja
    • En el caso de mega seguidores, la página no vería elementos antiguos
  • 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

    • La solución parecía no ser un orden temporal imperfecto, sino que faltaban publicaciones en el feed
  • Me pregunto cómo funcionaría limitar la cantidad de seguidores para evitar el problema de los hot shards

    • Cada usuario tendría un timeline separado por cada 1000 seguidores, y el cliente los fusionaría
    • Si hace falta, solo se podría cargar parte del timeline real para aplicar la parte con pérdida
  • AWS tiene un enfoque general muy bueno para este problema

    • Asigna a cada usuario a varios shards para reducir la probabilidad de que otros usuarios compartan todos los shards
    • Si hubieran hecho shuffle sharding desde el principio, podrían haber resuelto nuevos problemas sin afectar a muchos otros usuarios
  • 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

    • Me gusta leer sobre desafíos técnicos. Twitter tiene una arquitectura especial para celebridades con millones de seguidores
    • Si Bluesky es un clon similar, me pregunto por qué no siguió ese camino
  • 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í

    • Esto explica por qué en Bluesky, aunque sigo a menos de 100 usuarios, a veces no veo publicaciones de alguien en mi timeline
  • Me pregunto por qué implementaron el fanout de una manera en la que cada "página" bloquea la obtención de la siguiente

    • La actividad de obtención de páginas debería traer seguidores de forma continua y no esperar a que se actualicen todos los elementos de una página
    • Se me ocurre tener un componente de obtención que recupere la página, la guarde en S3 y publique los metadatos y la ubicación en S3 en una cola (SQS)
    • En este sistema se podría controlar mejor la concurrencia, y usando el shard como clave se podría particionar en la cola para "ralentizar" el trabajo según sea necesario