2 puntos por GN⁺ 2024-09-29 | 1 comentarios | Compartir por WhatsApp
  • A medida que el volumen de mensajes almacenados por Discord creció de miles de millones a billones, para inicios de 2022 la carga operativa y la latencia impredecible de un clúster Cassandra de 177 nodos se volvieron difíciles de sostener
  • El diseño de Cassandra centrado en channel_id y buckets de tiempo generaba particiones calientes bajo tráfico de lectura concentrado en servidores grandes, y la latencia se propagaba a un impacto más amplio en los usuarios por las lecturas y escrituras con quorum
  • Se colocó un servicio de datos basado en Rust entre la API y la base de datos, que fusiona solicitudes concurrentes a la misma fila y reduce la carga sobre la base de datos mediante enrutamiento con hashing consistente basado en ID de canal
  • La migración a ScyllaDB se hizo con un nuevo clúster y un migrador en Rust, reduciendo una tarea que con Spark se estimaba en 3 meses a unos 9 días, y migrando hasta 3.2 millones de mensajes por segundo
  • Tras el cambio en mayo de 2022, los nodos bajaron de 177 en Cassandra a 72 en ScyllaDB; el p99 de consultas de mensajes antiguos mejoró de 40–125 ms a 15 ms, y el p99 de inserción de mensajes pasó de 5–70 ms a 5 ms

La creciente carga operativa de Cassandra

  • En 2017, Discord publicó cómo almacenaba miles de millones de mensajes y compartió el proceso de migración de MongoDB a Cassandra
  • En ese momento, el objetivo era una base de datos escalable, resistente a fallas y con relativamente poco mantenimiento, pero a medida que creció el volumen almacenado, el propio clúster Cassandra se convirtió en una gran carga operativa
  • El clúster cassandra-messages para almacenar mensajes pasó de 12 nodos en 2017 a 177 nodos a inicios de 2022, y almacenaba billones de mensajes
  • El equipo de operaciones recibía llamados frecuentes por problemas de base de datos, la latencia era impredecible y era necesario reducir tareas de mantenimiento que se habían vuelto demasiado costosas

Esquema de mensajes y particiones calientes

  • El esquema simplificado de mensajes incluía channel_id, bucket, message_id, author_id y content, y la clave primaria era ((channel_id, bucket), message_id)
  • Los ID de Discord están basados en Snowflake, por lo que pueden ordenarse cronológicamente, y los mensajes se particionaban por canal y por una ventana de tiempo estática llamada bucket
  • En Cassandra, todos los mensajes de un canal y bucket específicos se almacenan juntos y se replican en varios nodos según el factor de replicación
  • Entre servidores de pequeños grupos de amigos y servidores de cientos de miles de personas había diferencias de varios órdenes de magnitud en la cantidad de mensajes, y esa diferencia se traducía en diferencias de carga a nivel de partición
  • Las escrituras se agregan al commit log y a una estructura en memoria llamada memtable, y luego se vacían a disco; las lecturas, en cambio, pueden consultar la memtable y múltiples SSTable, por lo que son más costosas
  • Cuando se concentraban lecturas concurrentes en servidores grandes, una partición específica se convertía en una partición caliente, aumentaba la latencia en ese nodo y también se veían afectadas otras consultas
  • Como se usaba un nivel de consistencia quorum para lecturas y escrituras, el aumento de latencia en los nodos a cargo de una partición caliente se propagaba a un impacto más amplio en los usuarios

Cuellos de botella de mantenimiento en Cassandra

  • La compactación de Cassandra también se convirtió en una causa recurrente de respuesta a incidentes
    • Para que el rendimiento de lectura mejore, las SSTable deben compactarse en disco; si la compactación se atrasa, aumenta el costo de lectura
    • Mientras un nodo ejecutaba compactación, la latencia podía aumentar en cadena
  • Discord realizaba con frecuencia una operación a la que llamaba “gossip dance
    • Sacaba un nodo del tráfico para ejecutar la compactación
    • Lo reincorporaba para que se pusiera al día con los hints de Cassandra hinted handoff
    • Repetía este proceso hasta que desapareciera el backlog de compactación
  • También dedicaban mucho tiempo a ajustar el recolector de basura de la JVM y la configuración del heap, y las pausas de GC generaban grandes picos de latencia

