El sistema de recomendaciones de Medium tiene un objetivo claro y complejo: mantener a los lectores enganchados ofreciéndoles contenido relevante y personalizado. Para conseguirlo, procesan continuamente señales de actividad del usuario —como historias leídas, recomendaciones vistas, seguidores y me gusta— y las correlacionan con un flujo constante de millones de nuevos artículos mensuales.
Este proceso requiere modelos inteligentes y lógica de inferencia efectiva, pero eso no basta: la rapidez para almacenar y acceder a los datos es clave para mantener la relevancia mientras el usuario navega. Esta función la cumple el feature store de Medium, cuyo diseño de modelo de datos empezó a ser un cuello de botella al escalar a un millón de operaciones por segundo.
Andréas Saudemont, ingeniero principal de software en Medium, explicó recientemente cómo identificaron este problema y cómo reinventaron su arquitectura para solucionarlo.
El papel central del feature store en la personalización
El feature store es la pieza que integra toda la información de actividad y eventos internos, alimentando los modelos de machine learning que generan las recomendaciones personalizadas, como el feed «Para ti» que reciben los usuarios registrados.
Cada característica o feature corresponde a una propiedad de una entidad, normalmente un usuario o una historia. Algunas son datos simples y estáticos, por ejemplo, si un usuario tiene una suscripción de pago; otras almacenan el historial de interacción, como qué historias ha leído o qué contenido se le ha mostrado recientemente.
El problema del modelo relacional para features
En la arquitectura original, Medium usaba un modelo relacional que asociaba características con múltiples valores vinculados a diferentes entidades. Por ejemplo, el feature «historias leídas por usuarios» relaciona historias con usuarios y registra cuándo un usuario leyó cierto artículo.
Este modelo divide los datos entre dos tablas: una para las IDs de las entidades y otra para los valores. Por tanto, obtener tanto la identidad como los valores de las entidades requiere múltiples consultas. Si quieres recuperar el valor para 1000 entidades, debes ejecutar 1000 consultas adicionales, lo que genera un grave problema de rendimiento.
Además, emplear ALLOW FILTERING en Cassandra para estas consultas provocaba que cerca del 90% de las filas leídas se descartasen, disparando el consumo innecesario de recursos y retrasos.
«ALLOW_FILTERING debería ser una solución puntual, no el patrón de diseño habitual.»
La transición al modelo basado en listas
Para solucionar estos problemas, reinventaron el modelo de datos apostando por un feature store basado en listas: toda la información relevante para una entidad vive en una única tabla y se recupera en una sola consulta eficiente.
Mientras que un feature tradicional tiene un único valor—como verdadero o falso—, un feature lista incluye una colección de ítems, cada uno con un valor y una marca temporal. Por ejemplo, el historial de lectura de un usuario es una lista de pares (ID de historia, fecha de lectura) con un tiempo de vida (TTL) que actualmente es de seis meses. Esto permite eliminar automáticamente datos antiguos irrelevantes para las recomendaciones.
Estas listas permiten múltiples operaciones: creación y eliminación ocasional de listas, añadir o eliminar ítems específicos, e incluso recuperar millones de ítems por segundo en consultas con elevada concurrencia.
Gestión de múltiples ítems con la misma marca temporal
Otro desafío fue soportar múltiples ítems con idéntico timestamp, como cuando se presentan simultáneamente varias miniaturas de historias al usuario. Para evitar colisiones en la clave primaria, diseñaron una tabla donde la clave de clúster concatena la marca temporal con un hash MD5 del valor, garantizando la unicidad incluso para entradas múltiples en el mismo instante.
Control del coste mediante TTL nativo
El almacenamiento está gestionado gracias al TTL de ScyllaDB, que elimina automáticamente los datos según su antigüedad, sin necesidad de lógica adicional. Esto mantiene constante el uso de almacenamiento a pesar del alto volumen de escritura y facilita la retirada de features obsoletos.
Implementación eficiente de operaciones clave
Las funciones para añadir y recuperar ítems en la lista se implementan con consultas eficientes a particiones únicas, evitando costes excesivos y maximizando la velocidad. La eliminación de ítems específicos es más compleja porque el valor no forma parte de la clave primaria, por lo que recurrieron a un índice secundario local que permite localizar y borrar rápidamente los registros pertinentes en batches atómicos.
Comparativa de rendimiento: ScyllaDB frente a DynamoDB
Medium probó sus operaciones listas en ambas bases de datos con sus datos reales de producción. En latencias medianas (P50), ScyllaDB logró respuestas en menos de 1,5 ms para añadir ítems, mientras que DynamoDB se situó bajo los 5 ms. En niveles P95 y P99, ScyllaDB mantuvo una latencia estable alrededor de 5-6 ms y cifras aún desdeñables en P99, en contraste con picos de 13 a 120 ms en DynamoDB.
Para consultas de recuperación, ScyllaDB alcanzó 1 ms en P50 y se mantuvo por debajo de 30 ms en P99, mientras DynamoDB experimentó picos de hasta 220 ms. Aunque DynamoDB no contaba con una capa de caché adicional que podría mejorar resultados, el rendimiento predecible y la velocidad de ScyllaDB son un gran valor añadido para Medium.
Reflexiones finales y lecciones aprendidas
El paso a un modelo de datos optimizado basándose en listas no solo mejoró el rendimiento del feature store, sino que ahora Medium está más confiada en ampliar el uso de ScyllaDB para otras cargas de trabajo internas.
Como recomendación, Andréas subrayó la importancia de invertir tiempo en diseñar correctamente el modelo de datos desde el inicio: “Un modelo subóptimo genera consultas lentas que escalan mal, y corregirlo en producción es muy costoso. Planifícalo bien antes de implementar.”