Este artículo va sobre la construcción del muro de Reddit, algo que me llamó muchísimo la atención a nivel de funcionalidad. A mi parecer fue un gran proyecto ya que se necesitaban alcanzar una serie de grandes requerimientos.
Antes de seguir, una breve aclaración. Seguramente leas el término “baldosa”. Esta palabra hace referencia a cada pequeño pedazo que un usuario podía pintar en el muro de Reddit. Más adelante la veras más seguido
Comencemos…
Según cuenta el artículo, el equipo cada año en el día de los inocentes quiere hacer un proyecto que explore la manera en la que los humanos interactuan a gran escala. En 2017 (año donde nace el muro de Reddit) eligieron un gran canva colaboratio, donde cada uno podia “pintar una baldosa” cada unos pocos minutos, y que todos puedan ver el progreso en tiempo real.
En cuanto a backend, comienzan a contar que el desafío principal fue mantener a todos los usuarios (clientes) sincronizados junto con el muro. Osea, que todos estén viendo lo mismo, al mismo tiempo.
Su solución fue inicia el estado del cliente escuchando la colocación de baldosas en tiempo real, e inmediatamente luego hacer la request para el tablero completo. La response del muro completo podía estar desactualiada unos pocos segundos, siempre y cuandotuvieramos la colocación de baldosas en tiempo real. Cuando el usuario recibe el tablero completo, se traen tambien las baldosas en tiempo real que recibió meintras esperaba. Una vez cargado el muro completo, las baldosas que siguieran se actualizaban al momento de ser recibidas.
Para este esquema de trabajo, necesitaron hacer la request del estado completo del muro lo más rápido posible. Su propuesta inicial fue guardar el muro completo en una sola fila en Cassandra y cada request del muro leería la columna entera. El formato que tenían planeado para cada columna en la fila era el siguiente:
(x, y): {‘timestamp’: epochms, ‘author’: user_name, ‘color’: color}
Basicamente se guardaban las cordenadas en el eje x y en el eje y, para luego guardar el tiempo, el autor y el color de la baldosa.
Esta propuesta viene porque el tablero contiene 1 millón de baldosas, lo que significa que debería leer 1 millón de columnas. En su cluster de producción eso les tomaría un tiempo de 30 segundos, un tiempo totalmente inaceptablemente lento y que podría ejercer una presión excesiva sobre Cassandra.
El siguiente enfoque fue guardar el muro completo en Redis. Guardaron en formato bitfield (campo de bits usado en Redis) de 1 millón de enteros de 4 bits. Cada entero de 4 bits puede codificar un color de 4 bits, y las cordenadas x e y se determinaban mediante el desplazamiento (offset = x + 1000y) dentro del campo de bits. Con esto podrían leer el estado del muro leyendo el campo de bits completo. Esto ademas permite actualizar individualmente baldosas actualizando el valor del campo de bits en un offset específico.
Igualmente todavía necesitaban guardar todos los detalles en Cassandra para que los usuarios puedan inspeccionar las baldosas individuales para ver quien las colocó y cuando. Tenían planeado utilizar Cassandra para restaurar el muro en caso de que Redis falle. Leer el muro entero desde Redis tomaba menos de 100 milisgundos, lo cual era suficientemente rápido.
Ilustración de como los colores son guardados en Redis usando un muro de 2x2
Estaban conscientes de exceder la capacidad máxima de bandaancha en Redis. Si muchos clientes se conectaban o frescaban al mismo tiempo el estado del muro solicitarían muchas lecturas en simultáneo a Redis. La solución fue cachearlo en el CDN porque es simple de implementar y significaba que el cache estaba lo más cerca posible del cliente, lo que ayuda a mejorar la velocidad de respuesta.
Peticiones para el estado completo del muro eran cacheadas por Fastly con expiración de 1 segundo. También comentan que añadieron un control de caché “stale-while-revalidate” para evitar que se generaran más solicitudes de las deseadas cuando la caché del tablero caducaba. Fastly mantiene alrededor de 33 puntos de presencia (POP) que realizan almacenamiento en caché de manera independiente, por lo que esperábamos recibimo como máximo 33 peticiones por segundo para el muro completo.
Para devolver las actualizaciones al cliente utilizaron websockets. Tuvieron éxito usandolo en producción para reddit live para más de 100,000 clientes simultáneos con todo lo que ello conlleva.
*Diagrama de peticiones entre el cliente y Reddit
Las peticiones van por Fastly. Si hay una copia del muro que no haya expirado, se devolvería inmediatamente sin necesidad de interactuar con los servidores de la aplicación de Reddit. Caso contrario, si no hay una copia válida en caché o si esta está desactualizada, Reddit leería todo el muro desde Redis y devolvería esa información al cliente por Fastly para que la almacene en caché y la devuelva al cliente.
Las peticiones nunca superaban las 33/s, lo que significa que el caché de Fastly funcionaba correctamente y prevenía que la mayoría de peticiones le peguen a la aplicación de Reddit.
Cuando una petición se hacía a la petición de Reddit, esta leí Redis de una manera eficiente y rápida.
*Guardado de una baldosa