Por qué migraron a ScyllaDB

  • Además de mensajes, Discord operaba varios clústeres Cassandra, y en cada uno aparecían problemas similares
  • ScyllaDB, por el que habían mostrado interés en el artículo anterior, es una base de datos compatible con Cassandra escrita en C++; ofrece mejor rendimiento, repairs más rápidos, aislamiento de cargas basado en una arquitectura shard-per-core y una estructura sin recolección de basura
  • Aunque ScyllaDB no estaba libre de problemas, el hecho de estar escrita en C++ y no en Java, por lo que no tenía recolector de basura, contrastaba directamente con los problemas operativos de Cassandra
  • Tras confirmar mejoras en experimentos y pruebas, Discord decidió migrar todas sus bases de datos a ScyllaDB, y para 2020 ya había migrado todas salvo cassandra-messages
  • El clúster de mensajes quedó para el final porque era un clúster grande, con billones de mensajes y casi 200 nodos
  • El rendimiento de ScyllaDB para consultas inversas no cumplía con los requisitos en las pruebas iniciales
    • Una consulta inversa es un escaneo en la dirección opuesta al orden de la tabla; un ejemplo es escanear mensajes en orden ascendente
    • Cuando el equipo de ScyllaDB priorizó e implementó mejoras de rendimiento, se eliminó el factor que impedía la última migración de base de datos

Servicio de datos basado en Rust

  • Discord consideró que cambiar solo la base de datos no resolvería automáticamente todos los problemas, así que también cambió la capa superior a la base de datos
  • Colocó un servicio de datos entre el monolito de la API y el clúster de base de datos para controlar las solicitudes concurrentes hacia la base de datos
  • El servicio de datos fue escrito en Rust
    • Discord ya había usado Rust antes en algunos proyectos
    • Rust ofrece velocidad de nivel C/C++ sin renunciar a la seguridad
    • El ecosistema Tokio se usó como base para construir el sistema de I/O asíncrono, y también había soporte de drivers para Cassandra y ScyllaDB
  • El servicio de datos tiene aproximadamente un endpoint gRPC por consulta de base de datos y, de forma deliberada, no contiene lógica de negocio
  • La clave es la fusión de solicitudes
    • Si varios usuarios solicitan simultáneamente la misma row, la base de datos se consulta una sola vez
    • La primera solicitud crea una worker task dentro del servicio, y las solicitudes posteriores detectan la existencia de esa task y se suscriben
    • La worker task consulta la base de datos y devuelve las rows resultantes a todos los suscriptores

Aumentar el efecto de fusión mediante enrutamiento

  • Delante del servicio de datos se aplicó enrutamiento basado en hashing consistente
  • Cada solicitud al servicio de datos recibe una routing key; en el caso de mensajes, la routing key es el ID del canal
  • Como todas las solicitudes del mismo canal van a la misma instancia del servicio, la fusión de solicitudes funciona mejor
  • Por ejemplo, si se produce una notificación @everyone en un servidor grande, muchos usuarios pueden abrir la app y leer el mismo mensaje, generando un pico de tráfico hacia la base de datos
  • En la arquitectura anterior, una situación así podía derivar en una partición caliente y requerir un llamado al equipo de operaciones, pero el servicio de datos reduce de forma significativa los picos de tráfico hacia la base de datos
  • Esta mejora no eliminó por completo las particiones calientes ni la latencia del clúster Cassandra, pero dio tiempo para preparar el nuevo clúster ScyllaDB y ejecutar la migración

Migración de billones de mensajes

  • Los requisitos de la migración eran claros
    • Había que migrar billones de mensajes
    • No podía haber downtime
    • Cassandra seguía generando respuestas frecuentes a incidentes, así que había que terminar rápido
  • Discord primero configuró un nuevo clúster ScyllaDB que usaba super-disk storage topology
    • Aseguraba velocidad con Local SSD
    • Replicaba los datos a persistent disk mediante RAID
    • Era una configuración que combinaba la velocidad del disco local con la durabilidad del persistent disk
  • El plan inicial era que, a partir del cutover time, los datos nuevos usaran ScyllaDB y los datos antiguos se migraran en segundo plano
  • Se empezó a hacer doble escritura de datos nuevos en Cassandra y ScyllaDB, mientras se preparaba el Spark migrator de ScyllaDB
  • El Spark migrator requería mucho tuning y, después de configurarlo, el tiempo estimado de finalización era de 3 meses
  • Luego Discord extendió su biblioteca rápida de base de datos existente y reescribió el migrador de datos en Rust
    • Leía token ranges desde la base de datos
    • Registraba checkpoints locales en SQLite
    • Enviaba datos masivamente a ScyllaDB
  • El tiempo estimado de finalización del nuevo migrador en Rust se redujo a 9 días, y gracias a esa velocidad fue posible hacer todo el cambio de una sola vez en lugar de usar un enfoque complejo basado en tiempo

