El dilema que define la arquitectura de datos moderna
Hay una pregunta que todo arquitecto de datos se hace en algún momento de su carrera, generalmente a las tres de la madrugada mientras intenta entender por qué el sistema de pagos ha dejado de funcionar: ¿cómo es posible que algo tan fundamental como almacenar datos se haya convertido en una disciplina tan compleja? La respuesta corta es que no estamos hablando de almacenar datos, sino de almacenarlos en múltiples lugares simultáneamente, mantenerlos sincronizados, garantizar que las transacciones sean consistentes y, de paso, asegurarnos de que el sistema siga funcionando cuando uno de esos lugares decida desaparecer del mapa.
Las bases de datos distribuidas representan uno de los desafíos más fascinantes de la ingeniería de software moderna. No porque sean intrínsecamente complicadas —que lo son—, sino porque nos obligan a enfrentarnos con las limitaciones físicas del universo. La velocidad de la luz tiene un límite, los cables de red se cortan, los servidores fallan y, por mucho que invirtamos en hardware, siempre habrá un momento en que dos nodos del sistema tendrán visiones diferentes de la realidad.
Este capítulo está pensado para CIOs que necesitan tomar decisiones estratégicas sobre infraestructura de datos, arquitectos que deben diseñar sistemas capaces de escalar y mantenerse disponibles, e ingenieros que implementarán y operarán estos sistemas en producción. No vamos a esquivar la complejidad técnica —entenderla es imprescindible—, pero tampoco nos perderemos en ella sin conectarla con las decisiones de negocio que importan.
Resumen ejecutivo
Las bases de datos distribuidas permiten que los datos residan en múltiples servidores o ubicaciones geográficas, proporcionando escalabilidad horizontal, alta disponibilidad y tolerancia a fallos. Sin embargo, esta distribución introduce compromisos fundamentales que el teorema CAP formaliza: en presencia de una partición de red, un sistema distribuido debe elegir entre consistencia y disponibilidad.
Los protocolos de consenso como Paxos y Raft proporcionan los mecanismos para que múltiples nodos acuerden un estado común, siendo Raft la opción preferida en implementaciones modernas por su mayor comprensibilidad. Las estrategias de particionado determinan cómo se dividen los datos entre nodos, mientras que la replicación define cómo se mantienen copias sincronizadas para durabilidad y rendimiento de lectura.
El impacto operativo de estas decisiones es sustancial: afectan directamente a la latencia de las operaciones, la complejidad del despliegue, los costes de infraestructura y la capacidad de recuperación ante desastres. Elegir entre consistencia fuerte y eventual, entre particionado por hash o por rango, entre replicación síncrona o asíncrona, no son decisiones técnicas aisladas sino compromisos de negocio que deben alinearse con los requisitos de la aplicación.
Fundamentos de la distribución: por qué y cuándo distribuir
Antes de sumergirnos en los mecanismos técnicos, conviene preguntarse por qué distribuir una base de datos en primer lugar. Las motivaciones principales son tres, y entenderlas ayuda a tomar mejores decisiones arquitectónicas.
La primera es la escalabilidad horizontal. Existe un límite físico a cuánto puede crecer un único servidor: más RAM, más CPU, discos más rápidos tienen un techo que, además, se vuelve exponencialmente más caro de superar. La distribución permite añadir capacidad agregando más máquinas commodity en lugar de invertir en hardware cada vez más especializado. Empresas como Google o Facebook procesan petabytes de datos no porque tengan supercomputadores mágicos, sino porque han distribuido la carga entre miles de servidores relativamente modestos.
La segunda motivación es la disponibilidad. Un único servidor es un único punto de fallo. Por robusto que sea el hardware, eventualmente fallará: el disco se corromperá, la fuente de alimentación se quemará o un operador desconectará el cable equivocado. La distribución permite que el sistema continúe funcionando aunque algunos de sus componentes fallen, lo que resulta crítico para aplicaciones que no pueden permitirse tiempos de inactividad.
La tercera motivación, frecuentemente subestimada, es la proximidad geográfica. Si tus usuarios están repartidos por el mundo, tener una única base de datos en un centro de datos de Frankfurt implica que un usuario en Tokio experimentará latencias de más de 200 milisegundos solo por el viaje de ida y vuelta de los paquetes de red. Distribuir los datos geográficamente permite servir a los usuarios desde réplicas cercanas, mejorando drásticamente la experiencia.
Sin embargo, distribuir tiene costes que no siempre se aprecian hasta que el sistema está en producción. La complejidad operativa aumenta significativamente: más servidores implican más componentes que pueden fallar, más conexiones de red que monitorizar, más actualizaciones de software que coordinar. La depuración de problemas se vuelve más difícil cuando los datos y las operaciones están repartidos entre múltiples nodos. Y ciertos patrones de acceso que son triviales en una base de datos centralizada —como las transacciones que tocan múltiples tablas o las consultas que agregan datos de diferentes particiones— se convierten en operaciones complejas y potencialmente lentas.
La decisión de distribuir no debería tomarse por defecto ni por seguir modas tecnológicas. Una base de datos centralizada bien dimensionada puede manejar cargas sorprendentemente altas: un PostgreSQL optimizado puede procesar decenas de miles de transacciones por segundo. La distribución tiene sentido cuando los requisitos de escalabilidad, disponibilidad o latencia geográfica exceden lo que una arquitectura centralizada puede ofrecer razonablemente.
El teorema CAP: el triángulo imposible
En el año 2000, Eric Brewer presentó una conjetura que se convertiría en una de las verdades fundamentales de los sistemas distribuidos. Dos años después, Seth Gilbert y Nancy Lynch la demostraron formalmente, elevándola a la categoría de teorema. El teorema CAP establece que un sistema de datos distribuido puede proporcionar, como máximo, dos de las siguientes tres garantías: Consistencia, Disponibilidad y tolerancia a Particiones.
La consistencia, en el contexto de CAP, significa que todos los nodos del sistema ven los mismos datos al mismo tiempo. Cuando escribes un valor, cualquier lectura posterior desde cualquier nodo devolverá ese valor o uno más reciente. Es lo que esperamos intuitivamente de una base de datos: si actualizo el saldo de una cuenta, la siguiente consulta debería reflejar ese cambio.
La disponibilidad significa que cada solicitud recibe una respuesta, sin garantía de que contenga la versión más reciente de la información. Un sistema disponible siempre responde a las peticiones, incluso si algunos de sus nodos no están funcionando correctamente.
La tolerancia a particiones significa que el sistema sigue operando aunque se pierda la comunicación entre algunos de sus nodos. En un sistema distribuido real, las particiones de red son inevitables: cables que se cortan, switches que fallan, congestión que provoca timeouts. Un sistema tolerante a particiones no deja de funcionar cuando esto ocurre.
La parte crucial del teorema es que no se trata de elegir dos cualidades y sacrificar la tercera de forma permanente. La elección solo se activa cuando ocurre una partición de red. En condiciones normales, cuando todos los nodos pueden comunicarse entre sí, es perfectamente posible tener consistencia y disponibilidad simultáneamente. El dilema aparece cuando la red falla: en ese momento, el sistema debe decidir si prioriza la consistencia —rechazando peticiones hasta que pueda verificar que tiene datos actualizados— o la disponibilidad —respondiendo con los datos que tiene, aunque puedan estar desactualizados—.
Esta comprensión matizada del teorema CAP es importante porque la elección no es binaria ni permanente. Sistemas como Cassandra permiten configurar diferentes niveles de consistencia por operación: puedes exigir consistencia fuerte para las transacciones financieras y aceptar consistencia eventual para las métricas de analytics. CockroachDB prioriza la consistencia pero implementa mecanismos sofisticados para minimizar el impacto en la disponibilidad durante particiones parciales.
En la práctica, la mayoría de los sistemas modernos eligen ser CP (consistentes y tolerantes a particiones) o AP (disponibles y tolerantes a particiones), dado que la tolerancia a particiones no es realmente opcional en un sistema distribuido real. Los sistemas CA teóricamente posibles solo funcionan en redes que nunca fallan, lo cual es una fantasía en cualquier despliegue de producción.
Más allá de CAP: el modelo PACELC
El teorema CAP, aunque fundamental, resulta insuficiente para guiar muchas decisiones prácticas. Solo describe el comportamiento del sistema durante particiones de red, que son relativamente infrecuentes. ¿Qué ocurre el 99.9% del tiempo, cuando todo funciona correctamente?
Daniel Abadi propuso el modelo PACELC como extensión de CAP. El acrónimo significa: si hay una Partición (P), elige entre Disponibilidad (A) y Consistencia (C); de lo contrario (Else, E), elige entre Latencia (L) y Consistencia (C).
Esta extensión captura un compromiso crítico que CAP ignora: incluso cuando no hay particiones, mantener consistencia fuerte tiene un coste de latencia. Para garantizar que todos los nodos tienen los mismos datos antes de confirmar una escritura, el sistema debe esperar a que múltiples réplicas confirmen la operación. Esto añade latencia de red a cada transacción.
Bajo este modelo, los sistemas se pueden clasificar más precisamente. DynamoDB y Cassandra son PA/EL: durante particiones priorizan disponibilidad, y en condiciones normales priorizan baja latencia sobre consistencia. PostgreSQL tradicional y MySQL son PC/EC: priorizan consistencia siempre, aceptando mayor latencia y menor disponibilidad durante particiones. CockroachDB y Spanner son PC/EC con optimizaciones sofisticadas para minimizar el impacto en latencia.
PACELC resulta más útil que CAP para tomar decisiones arquitectónicas porque refleja los compromisos que realmente importan en el día a día. Una aplicación de trading de alta frecuencia no puede aceptar latencias adicionales por consistencia fuerte en condiciones normales. Una aplicación bancaria no puede aceptar inconsistencias aunque eso mejore la latencia. Entender dónde se sitúa tu aplicación en estos ejes es esencial para elegir la tecnología correcta.
Protocolos de consenso: cómo ponerse de acuerdo
El corazón de cualquier sistema distribuido consistente es su protocolo de consenso: el mecanismo mediante el cual múltiples nodos acuerdan un valor o una secuencia de operaciones. Sin consenso, no hay forma de garantizar que todos los nodos vean la misma versión de los datos.
El problema del consenso distribuido fue formalizado por Leslie Lamport en los años 80, quien también propuso la primera solución práctica: Paxos. Sin embargo, Paxos tiene fama de ser extraordinariamente difícil de entender e implementar correctamente. El propio Lamport presentó el algoritmo como un parlamento ficticio en una isla griega, y la metáfora resultó tan confusa que muchos lectores abandonaron el paper convencidos de que era una broma elaborada.
Paxos funciona en dos fases: preparación y aceptación. En la fase de preparación, un nodo que quiere proponer un valor envía un mensaje a la mayoría de los nodos con un número de propuesta único. Los nodos responden prometiendo no aceptar propuestas anteriores y comunicando cualquier valor que ya hayan aceptado. En la fase de aceptación, si el proponente recibe respuestas de una mayoría, envía el valor a aceptar. Si una mayoría acepta, el valor queda decidido.
La genialidad de Paxos está en cómo maneja los fallos y las propuestas concurrentes. Si un proponente falla a mitad del protocolo, otro puede retomarlo. Si dos proponentes compiten, el sistema garantiza que solo uno tendrá éxito, y el valor decidido será consistente. Pero implementar esto correctamente es diabólicamente difícil. Los casos extremos son numerosos y sutiles, y errores de implementación han causado pérdidas de datos en producción.
Raft surgió en 2014 como respuesta a la complejidad de Paxos. Diseñado explícitamente para ser comprensible, Raft descompone el consenso en subproblemas más manejables: elección de líder, replicación de log y seguridad. En Raft, siempre hay un único líder que recibe todas las escrituras y las replica a los seguidores. Si el líder falla, los seguidores detectan la ausencia de heartbeats y eligen un nuevo líder mediante votación.
El enfoque de liderazgo fuerte de Raft simplifica enormemente el razonamiento sobre el sistema. Todas las decisiones fluyen del líder a los seguidores, eliminando los casos complejos de propuestas concurrentes que hacen Paxos tan difícil. Por supuesto, esto introduce sus propios compromisos: el líder se convierte en un cuello de botella potencial, y las elecciones de líder causan breves interrupciones en la disponibilidad de escritura.
En la práctica, Raft ha ganado la batalla de la adopción. etcd, el almacén de configuración de Kubernetes, usa Raft. CockroachDB usa Raft. TiDB usa Raft. La comprensibilidad no es solo una virtud académica: significa que más ingenieros pueden entender el código, más errores se detectan en revisiones, y más operadores pueden diagnosticar problemas en producción.
Multi-Paxos y sus variantes siguen usándose en sistemas como Spanner de Google, donde el rendimiento extremo justifica la complejidad adicional. Pero para la mayoría de las organizaciones, la recomendación clara es preferir sistemas basados en Raft.
Estrategias de particionado: dividir para conquistar
Una vez que tenemos múltiples nodos que pueden ponerse de acuerdo, necesitamos decidir cómo distribuir los datos entre ellos. Esta decisión se conoce como particionado o sharding, y tiene implicaciones profundas en el rendimiento, la escalabilidad y la operatividad del sistema.
El particionado por rango divide los datos según intervalos de una clave de partición. Si la clave es una fecha, todos los datos de enero irían a una partición, los de febrero a otra, y así sucesivamente. Si la clave es un ID de usuario, los usuarios del 1 al 1000 irían a la partición A, del 1001 al 2000 a la B, etcétera.
La principal ventaja del particionado por rango es que las consultas por rango son eficientes: obtener todos los pedidos de una semana específica solo requiere consultar la partición correspondiente. Esto lo hace ideal para datos temporales, logs, series temporales y cualquier caso donde los patrones de acceso típicos involucren rangos contiguos de claves.
Sin embargo, el particionado por rango tiene un riesgo importante: los hotspots. Si los datos nuevos siempre tienen claves mayores —como ocurre con timestamps o IDs auto-incrementales—, todas las escrituras recientes irán a la misma partición. Esta partición se convertirá en un cuello de botella mientras las demás permanecen infrautilizadas. Hemos visto sistemas donde el 90% de la carga recaía sobre el 10% de los nodos precisamente por este problema.
El particionado por hash aplica una función hash a la clave de partición y distribuye los datos según el resultado. Si el hash es uniforme, los datos se distribuirán equitativamente entre las particiones independientemente del patrón de las claves originales. Timestamps consecutivos, IDs secuenciales, cualquier patrón que causaría hotspots con particionado por rango se dispersa aleatoriamente con hash.
El precio del particionado por hash es la pérdida de localidad: las consultas por rango deben consultar todas las particiones porque no hay garantía de que las claves adyacentes estén en la misma partición. Para aplicaciones que principalmente acceden a registros individuales por clave, esto no es problema. Para aplicaciones que necesitan escanear rangos de datos, puede ser catastrófico para el rendimiento.
El hash consistente es una variante sofisticada que facilita el rebalanceo cuando se añaden o eliminan nodos. En lugar de asignar particiones fijas a nodos, tanto las claves como los nodos se mapean a un anillo virtual. Cada clave se asigna al siguiente nodo en el anillo. Cuando se añade un nodo, solo las claves del segmento donde se inserta necesitan moverse. Cassandra, DynamoDB y Riak usan variantes de hash consistente.
El particionado compuesto combina múltiples estrategias. Una práctica común es particionar primero por hash de una clave principal —digamos, ID de inquilino en un sistema multi-tenant— y luego por rango de una clave secundaria —como fecha—. Esto permite distribuir la carga entre tenants mientras se mantiene la eficiencia de consultas temporales dentro de cada tenant.
La elección de estrategia de particionado no es reversible fácilmente. Cambiar de un esquema a otro implica migrar todos los datos, lo cual es una operación compleja que puede requerir tiempo de inactividad. Por eso es crucial analizar los patrones de acceso esperados antes de tomar esta decisión. ¿Las consultas típicas serán por clave individual o por rangos? ¿Hay una dimensión de los datos que crece más rápido que otras? ¿Existen claves que serán mucho más populares que el resto?
Estrategias de replicación: copias que salvan
Mientras que el particionado divide los datos entre nodos, la replicación crea copias de los datos en múltiples nodos. Los objetivos de la replicación son durabilidad —si un nodo falla, los datos no se pierden—, disponibilidad —si un nodo no está accesible, otro puede servir las peticiones— y rendimiento de lectura —múltiples réplicas pueden servir lecturas en paralelo—.
La replicación con líder único es el modelo más común. Un nodo es designado como líder o primario para cada partición de datos. Todas las escrituras van al líder, que las replica a uno o más seguidores. Las lecturas pueden ir al líder —para garantizar datos actualizados— o a los seguidores —para distribuir la carga pero con posible lag—.
PostgreSQL, MySQL, MongoDB y la mayoría de las bases de datos tradicionales usan este modelo. Es relativamente simple de razonar: siempre hay una fuente de verdad clara para cada dato. Los conflictos de escritura no existen porque todas las escrituras pasan por el mismo nodo.
La replicación síncrona espera a que los seguidores confirmen cada escritura antes de considerarla completada. Esto garantiza que, si el líder falla, al menos un seguidor tiene todos los datos confirmados y puede asumir el liderazgo sin pérdida. El coste es latencia: cada escritura debe esperar a la confirmación de red de los seguidores.
La replicación asíncrona confirma las escrituras tan pronto como el líder las persiste, sin esperar a los seguidores. Esto minimiza la latencia pero introduce una ventana de pérdida potencial: si el líder falla antes de replicar una escritura a los seguidores, esa escritura se pierde aunque se haya confirmado al cliente.
La replicación semi-síncrona es un compromiso: se espera a que al menos N seguidores confirmen, donde N es menor que el total de seguidores. Esto proporciona cierto nivel de durabilidad sin el coste de latencia de esperar a todos.
La replicación multi-líder permite escrituras en múltiples nodos simultáneamente. Esto mejora la disponibilidad de escritura y reduce la latencia para escrituras geográficamente distribuidas: un usuario en Asia puede escribir en un líder asiático sin esperar a un viaje de ida y vuelta a Europa. Pero introduce un problema fundamental: los conflictos. ¿Qué ocurre si dos usuarios modifican el mismo dato en dos líderes diferentes antes de que las actualizaciones se repliquen?
La resolución de conflictos en sistemas multi-líder es un tema complejo con múltiples estrategias. La más simple es "el último escritor gana" (LWW): se asignan timestamps a las escrituras y la más reciente sobrescribe a las anteriores. Esto es simple pero puede perder datos silenciosamente. Alternativas más sofisticadas incluyen vectores de versión, CRDTs (Conflict-free Replicated Data Types) y resolución manual por la aplicación.
La replicación sin líder, popularizada por Amazon Dynamo y adoptada por Cassandra y Riak, elimina el concepto de líder por completo. Las escrituras y lecturas se envían a múltiples nodos simultáneamente. El sistema está disponible mientras responda un quórum: si tienes 3 réplicas y configuras quórum de escritura de 2 y quórum de lectura de 2, puedes tolerar un nodo fallido para cualquier operación.
La regla del quórum es W + R > N, donde W es el número de nodos que deben confirmar una escritura, R es el número de nodos que se consultan en una lectura, y N es el total de réplicas. Si se cumple esta desigualdad, al menos un nodo habrá participado tanto en la escritura como en la lectura, garantizando que se leerá el valor más reciente.
La flexibilidad del modelo sin líder es poderosa: puedes ajustar W y R según los requisitos de cada operación. W=N, R=1 maximiza la durabilidad de escritura a costa de disponibilidad y latencia de escritura. W=1, R=N hace lo contrario. W=R=2 con N=3 proporciona un equilibrio razonable para muchos casos.
El impacto operativo: más allá de la teoría
La teoría es importante, pero los sistemas de producción viven en el mundo real. Las decisiones sobre consistencia, particionado y replicación tienen consecuencias operativas que no siempre son evidentes hasta que el sistema está bajo carga o experimentando fallos.
El factor de replicación determina cuántas copias de cada dato existen. Un factor de 3 es el mínimo recomendado para producción: permite tolerar un fallo mientras se mantiene redundancia para un segundo fallo durante la recuperación. Pero también significa triplicar los costes de almacenamiento y ancho de banda de replicación.
La elección de zonas de disponibilidad para las réplicas afecta tanto a la latencia como a la resiliencia. Distribuir réplicas entre zonas en la misma región proporciona protección contra fallos de zona con latencia relativamente baja. Distribuir entre regiones proporciona protección contra desastres regionales pero añade latencia significativa: los viajes de luz entre continentes son del orden de 100-200ms.
El rebalanceo de particiones es una operación delicada. Cuando se añaden nodos al cluster o se detecta un desequilibrio de carga, los datos deben moverse entre nodos. Durante este proceso, el rendimiento puede degradarse significativamente. Sistemas como CockroachDB realizan rebalanceo automático en segundo plano, pero incluso así el impacto es perceptible. Planificar las expansiones de capacidad con antelación y realizarlas durante ventanas de baja carga es una buena práctica operativa.
La gestión de versiones y actualizaciones en un cluster distribuido es más compleja que en un sistema centralizado. Idealmente, las actualizaciones deberían ser rolling: actualizar nodos uno a uno mientras el sistema continúa operativo. Pero esto requiere compatibilidad entre versiones durante el período de transición. Algunos cambios de protocolo no son compatibles y requieren downtime coordinado.
El monitoreo de sistemas distribuidos requiere métricas específicas más allá de las tradicionales de CPU, memoria y disco. El lag de replicación mide cuánto retraso tienen los seguidores respecto al líder: si crece sostenidamente, indica que los seguidores no pueden seguir el ritmo de escrituras. La latencia de consenso mide cuánto tardan los nodos en ponerse de acuerdo: picos pueden indicar problemas de red o nodos sobrecargados. Las métricas de quórum muestran cuántos nodos están participando activamente: si cae por debajo del mínimo, el sistema puede dejar de aceptar escrituras.
Los runbooks para sistemas distribuidos deben contemplar escenarios específicos: ¿qué hacer si un nodo se desincroniza tanto que necesita reconstrucción completa? ¿Cómo forzar una elección de líder si el automático no funciona? ¿Cómo recuperarse de un split-brain donde dos partes del cluster creen ser la autoridad? Tener procedimientos documentados y probados antes de que ocurra la emergencia es crítico.
Casos de uso y ejemplos sectoriales
Para aterrizar toda esta teoría, veamos cómo diferentes sectores aplican estos conceptos y qué compromisos hacen.
En el sector financiero, la consistencia es innegociable. Un banco no puede permitirse que dos nodos tengan diferentes visiones del saldo de una cuenta: el dinero se crearía o destruiría mágicamente. Los sistemas bancarios core tradicionalmente han sido centralizados precisamente por esto. Las nuevas plataformas como Spanner o CockroachDB permiten distribución geográfica manteniendo consistencia fuerte mediante transacciones serializables distribuidas, pero el coste de latencia es significativo.
Un conocido neobanco europeo migró de PostgreSQL centralizado a CockroachDB distribuido entre Frankfurt, Londres y Dublín. La latencia de escritura aumentó de 5ms a 35ms por el consenso distribuido, pero ganaron la capacidad de sobrevivir a la pérdida completa de un datacenter sin intervención manual. Para operaciones de lectura, implementaron read replicas locales que reducen la latencia a 2-3ms a costa de eventual consistency, aceptable para consultas de saldo informativas pero no para transacciones.
En e-commerce, los compromisos son diferentes. El catálogo de productos puede tolerar consistencia eventual: si un producto aparece con precio ligeramente desactualizado durante unos segundos, no es catastrófico. El carrito de compra y el inventario requieren más cuidado. Amazon diseñó DynamoDB precisamente para estos casos: disponibilidad extrema para lecturas del catálogo, con opciones de consistencia fuerte para operaciones críticas.
Un reputado marketplace usa Cassandra para el histórico de navegación y recomendaciones —escrituras masivas, lecturas tolerantes a desactualización— y PostgreSQL para el inventario y transacciones —consistencia fuerte imprescindible—. La arquitectura híbrida añade complejidad operativa pero optimiza cada caso de uso.
En IoT y telemetría, el volumen de datos y la distribución geográfica de los dispositivos dominan las decisiones. Un sistema de smart metering que procesa lecturas de millones de contadores eléctricos usa InfluxDB en modo cluster para ingerir datos cerca de donde se generan, con agregación y replicación a un cluster central para analytics. La pérdida de algunas lecturas durante una partición de red es aceptable: se interpolan o se recuperan en la siguiente ventana de conectividad.
Las plataformas de gaming necesitan latencias extremadamente bajas para experiencias en tiempo real. Un conocido juego multijugador masivo usa Redis Cluster para el estado de sesión con replicación asíncrona: la pérdida de unos milisegundos de estado de juego tras un fallo es preferible a la latencia de replicación síncrona. El estado persistente crítico —inventarios de jugadores, progresión— va a MySQL con replicación síncrona y confirmación solo tras réplica.
Riesgos frecuentes y anti-patrones
La experiencia en consultoría hace aflorar patrones de error recurrentes que merece la pena documentar.
El más común es el split-brain: dos partes del cluster que, aisladas por una partición de red, creen ambas ser el líder legítimo. Esto puede resultar en escrituras conflictivas que corrompen datos. La prevención requiere mecanismos de fencing robustos: verificar que realmente se tiene quórum antes de aceptar el liderazgo, y tener mecanismos externos (como servicios de bloqueo distribuido) para desempatar.
Otro error frecuente es subestimar el impacto del lag de replicación. Un sistema que funciona perfectamente en pruebas con datos sintéticos puede colapsar en producción cuando el volumen real de escrituras excede la capacidad de replicación. El lag crece, las lecturas devuelven datos cada vez más desactualizados, y eventualmente la aplicación empieza a comportarse de formas extrañas porque las diferentes partes del sistema ven diferentes versiones de la realidad.
El hot key problem afecta especialmente a sistemas particionados por hash. Si una clave específica recibe mucho más tráfico que otras —un tweet viral, un producto en oferta, un evento masivo— toda la carga recae sobre la partición que contiene esa clave. La solución puede implicar añadir sufijos aleatorios a las claves calientes para dispersarlas, pero esto complica las lecturas que ahora deben agregar múltiples particiones.
La coordinación excesiva es un anti-patrón donde se implementan transacciones distribuidas para operaciones que no las necesitan realmente. Cada transacción distribuida implica múltiples rondas de comunicación entre nodos, coordinación con el sistema de transacciones y posibles bloqueos. Si la consistencia eventual es aceptable para una operación, forzar consistencia fuerte es desperdiciar latencia y throughput.
Por el contrario, la consistencia insuficiente es igual de problemático. Hemos visto sistemas donde se asumió que la consistencia eventual era aceptable para datos que realmente requerían consistencia fuerte, resultando en bugs sutiles y pérdida de datos que solo se manifestaban bajo condiciones específicas de timing.
El anti-patrón del backup olvidado es especialmente peligroso en sistemas distribuidos. La replicación no es backup: protege contra fallos de nodos pero no contra errores de aplicación que corrompen datos (la corrupción se replica), eliminaciones accidentales (la eliminación se replica) o ataques de ransomware (el cifrado malicioso se replica). Los backups puntuales e independientes siguen siendo imprescindibles.
Checklist operativo
Antes de poner en producción un sistema de bases de datos distribuido:
Respecto a la arquitectura, hay que verificar que se ha documentado el modelo de consistencia elegido y sus implicaciones para cada tipo de operación. También que se ha definido la estrategia de particionado basándose en el análisis de patrones de acceso reales, no supuestos. El factor de replicación debe ser de al menos 3 para datos críticos, con réplicas distribuidas entre zonas de disponibilidad. Se deben documentar los compromisos CAP y PACELC específicos del sistema elegido.
Para el despliegue, es importante confirmar que la latencia entre nodos es aceptable para el protocolo de consenso configurado: si supera los 10ms entre réplicas síncronas, hay que revisar la arquitectura. Se debe verificar que el dimensionamiento de almacenamiento contempla tanto los datos como los logs de transacciones y el espacio necesario para compactación. Hay que probar el comportamiento del sistema con una réplica caída antes del lanzamiento.
En cuanto al monitoreo, hay que configurar alertas sobre lag de replicación que activen antes de alcanzar valores críticos. Se necesitan métricas de latencia de consenso con histogramas para detectar outliers. Es necesario monitorizar la distribución de carga entre particiones para detectar hotspots, así como la salud del quórum y el estado de cada nodo.
Sobre las operaciones, es imprescindible documentar el runbook de recuperación ante fallo de nodo, incluyendo tiempos esperados. Se debe probar el proceso de rebalanceo y medir su impacto en rendimiento. Hay que verificar que los procedimientos de backup y restore funcionan y medir el tiempo de recuperación. También se debe documentar el proceso de actualización rolling y verificar compatibilidad entre versiones.
Para la recuperación ante desastres, hay que definir RTO y RPO para cada tipo de dato y verificar que la arquitectura los cumple. Se debe probar la recuperación ante pérdida completa de una zona de disponibilidad, verificar que existen backups offsite no afectados por los mismos fallos que el cluster principal, y documentar el procedimiento de failover regional si aplica.
Caso práctico: arquitectura distribuida para una plataforma de pagos
Para ilustrar cómo se aplican estos conceptos en conjunto, consideremos el diseño de una plataforma de pagos que debe procesar transacciones en tiempo real con alta disponibilidad y consistencia fuerte.
Los requisitos incluyen procesar hasta 10,000 transacciones por segundo en pico, garantizar que ninguna transacción se pierda ni se duplique, mantener disponibilidad del 99.99% (menos de 53 minutos de downtime al año), ofrecer latencia P99 inferior a 200ms, y soportar despliegue multi-región para resiliencia geográfica.
Para la capa transaccional, seleccionamos CockroachDB por su consistencia serializable, distribución geográfica nativa y protocolo de consenso Raft bien probado. Desplegamos un cluster de 9 nodos distribuidos entre tres regiones (3 nodos por región) con replicación de factor 3 configurada para mantener al menos una réplica en cada región.
El particionado de la tabla de transacciones usa hash del ID de comercio como clave de partición. Esto distribuye la carga entre comercios pero mantiene todas las transacciones de un comercio en la misma partición, facilitando las consultas de reconciliación. Para comercios con volumen excepcional, implementamos sub-particionado por rangos horarios.
La tabla de saldos de cuenta usa particionado por rango del ID de cuenta con particiones de 100,000 cuentas. Esto facilita la expansión gradual y las migraciones de cuentas entre particiones cuando sea necesario.
Para lecturas de alto volumen —consultas de estado de transacción, historiales— desplegamos read replicas asíncronas en cada región. Estas aceptan consistencia eventual con lag máximo de 5 segundos, aceptable para consultas informativas. Las operaciones de escritura siempre van al cluster principal con consistencia fuerte.
El sistema de monitoreo incluye alertas de lag de replicación que se activan a 1 segundo para read replicas y 100ms para réplicas principales, latencia de consenso que activa alerta si P99 supera 50ms, distribución de particiones que notifica si cualquier nodo tiene más del 150% de la carga media, y estado de quórum que activa alerta crítica si cualquier rango tiene menos de 2 réplicas disponibles.
El plan de recuperación ante desastres contempla la pérdida de una región completa con RTO de 30 segundos (failover automático al detectar pérdida de quórum regional) y RPO de 0 (sin pérdida de transacciones confirmadas gracias a replicación síncrona). La pérdida de dos regiones requiere intervención manual, con RTO de 4 horas y RPO de 5 segundos.
Las pruebas de caos realizadas trimestralmente incluyen terminar nodos aleatoriamente durante carga de producción, simular particiones de red entre regiones, saturar la red para probar el comportamiento bajo congestión, y ejecutar un desastre regional simulado anualmente.
Recursos y lecturas recomendadas
Para profundizar en los conceptos tratados, recomendamos los siguientes recursos.
El libro "Designing Data-Intensive Applications" de Martin Kleppmann es la referencia definitiva sobre sistemas de datos distribuidos. Cubre con rigor técnico y claridad expositiva todo lo tratado en este capítulo y mucho más. Es lectura obligatoria para cualquier arquitecto de datos.
El paper original de Raft, "In Search of an Understandable Consensus Algorithm" de Diego Ongaro y John Ousterhout, es sorprendentemente accesible para un paper académico. La visualización interactiva en thesecretlivesofdata.com/raft/ ayuda enormemente a entender el algoritmo.
La documentación de CockroachDB es excepcionalmente buena explicando no solo cómo usar el producto sino por qué está diseñado como está, con explicaciones detalladas de sus decisiones arquitectónicas sobre consistencia, particionado y replicación.
El blog de engineering de Uber contiene múltiples artículos sobre sus desafíos con sistemas distribuidos a escala, incluyendo su famosa migración de PostgreSQL a MySQL y Schemaless, y más recientemente sus experiencias con CockroachDB.
Para una perspectiva más teórica, el curso distribuido systems de Martin Kleppmann publicado en YouTube proporciona una introducción rigurosa a los conceptos fundamentales.
Conclusión: la distribución como herramienta, no como objetivo
Las bases de datos distribuidas son herramientas poderosas pero complejas. No son inherentemente mejores que las bases de datos centralizadas: son diferentes, con diferentes compromisos. La clave está en entender cuáles son tus requisitos reales y elegir la arquitectura que mejor los satisface.
Si puedes resolver tu problema con una base de datos centralizada bien dimensionada, hazlo. Es más simple de operar, más fácil de razonar y más predecible en su comportamiento. Solo cuando los requisitos de escalabilidad, disponibilidad o latencia geográfica exceden lo que una solución centralizada puede ofrecer, la distribución se convierte en una necesidad.
Cuando sí necesites distribuir, invierte tiempo en entender los compromisos que estás haciendo. El teorema CAP y el modelo PACELC no son curiosidades académicas: son guías para decisiones que afectarán a tu sistema durante años. Elige conscientemente entre consistencia y disponibilidad, entre latencia y durabilidad. Documenta estas decisiones y asegúrate de que el equipo de operaciones las entiende.
Los protocolos de consenso y las estrategias de particionado y replicación son implementaciones de estos compromisos. Raft vs Paxos, hash vs rango, síncrona vs asíncrona: cada elección tiene consecuencias operativas. No hay respuestas universalmente correctas, solo respuestas correctas para tu contexto.
Y sobre todo, prepárate para los fallos. Los sistemas distribuidos fallan de formas que los sistemas centralizados no pueden. Particiones de red, split-brain, lag de replicación, hotspots: todos ocurrirán eventualmente. La diferencia entre un sistema resiliente y uno frágil está en la preparación: runbooks documentados, monitoreo comprehensivo, pruebas de caos regulares.
La distribución no es magia. Es ingeniería aplicada a un problema difícil, con compromisos claros y consecuencias predecibles. Entender estos compromisos es el primer paso para diseñar sistemas que realmente funcionen cuando más importa.
