- La plataforma de pagos de Uber trasladó una estructura mixta de datos del libro mayor acumulados desde 2017 —más de 1 billón de registros y varios PB de tamaño— en DynamoDB, TerraBlob y LedgerStore, hacia un modelo centrado en LedgerStore
- Debido al costo de DynamoDB, antes solo se conservaban las 12 semanas más recientes de datos; LedgerStore, un almacenamiento tipo libro mayor append-only, fue elegido como solución de largo plazo
- La validación combinó shadow validation que replica el tráfico de producción y validación offline que compara dumps completos, separando así el riesgo entre los datos actuales y los datos fríos
- Como el backfill puede generar más carga que el tráfico normal, se avanzó gradualmente con lotes pequeños, idempotencia, control de velocidad, parada de emergencia y aislamiento de registros problemáticos
- La transición se desplegó de forma conservadora durante varias semanas y, tras un fallback inicial y un mes de retención de datos en DynamoDB, se completó sin interrupciones hasta el respaldo final y la eliminación de las tablas
Objetivo de la migración y estructura de almacenamiento anterior
- La plataforma de pagos de Uber, Gulfstream, usaba DynamoDB como almacenamiento cuando se lanzó en 2017
- A escala de Uber, el costo de DynamoDB creció, por lo que la estructura de almacenamiento se dividió en tres partes
- Los datos calientes de las 12 semanas más recientes se almacenaban en DynamoDB
- Los datos fríos más antiguos se almacenaban en TerraBlob, el blob store interno de Uber
- LedgerStore ya recibía escrituras y se convirtió en el destino final de la migración
- El alcance de la migración cubría los datos del libro mayor de todo el negocio de Uber desde 2017
- Registros inmutables: 1.2 PB comprimidos
- Índices secundarios: 0.5 PB sin comprimir
- En la práctica, los registros del libro mayor no pueden modificarse una vez escritos; si hace falta corregir un problema, sí se pueden modificar los datos de los índices secundarios
Por qué eligieron LedgerStore
- LedgerStore es una base de datos estilo libro mayor append-only
- Su diseño orientado a datos de pagos coincidía con los requisitos de Gulfstream
- Inmutabilidad verificable para comprobar mediante firmas criptográficas si un registro fue alterado
- Almacenamiento en capas que separa datos calientes y fríos según el procesamiento de solicitudes y el costo de almacenamiento
- Mejores características de latencia para índices secundarios con consistencia eventual
- Reducir tres almacenamientos a uno simplificó el código de acceso a almacenamiento de Gulfstream y el diseño de generación de índices
- LedgerStore funciona on-premises dentro de los centros de datos de Uber, lo que puede ofrecer una latencia de red más baja
- La migración a LedgerStore también aporta un ahorro importante en costos recurrentes
Estabilidad del tráfico actual verificada con shadow validation
- Para determinar si el backfill era correcto, definieron cinco criterios
- Completitud: que todos los registros fueran cargados en el backfill
- Exactitud: que todos los registros fueran correctos
- Carga: que LedgerStore pudiera manejar la carga actual
- Latencia: que la latencia P99 de LedgerStore estuviera dentro del rango aceptable
- Latencia de índices: que el retraso en la creación de índices secundarios en segundo plano estuviera dentro del rango aceptable
- La shadow validation comparaba la respuesta basada en el almacenamiento existente con la respuesta usando LedgerStore como fuente de datos
- El objetivo era que la completitud y exactitud del backfill medidas con shadow validation alcanzaran al menos 99.99%, con un límite superior de 99.9999%
- Ese límite era necesario porque investigar hasta el final todos los casos sospechosos en una validación de datos a gran escala puede frenar todo el proyecto
- En migraciones de datos históricos pueden mezclarse escrituras incorrectas de las primeras etapas de desarrollo o corrupción de datos asociada a la escala
- Aunque S3 garantice durabilidad de 11 nueves, a escala de 1 billón de registros aún podrían esperarse 10 registros corruptos
- En índices con consistencia eventual pueden aparecer falsos positivos, donde registros que aparecerán segundos después parecen ausentes en la shadow validation
- Para verificar de forma confiable 6 nueves se requieren 100 millones de comparaciones; comparando 1,000 por segundo se necesita más de un día de recolección de datos
- Para 7 nueves habría que esperar 12 días en las mismas condiciones
- Al replicar el tráfico de producción hacia LedgerStore, también pudieron validar la carga, la latencia, la latencia de índices y la confiabilidad del código de acceso
- Los problemas de latencia y retraso de índices detectados durante la migración llevaron a varias mejoras
- Optimización de la partition key para mejorar la distribución de los datos de índices
- Corrección de problemas de índices que provocaban escaneos de registros en vez de consultas puntuales
- La shadow validation en vivo es útil para los datos que se consultan actualmente, pero ofrece garantías limitadas sobre el conjunto completo de datos históricos a los que casi no se accede
Validación offline y backfill incremental
- La validación offline comparó todos los datos de LedgerStore con dumps de datos de DynamoDB
- Como el tráfico en vivo accede sobre todo a datos recientes, los problemas ocultos en datos fríos son difíciles de detectar solo con shadow validation
- Los registros con problemas de datos debían omitirse para que el backfill pudiera seguir avanzando, y también había que considerar la posibilidad de errores en el propio proceso de backfill
- La tarea de validación más grande trabajó sobre 70 TB comprimidos, estimados en 300 TB sin comprimir, y comparó 760 mil millones de registros en un solo job
- Ese trabajo de Apache Spark requirió data shuffle, y se usaron en conjunto Distributed Shuffle as a Service for Spark, Dynamic Resource Allocation y Speculative Execution
- Los registros faltantes encontrados en la validación offline se usaron como entrada para el backfill incremental
- Al repetir la validación y el backfill, verificaron que todos los registros hubieran sido escritos
Problemas operativos encontrados en el backfill
- El backfill debe comenzar a pequeña escala e ir creciendo gradualmente hasta los límites del sistema
- Si se empuja sin control más allá de esos límites, se termina haciendo un DDoS contra los propios sistemas
- Hay que identificar y resolver cuellos de botella antes de volver a escalar
- Después de cada expansión, se requiere monitoreo cercano
- Si se hace backfill de varios años de datos en pocos meses, la carga será mucho mayor que el tráfico normal
- Si producción procesa 1,000 por segundo y se hace backfill de 100 mil millones de registros a 10,000 por segundo, se necesitan 120 días
- Si el job de backfill puede causar una incidencia mientras se ejecuta, debe detenerse de inmediato
- El backfill no debe ser una tarea que corre de principio a fin de una sola vez, sino dividirse en lotes incrementales
- Cada lote debe ser lo bastante pequeño como para terminar en pocos minutos
- Debe ser idempotente, porque el trabajo puede terminar a mitad del lote
- Al completar un lote, se guardan en archivos y se agregan estadísticas como la cantidad de registros leídos y la cantidad de registros cargados por backfill para seguir el progreso
- Un backfill seguro necesita control de velocidad ajustable
- En Java/Scala se puede usar el
RateLimiterde Guava - Si se puede ejecutar más rápido cuando el tráfico de producción es bajo, se ajusta el RPS monitoreando el estado del sistema
- Uber ajustó el RPS con un esquema de additive increase/multiplicative decrease, pero manteniendo un límite superior por seguridad
- En Java/Scala se puede usar el
- Si se sospecha de una falla o sobrecarga, debe ser posible detener el backfill rápidamente
- Durante incidentes, el backfill debe pausarse como medida preventiva y para reducir ruido
- Incluso después de una falla, el proceso de recuperación del sistema puede generar carga adicional
- La función de parada de emergencia también ayuda a depurar problemas relacionados con la escala
Archivos grandes, tolerancia a fallas y logging
- Conviene mantener los archivos de dump de datos alrededor de 1 GB, con flexibilidad de aproximadamente 10 veces hacia ambos lados
- Si los archivos son demasiado grandes, pueden chocar con límites de MultiPart en varias herramientas
- Si son demasiado pequeños, habrá demasiados archivos y solo listar todo puede tardar mucho
- Al ejecutar comandos de shell se puede topar con el límite de ARGMAX
- Durante la transformación de datos del backfill, inevitablemente aparecen problemas de calidad de datos o registros corruptos
- Como los registros problemáticos están distribuidos de forma aleatoria, no se puede detener todo el job cada vez
- Al mismo tiempo, tampoco deben ignorarse porque podrían ser errores de código
- Los registros problemáticos se vuelcan por separado y se monitorean sus estadísticas
- Si la tasa de fallos es alta, el backfill se detiene manualmente, se corrige el problema y luego se reanuda
- Las escrituras de registros pueden fallar por
RPC timeout- Se puede reintentar, pero en algún punto hay que seguir adelante y abandonar esos intentos sin importar la causa para que el trabajo completo avance
- Aunque se quiera dejar muchos logs para depurar y medir el progreso, eso puede ejercer una gran presión sobre la infraestructura de logging
- Incluso si es posible guardar los logs, el volumen a retener puede ser excesivo
- Se aplicó rate limiting a las partes que generan muchos logs
- Si los errores ocurren rara vez, sí puede registrarse cada error
Rollout gradual y eliminación del fallback
- La migración a LedgerStore redujo riesgos no solo con validación y análisis de estadísticas de backfill, sino también mediante un rollout conservador
- El rollout se llevó a cabo durante varias semanas y con aprobación de los ingenieros on-call de los principales servicios consumidores
- Al principio se usó un fallback que consultaba DynamoDB si no se encontraba el dato en LedgerStore
- Cada registro marcado como faltante en los logs del fallback se volvió a verificar para confirmar si realmente faltaba en LedgerStore
- Incluso después de eliminar el fallback, los datos de DynamoDB se conservaron durante un mes
- Después se detuvieron las escrituras a DynamoDB, se creó un respaldo final y se eliminaron las tablas
- Toda la migración tomó 2 años y se completó sin downtime ni incidentes, ni durante ni después de la migración
1 comentarios
Opiniones de Hacker News
Me pregunto si sería posible meter 1.7 petabytes de datos (1 billón de registros indexados) en un solo servidor bare metal muy potente, de menos de unos cuantos miles de dólares al mes, y servirlos con SQLite.
Por ejemplo, algo así: https://use.expensify.com/blog/scaling-sqlite-to-4m-qps-on-a...
SQLite soporta bases de datos de hasta 281 terabytes, asumiendo que puedas encontrar un disco y un sistema de archivos que soporten archivos de 281 terabytes. Aun así, dice que si el contenido empieza a acercarse a tamaños de terabytes, conviene considerar una base de datos cliente/servidor centralizada en lugar de SQLite.
Parece difícil meter esa cantidad de almacenamiento en un solo servidor, y mucho más por unos cuantos miles de dólares al mes. SQLite tampoco encaja para este caso de uso.
Si una empresa tiene suficiente capital y capacidad organizativa, puede construir su propia nube mucho más barata, pero buena parte del cálculo está en tercerizar los riesgos.
[0] https://www.sqlite.org/releaselog/3_33_0.html
[1] https://www.sqlite.org/limits.html (#12)
LedgerStore no parece ser open source [1], y para encontrar información relacionada hay que seguir artículos del blog de Uber que se enlazan entre sí.
De los artículos de 2021, este parece ser el que tiene más información sobre LedgerStore:
https://www.uber.com/en-US/blog/dynamodb-to-docstore-migrati...
[1]:https://github.com/uber
En general, Uber parece tener una fuerte tendencia a construirlo por su cuenta. Suelen concluir que las soluciones open source existentes no son suficientes y prefieren crear las propias. Por ejemplo, es distinto al enfoque de Facebook de mejorar MySQL con MyRocks/RocksDB y mantenerlo como open source.
Al leer el artículo queda claro que Uber estuvo usando mal DynamoDB desde bastante temprano.
Parece que algunos recorridos críticos de usuario necesitaban consistencia fuerte, y que las transacciones históricas requerían mucho data warehousing.
Es raro que no hayan cambiado primero la estructura de DynamoDB de 2 tablas por algo como DynamoDB + Redshift. Es un patrón bastante común.
Alrededor de 2015 hubo una época en la que empresas tecnológicas interesantes como Netflix, Spotify, SoundCloud y Uber creaban muchas herramientas de infraestructura y bases de datos.
Hoy los ingenieros suelen hablar en términos de AWS/nube.
Se siente refrescante que todavía haya organizaciones que construyan este tipo de herramientas por su cuenta.
No sé cuál será la economía específica de este proyecto, pero DynamoDB es realmente caro.
En algún momento pensé que todos los demás estaban usando DynamoDB mal, haciendo scans y queries en vez de búsquedas puntuales sobre tablas precalculadas.
Pero incluso usándolo como una tabla hash distribuida, sigues pagando una prima importante.
Una RCU lee hasta 4 KB, así que para leer 100 MB necesitas 100,000 RCU, lo que son 30,000 dólares al año o 2,500 dólares al mes. Si mi cálculo no está mal, no veo nada comparable en precio.
Me pregunto si habrán evaluado https://tigerbeetle.com.
Y Uber probablemente sea una de las pocas grandes empresas con un contrato de soporte con la Zig Foundation.
Felicitaciones a quienes participaron en este trabajo. Dicho eso, solo el costo de operar este equipo ya debe ser bastante grande y probablemente no muy distinto de los 6 millones de dólares de ahorro, a lo que se suma la carga de mantenimiento.
Tampoco parece muy probable que un sistema de pagos sea una apuesta de largo plazo, así que es interesante por qué los equipos toman proyectos así. ¿Será una especie de costo hundido por los equipos de ingeniería que ya tienen?
Entre ambos extremos hay bastante espacio para una estimación de costos más realista.
Si los datos e índices suman 1.7 PB, con el precio de lista del almacenamiento de DynamoDB son aproximadamente 5.1 millones de dólares al año.
Parece bastante realista para tener un sistema dedicado ajustado a una parte central del negocio.
No es un trabajo tan enorme como algunos están diciendo. Además, como los registros son inmutables, muchas partes se vuelven mucho más fáciles.
Me pregunto si este es otro caso excepcional en el que, al llegar a cierta escala, conviene más construirlo por cuenta propia. La escala que Uber tiene que manejar es bastante sorprendente.
Con el texto original no queda claro cuál es el costo total de propiedad del servicio refactorizado. ¿Ahora no tienen que administrar su propia base de datos y el almacenamiento detrás de ella? ¿Me estoy perdiendo algo?
Esa empresa no vendía un producto para usuarios finales, sino que su producto era un sistema de archivos distribuido.
En mi opinión, la mayoría de las empresas no tiene la experiencia para construir sistemas como bases de datos, y aunque desde el punto de vista de costos el desarrollo parezca conveniente, en la práctica les da miedo hacerlo.
Me parece un muy buen ejemplo de lo caros que pueden ser los almacenes de datos propietarios basados en la nube, y de que migrar desde ahí a otra cosa es realmente posible.
Suponiendo que actúen de forma lógica, creo que llegará la hora de rendir cuentas para los proveedores de servicios en la nube.
Una vez trabajé por contrato en una empresa pequeña que usaba GCP Bigtable para ejecutar reportes con datos provenientes de una DB MySQL de 375 MB, y pagaba más de 11,000 dólares al mes.
Habían contratado a una data scientist recién salida de la universidad para crear reportes, y estaba haciendo algo absurdamente ineficiente sobre un dataset diminuto. Me pidieron que lo arreglara al día siguiente por una miseria y me negué.
Es simplemente un sistema mal diseñado. Habría pasado lo mismo si hubieran sobreaprovisionado de forma absurda una base de datos on-premises para hacer lo mismo.