Cambio y verificación

  • La migración avanzó a una velocidad máxima de 3.2 millones de mensajes por segundo
  • El progreso se detuvo en 99.9999%, porque en los últimos token ranges había enormes rangos de tombstones no compactados en Cassandra
  • Al compactar esos token ranges, la migración terminó unos segundos después
  • Discord realizó verificación automática de datos enviando un pequeño porcentaje de solicitudes de lectura a ambas bases de datos y comparando los resultados, que no presentaron problemas
  • El clúster ScyllaDB resistió bien todo el tráfico de producción, mientras Cassandra sufría problemas de latencia cada vez más frecuentes
  • El equipo cambió en vivo ScyllaDB para que fuera la base de datos primaria

Rendimiento y escala después del cambio

  • La migración de la base de datos de mensajes se realizó en mayo de 2022
  • Desde entonces, la operación fue tranquila y estable; dejaron de pasar fines de semana enteros respondiendo incidentes o ajustando nodos del clúster para mantener el uptime
  • La cantidad de nodos bajó de 177 nodos Cassandra a 72 nodos ScyllaDB
  • El espacio en disco por nodo ScyllaDB era de 9 TB, más que el promedio de 4 TB por nodo Cassandra
  • La tail latency también mejoró de forma importante
    • p99 de consulta de mensajes antiguos: Cassandra 40–125 ms → ScyllaDB 15 ms
    • p99 de inserción de mensajes: Cassandra 5–70 ms → ScyllaDB 5 ms
  • Gracias a la mejora de rendimiento, Discord pudo habilitar nuevos casos de uso de producto con confianza en la base de datos de mensajes

Manejo de carga comprobado durante el tráfico del World Cup

  • A fines de 2022, durante el World Cup, usuarios de todo el mundo veían los partidos y los goles también aparecían en la gráfica de envío de mensajes de Discord
  • En la gráfica de envío de mensajes de la World Cup Final aparecieron varios picos correspondientes a eventos del partido
    • Penal de Messi y ventaja 1-0 de Argentina
    • Gol adicional de Argentina
    • Plateau de 15 minutos sostenido durante el entretiempo
    • Pico cuando Mbappé anotó para Francia y volvió a anotar 90 segundos después para empatar
    • Fin del tiempo regular e ingreso al tiempo extra
    • Chat durante el entretiempo de la primera parte del alargue
    • Gol adicional de Messi
    • Gol del empate de Mbappé
    • Fin del alargue e ingreso a la tanda de penales
    • Fallo de Francia y campeonato de Argentina
  • Incluso ante un gran aumento en el volumen de envío de mensajes, el servicio de datos basado en Rust y ScyllaDB procesaron el tráfico
  • El sistema final quedó organizado como una arquitectura capaz de manejar billones de mensajes

1 comentarios

 
GN⁺ 2024-09-29
Opiniones de Hacker News
  • Este artículo parece culpar mucho a la recolección de basura, pero si se ve el artículo anterior [0], parece más bien una limitación de cómo usaban Cassandra, de cómo Cassandra maneja las eliminaciones masivas, o de ambas cosas
    Un usuario borró millones de mensajes mediante la API y dejó solo 1 mensaje en el canal; como Cassandra maneja las eliminaciones con tombstones, cuando un usuario abría ese canal tenía que escanear millones de tombstones aunque en realidad solo hubiera 1 mensaje. La explicación es que, durante ese proceso, se generaba basura más rápido de lo que la JVM podía recolectarla
    También se habla de tuning del GC, pero si se ve [1], no parece que hubieran hecho mucho tuning, y al parecer estaban usando una versión antigua de Cassandra, y probablemente también de la JVM. También pesa bastante el hecho de que acababan de migrar desde CMS
    0) https://discord.com/blog/how-discord-stores-billions-of-messages

    1. https://news.ycombinator.com/item?id=33136453
    • Aun así, es bueno que ahora usen ScyllaDB, así ese problema dejó de ser una preocupación por completo
      Aunque estuvieran usando mal la solución original, creo que una solución difícil de usar mal es mejor
    • Es bastante interesante que acabaran de salir de CMS. CMS fue reemplazado por G1GC en Java 9 y luego eliminado en Java 14
      Si en 2022 estaban en Java 11, usaban un runtime de 4 años; si estaban en Java 8, uno de 8 años, así que es muy probable que estuvieran perdiendo bastante rendimiento
  • Needs (2023)
    Esta capa de servicio parecía un Varnish Cache distribuido, grande y vistoso. Como no mencionan caché y eligieron la palabra “coalesce”, parece que en realidad no hacen mucho caching, pero me recordó al “grace mode” de Varnish y a su uso para evitar stampedes, es decir, el contexto en el que escuché por primera vez “fusión de solicitudes (request coalescing)” https://varnish-cache.org/docs/6.1/users-guide/vcl-grace.htm...
    También me alegra ver que la hashing consistente aparece una y otra vez. Es una gran tecnología tipo cinta adhesiva cuya utilidad está probada en situaciones similares. Si sabes dónde debería estar algo, todos también saben dónde ir a buscarlo

    • Creo que para ese concepto suelen ser más comunes las expresiones fusión de solicitudes u “origin shielding”. “Grace” lo escucho hoy por primera vez
    • El grace mode en sí no evita las stampedes. Varnish fusiona automáticamente todas las solicitudes, y grace mode se usa para aumentar la probabilidad de que el cliente reciba una respuesta cacheada. Aunque esa respuesta puede estar desactualizada
    • Nginx siempre es más burocrático
      proxy_cache_use_stale updating;
  • Hay algunos datos adicionales de un cofundador de ScyllaDB: Discord no podía completar el repair con Cassandra, pero con Scylla eso no pasaba; Scylla tiene mucho en común con Cassandra, como los árboles LSM y la compaction, pero con su scheduler propio de CPU/IO puede dar a las consultas mayor prioridad que a la compaction
    Dice que pueden posponer la compaction a un intervalo de medio milisegundo en el que haya suficiente ancho de banda ocioso, y que hay muchos artículos relacionados. Scylla tiene un modo más seguro llamado tombstone_gc=repair, de hace 1,5 años, y la nueva arquitectura basada en Raft y tablets lanzada recientemente será el próximo gran cambio para los usuarios

  • Este problema ni siquiera habría existido si se hubiera usado IRC, un protocolo de chat distribuido que existe desde hace más de 40 años.
    También tiene la ventaja de contar con una especificación abierta y varias implementaciones. Tampoco es un jardín amurallado. Si creen que IRC está demasiado viejo para la era moderna, pueden mirar Matrix o XMPP. Es difícil entender cómo Discord llegó a dominar, y más bien se siente cercano a una tragedia.

    • IRC no almacena mensajes, solo los retransmite a los clientes. Para guardar el historial de chat se necesita una solución aparte, y esa es una funcionalidad que se da por sentada desde hace unos 30 años.
      IRC prácticamente exige un bouncer si se quiere seguir una conversación desde varios dispositivos. Tampoco cifra los mensajes; solo cifra, de forma opcional, la conexión entre cliente y servidor. Sin cifrado de extremo a extremo, no hay privacidad frente al servidor o sus operadores, y ese servidor es un punto único de falla que se convierte fácilmente en objetivo.
      El protocolo Matrix todavía está cambiando y las implementaciones no alcanzan a seguir la especificación. Si no usas Element, te quedas atrás en funciones y seguridad. XMPP, igual que IRC, depende de extensiones opcionales para funciones básicas como el cifrado de extremo a extremo, y puede que los clientes no las soporten de forma completa y correcta.
      Recomiendo leer el análisis de soatok: https://soatok.blog/2024/08/04/against-xmppomemo/ https://soatok.blog/2024/08/14/security-issues-in-matrixs-ol...
      Lo de Snowden en 2013 fue hace 11 años. A estas alturas, el cifrado de extremo a extremo debería ser una función básica y tratarse como una commodity, y deberíamos exigirlo con la misma insistencia con la que se exigía HTTPS. Claro que Discord no implementa cifrado de extremo a extremo.
    • El hecho mismo de no entender por qué Discord dominó es la razón por la que Discord dominó. Su experiencia de usuario es 10 veces mejor, y no se quiere reconocer que es adecuada para usuarios comunes no técnicos.
    • Me gusta mucho IRC y no me gusta Discord, pero los otros servicios son demasiado toscos, e incluso IRC hoy en día solo resulta usable mediante IRCCloud, que al menos tiene una app móvil decente.
      Hace poco, un grupo muy técnico al que pertenezco se mudó de Telegram a Matrix, y la experiencia de usuario no es buena. La app tiene bugs y se ve mal, y en la nueva app “Element” no se soportaba SSO, así que no pude usar mi cuenta. Son pequeñas molestias aceptables para alguien como yo, que se mete a investigar por su cuenta, pero no podría convencer a mis amigos de usarlo.
    • Porque era un producto varios órdenes de magnitud mejor que los competidores de la época.
    • Por la usabilidad.
      Descargas el exe, lo instalas, creas una cuenta y ya funciona. Cualquiera puede hacerlo. Hay mucho software útil y excelente, pero la mayoría no es fácil para el público general, y algunos —o la mayoría— ni siquiera tienen GUI. La gente prefiere vender su identidad e incluso pagar dinero antes que soportar demasiados pasos.
  • La conclusión que saqué de este artículo quizá sea un poco distinta de la intención del autor.
    “¿El último? Nuestro amigo cassandra-messages. [...] Fue un clúster grande desde el comienzo. Con billones de mensajes y casi 200 nodos, cualquier migración inevitablemente iba a ser una tarea compleja”.
    Considerando la escala de Discord, que el almacén de mensajes tenga menos de 200 nodos me parece sorprendentemente pequeño. Esperaba una arquitectura compleja, orientada a una escalabilidad mucho más rápida y con muchas más piezas móviles. La complejidad real seguramente sea mayor que la que se muestra en el artículo, pero al recordar mi experiencia siendo parcialmente responsable de más de 200 nodos físicos que hacían mucho menos trabajo, me pregunto cuán sobrediseñadas están las arquitecturas cloud modernas.

    • Aquí se habla de 177 nodos de base de datos, y no se puede juzgar la complejidad de la arquitectura solo por ese número. Imagino que debe haber decenas o cientos de servicios compuestos por múltiples nodos de alta disponibilidad distribuidos en varias regiones.
      Habiendo almacenado miles de millones de registros en un conjunto mucho más pequeño de nodos Cassandra, Cassandra fue realmente doloroso para la guardia on-call y causó varias fallas graves.
    • Creo que es muy probable que la mayoría de las arquitecturas cloud modernas estén sobrediseñadas. Como ejemplo del lado opuesto, se me viene a la mente la arquitectura de Stack Overflow[0].
      [0] https://news.ycombinator.com/item?id=34950843
    • También hay que tener en cuenta que ahora hacen lo mismo con solo 72 nodos.
  • El artículo está muy bien escrito. Me alegra que parte de la solución no haya sido introducir algo completamente distinto, sino cambiar a ScyllaDB, que puede usarse como reemplazo de Cassandra.

  • Discord hace que sea prácticamente imposible borrar mensajes antiguos. Es una pesadilla de privacidad, y me pregunto por qué la UE no ha intervenido.

    • Creo que hace falta un equilibrio. En la comunicación directa, los destinatarios de mensajes antiguos también son partes interesadas, y mantener un historial coherente por defecto es una parte fundamental del “servicio” que ofrecen. El contenido de los mensajes no es lo mismo que recopilar a escondidas patrones de clics. Matrix también reflexionó sobre esto cuando se enfrentó a la misma pregunta.
      La pregunta central se reduce a si Matrix debería verse más como el correo electrónico o más como Facebook. Si fuera correo electrónico, la gente se escandalizaría ante la idea de que el remitente pudiera borrar sus mensajes de mi buzón; si fuera Facebook, la gente se escandalizaría de que una publicación siguiera viéndose en algún lugar incluso después de ejercer el derecho de eliminación.
      Explican que resolver esto requiere criterio, y que lo abordaron considerando primero qué intenta lograr realmente el espíritu del GDPR.
      https://matrix.org/blog/2018/05/08/gdpr-compliance-in-matrix...
    • En la cultura de Discord, los usuarios suelen publicar muchísima información de identificación personal en mensajes de “presentación”, como imágenes, pasatiempos específicos o información médica. Por ejemplo, en comunidades de “apoyo”.
      El problema desde la perspectiva del GDPR es que Discord hace que sea difícil borrar eso. Si detecta la intención de eliminar los datos de la cuenta, guía al usuario hacia la “anonimización”; públicamente el nombre de usuario queda separado de los mensajes, pero todavía se puede rastrear hasta una persona específica. Si del lado del servidor también se maneja así, hacer cumplir la solicitud de un usuario para borrar su información de identificación personal implicaría revisar una cantidad enorme de mensajes o borrar en masa mensajes antiguos.
      El Parlamento de la UE no es un parlamento real en el sentido de que solo la Comisión puede proponer nuevas leyes y el parlamento electo en general solo vota; y considero que quien controla la Comisión no es el público, sino el Departamento de Estado de EE. UU. Newsguard, las grandes tecnológicas estadounidenses que no son Musk, y Discord también están dentro de la misma red de intereses políticos y financieros establecidos, con mucho personal proveniente del Departamento de Estado.
      Sin indignación pública, es muy probable que las instituciones a nivel de la UE sean capturadas, y creo que la opinión pública también la controlan esos intereses establecidos de estilo cyberpunk que entrenan LLM con datos ilegales de Discord y apuntan publicidad electoral. Al final deberíamos preocuparnos por la posibilidad de llegar a un estado orwelliano del que no se pueda escapar, dominado por intereses establecidos permanentes.
      Dicho eso, creo que la mayor parte de esto solo tiene una probabilidad algo superior al 50% de ser cierto en sentido amplio, así que hay que tomarlo con bastante cautela.
    • Dada la escala y el alcance de los datos de usuarios que se recopilan y procesan, también es posible que la UE esté preparando discretamente un caso grande.
  • Cassandra, en esencia, se parece más a una tabla hash distribuida y tolerante a fallos, mayormente solo de inserciones. Si necesitas exactamente eso con un alto rendimiento de escritura, es una buena opción.
    No entiendo por qué la gente la usa como si fuera una base de datos. Te topas enseguida con sus límites, y el dolor de intentar usarla como base de datos empeora a medida que crece la escala.

    • El texto dice que “en Cassandra las lecturas son más caras que las escrituras”.
      Con eso, no le veo sentido como almacén de mensajes para un servidor de chat. Parece adecuada como destino de recolección de logs en sistemas distribuidos, donde muchos clientes vierten datos pero la mayoría de los logs ni siquiera necesitan auditarse, de modo que la cantidad de lecturas de un elemento específico es menor que 1. Los mensajes de Discord claramente no son eso.
    • Tanto la documentación como el sitio web de Cassandra dicen que es una base de datos. No se puede culpar a la gente por confundirse.
      En mi experiencia, nunca he visto un proyecto que empezara usando Cassandra y siguiera usándola después de alrededor de un año. Toma más o menos un año chocar con sus límites, y al final la reemplazan por una base de datos como Postgres.
  • No entiendo por qué no pueden simplemente hacer sharding. ¿No está cada “servidor” de Discord aislado de los demás? Si no pueden enviarse mensajes entre sí, me pregunto por qué no dividir billones de mensajes en miles de shards, de modo que cada shard maneje miles de millones.

    • La clave de partición incluía el ID del canal, y aun con un sharding tan granular seguían teniendo problemas de particiones calientes.
    • La última vez que vi la API de bots de Discord, había cláusulas explícitas relacionadas con sharding.
  • Dice que “el equipo de ScyllaDB priorizó el trabajo de mejora e implementó consultas inversas de buen rendimiento, eliminando el último obstáculo de base de datos del plan de migración”.
    Me pregunto cuánto habrán pagado para que hicieran eso incluso antes de usar ScyllaDB.

    • Según el texto, ya usaban ScyllaDB en todas partes excepto en el almacén de mensajes desde dos años antes de migrar el almacén de mensajes.