main content

Cómo diseñar una estrategia de bases de datos distribuidas y sharding horizontal para escalar tu aplicación web a medida

Todas las aplicaciones pasan por el mismo rito de paso. Tu base de datos empieza siendo esa pieza invisible que simplemente funciona, hasta que un martes a las tres de la madrugada recibes una alerta porque la latencia de escritura se ha disparado y el CPU del primario lleva cuarenta minutos al 98%. Las consultas se acumulan, los bloqueos se propagan en cascada y descubres que ningún índice nuevo ni ninguna capa de caché va a sacarte de esta. Ahí te quedas con dos caminos: meter más hierro al servidor único o repartir la carga entre varios nodos. Lo primero tiene fecha de caducidad y precio prohibitivo. Lo segundo exige diseño real, pero es lo único que escala de verdad.

Lo que viene a continuación es lo que he aprendido diseñando (y arreglando) arquitecturas distribuidas con sharding horizontal: desde los fundamentos hasta la migración desde un monolito, pasando por las decisiones que parecen menores y luego te persiguen durante años.


Por qué el escalado vertical tiene un techo

Subir RAM, CPU o disco en una sola máquina es la reacción natural al primer susto de rendimiento. Y funciona. Un tiempo. Pero el coste crece de forma exponencial: pasar de 64 GB a 256 GB de RAM no cuadruplica la capacidad útil, aunque sí puede triplicar la factura de la instancia. Llega un punto en el que la nube directamente no ofrece una instancia más grande. ¿Y entonces qué?

Además del coste está el riesgo concentrado. Una sola máquina absorbe toda la carga de escritura, toda la de lectura y todos los datos críticos. Un fallo de disco, una migración de versión que se tuerce o un pico de tráfico no previsto tumba la totalidad del sistema de golpe. He visto equipos con servidores de 1 TB de RAM que dormían tranquilos hasta que un upgrade de motor los dejó sin servicio cuatro horas. Un solo nodo, un solo punto de fallo.

El sharding horizontal parte de otra premisa: en lugar de un servidor más potente, distribuimos los datos entre múltiples nodos (shards). Cada nodo gestiona solo una fracción del total. La capacidad crece añadiendo nodos, no comprando hardware más caro.


Cuándo necesitas sharding (y cuándo no te compliques)

No toda aplicación necesita sharding. Antes de asumir la complejidad operativa que trae, conviene agotar vías más sencillas:

  • Read replicas: si tu problema es de lectura, un conjunto de réplicas con balanceo adecuado multiplica el throughput de consultas sin tocar el esquema. Muchas aplicaciones viven años felices con un primario y tres réplicas.
  • Particionamiento por tablas: PostgreSQL permite particionar tablas individuales por rango o lista sin distribuir datos entre servidores distintos. Transparente para la aplicación y reduce la contención de índices. Lo he usado en tablas de logs de 2.000 millones de filas y la mejora fue brutal.
  • Caching: Redis o Memcached absorben patrones de lectura repetitiva. Hay cargas de trabajo donde el 80% de las consultas se resuelven desde caché sin que la base de datos se entere.

¿Cuándo sí necesitas sharding? Cuando el volumen de escrituras supera la capacidad de un único nodo primario. Cuando el tamaño de los datos ya no cabe eficientemente en un solo servidor. Cuando la latencia de escritura es el factor limitante y la replicación no puede hacer nada por ti.


Estrategias de sharding: range-based, hash-based y geográfico

Sharding por rango

Los datos se distribuyen según el valor de la clave de partición dentro de rangos definidos. Usuarios con ID entre 1 y 1.000.000 van al shard A, entre 1.000.001 y 2.000.000 al shard B, y así sucesivamente.

La ventaja: las consultas por rango son eficientes. Una consulta que pide todos los pedidos entre dos fechas puede resolverse accediendo a uno o dos shards en lugar de a todos. El peligro: el desbalanceo. Si los usuarios más activos se concentran en un rango reciente, ese shard acumula una carga desproporcionada. Lo llaman hotspot, y lo he visto tumbar shards en pleno Black Friday porque nadie revisó la distribución de actividad por rango antes de lanzar.

Sharding por hash

Se aplica una función hash sobre la clave de partición para determinar el shard destino. La distribución tiende a ser uniforme, eliminando los hotspots. ¿La contrapartida? Las consultas por rango se vuelven ineficientes porque datos contiguos acaban en shards distintos. Cada decisión de arquitectura tiene su factura.

Sharding geográfico

Los datos se asignan a shards según la región del usuario o el origen de la solicitud. Tiene sentido cuando los requisitos regulatorios obligan a mantener ciertos datos dentro de una jurisdicción —GDPR en Europa, por ejemplo— o cuando la latencia de red es crítica y conviene colocar los datos cerca del usuario.

Muchas arquitecturas de producción combinan estrategias: sharding geográfico en primer nivel y hash-based dentro de cada región. No tienes que casarte con un solo modelo.


Selección de la shard key: la decisión que no puedes deshacer

La elección de la clave de partición es irreversible en la práctica. Una shard key incorrecta te obliga a reescribir el esquema, migrar datos y modificar la lógica de la aplicación, todo bajo presión operativa y con producción en marcha. He visto migraciones de shard key que duraron seis meses y consumieron a equipos enteros. Tres criterios guían la decisión:

Cardinalidad alta: la clave debe tener suficientes valores distintos para distribuir los datos entre todos los shards. Usar el país como shard key en una aplicación con usuarios de 200 países pero concentrada en tres o cuatro es garantía de hotspot.

Distribución uniforme: la clave debe correlacionar con la carga real. Un ID secuencial puede parecer uniforme sobre el papel, pero si los usuarios más nuevos son los más activos, los shards con IDs altos acumulan más escrituras. La uniformidad del dato no implica uniformidad de carga.

Alineación con los patrones de acceso: las consultas que necesitan datos de un único usuario, pedido o entidad deberían resolverse tocando un solo shard. Diseñar la shard key para que las consultas frecuentes sean locales a un shard reduce drásticamente la latencia y elimina la necesidad de joins distribuidos. ¿Tu aplicación consulta siempre por tenant_id? Entonces esa es probablemente tu shard key.


Consistent hashing para gestionar la reconfiguración

Uno de los problemas del hashing simple: añadir o eliminar un shard invalida la asignación de casi todos los datos, porque el módulo cambia. Imagina que tienes cuatro shards y añades un quinto. Con hash simple, el 80% de las claves cambian de destino. Eso es una migración masiva cada vez que necesitas escalar.

El consistent hashing resuelve esto organizando los shards en un anillo virtual. Cuando se añade un nodo, solo se reasigna la fracción de datos adyacente en el anillo, no la totalidad. Esta propiedad es fundamental para sistemas que necesitan elasticidad: añadir capacidad durante picos de tráfico y reducirla después sin interrupciones ni migraciones apocalípticas. Cassandra y DynamoDB implementan consistent hashing de forma nativa.


Cross-shard queries y el coste real de la agregación distribuida

Una vez que los datos están repartidos, cualquier consulta que no pueda resolverse con un único shard introduce latencia adicional y complejidad. Las queries que agregan datos de múltiples shards —COUNT, SUM, GROUP BY— requieren ejecutar la consulta en paralelo en todos los shards afectados y luego consolidar los resultados en una capa de coordinación. Suena limpio en un diagrama. En producción, cada millisegundo de coordinación se nota.

El impacto varía según la herramienta. Vitess (MySQL distribuido) tiene un planner de queries que paraleliza automáticamente y consolida resultados. CockroachDB implementa SQL distribuido con semántica ACID completa. Citus (extensión de PostgreSQL) permite consultas distribuidas con sintaxis estándar de PostgreSQL. El coste es real pero gestionable si el diseño del esquema minimiza las queries que necesitan datos de múltiples shards. La clave está en el "si": esa minimización empieza en el momento de elegir la shard key, no después.


Transacciones distribuidas: 2PC y el patrón Saga

Las transacciones que afectan a datos en más de un shard son el punto donde muchas arquitecturas empiezan a crujir. Dos enfoques principales:

Two-Phase Commit (2PC)

El coordinador pregunta a todos los shards implicados si pueden hacer commit (fase prepare). Si todos responden afirmativamente, envía la orden de commit. Si alguno falla, rollback a todos. Garantiza atomicidad, pero introduce latencia —dos rondas de red— y crea un punto de fallo en el coordinador. Válido para operaciones de baja frecuencia donde la consistencia fuerte no es negociable. ¿Pagos? Sí. ¿Actualizar un contador de likes? Probablemente no.

Patrón Saga

La operación se descompone en una secuencia de transacciones locales en cada servicio o shard, con operaciones compensatorias definidas para revertir cada paso en caso de fallo. Nada de bloqueo distribuido. La solución estándar en arquitecturas de microservicios porque escala mejor, a cambio de eventual consistency y mayor complejidad en la lógica de negocio. Diseñar las compensaciones bien es la parte difícil; el framework es lo de menos.


Replicación y sharding: complementarios, no intercambiables

La replicación copia los mismos datos en múltiples nodos para aumentar disponibilidad y capacidad de lectura. El sharding divide los datos entre nodos para aumentar la capacidad de escritura y el volumen total almacenable. Parecen similares. No lo son.

La replicación sola no resuelve el problema de escrituras masivas porque todas las escrituras siguen yendo al primario. El sharding sin replicación crea puntos únicos de fallo por cada shard —y ahora tienes múltiples puntos únicos de fallo en lugar de uno, que es peor—. Una arquitectura robusta combina ambas: cada shard con su propio conjunto de réplicas. Más nodos que gestionar, sí. Pero duermes mejor.


Herramientas para implementar sharding

Vitess: proxy distribuido para MySQL desarrollado en YouTube. Gestiona el routing de queries, el resharding en caliente y el escalado de conexiones. La opción más madura para aplicaciones que ya usan MySQL y no quieren cambiar de motor. Si tu equipo conoce MySQL, la curva de aprendizaje de Vitess es asumible.

Citus: extensión de PostgreSQL que convierte una instancia estándar en un clúster distribuido. Permite sharding por columna de distribución con sintaxis SQL estándar. Especialmente útil para workloads analíticos y multitenant. Para quien ya vive en el ecosistema PostgreSQL, Citus mantiene las cosas familiares.

CockroachDB: base de datos NewSQL distribuida con ACID completo y consistent hashing nativo. El sharding es transparente para la aplicación. Tiene el overhead típico de los sistemas de consenso distribuido (Raft), pero elimina casi toda la complejidad operativa del sharding manual. Lo pagas en latencia de escritura por la coordinación de consenso.

MongoDB Sharding: el motor nativo de MongoDB incluye soporte de sharding con mongos como router y config servers para los metadatos. Opción válida si el modelo de datos ya usa documentos, aunque el diseño de la shard key requiere la misma atención que en cualquier otro sistema. He visto clústeres de MongoDB con shard keys mal elegidas que acabaron con un shard al 90% de capacidad y tres prácticamente vacíos.


Migración desde una base de datos monolítica

Migrar un sistema en producción a una arquitectura distribuida no se hace en un fin de semana ni con un script mágico. Requiere una estrategia incremental, tabla a tabla:

  1. Auditar los patrones de acceso actuales: identificar qué tablas concentran las escrituras, qué queries son más frecuentes y dónde están los bloqueos. Esto determina qué shardear primero. Si no mides antes de cortar, estás adivinando.
  2. Introducir una capa de abstracción: un proxy o una capa de repositorio en la aplicación que pueda enrutar queries al destino correcto. Permite cambiar el backend sin modificar toda la lógica de negocio. Este paso parece burocrático hasta que te ahorra tres meses de refactoring.
  3. Migrar tabla a tabla con dual-write: durante la transición, escribir en el sistema antiguo y en el nuevo simultáneamente, verificando consistencia antes de cortar el tráfico de lectura. Sí, duplicas escrituras. Sí, merece la pena.
  4. Validar con tráfico real: usar shadow queries o lectura paralela para comparar resultados entre el sistema monolítico y el distribuido antes del cutover definitivo. Las diferencias que encuentras aquí te evitan incidentes después.
  5. Cortar progresivamente: migrar primero el tráfico de lectura, luego el de escritura, empezando por los módulos menos críticos. Nada de big-bang. Nadie quiere ser el héroe que cortó todo de golpe y descubrió un edge case a las dos de la mañana.

Monitorización y los desafíos que no aparecen en el whiteboard

Un clúster distribuido requiere observabilidad de grano fino. Los indicadores que te salvan la vida: distribución de escrituras por shard (para detectar hotspots antes de que exploten), latencia de cross-shard queries, tasa de rebalanceo y lag de replicación por shard. Prometheus con dashboards de Grafana adaptados a la herramienta de sharding elegida es el estándar operativo. Si no lo puedes medir, no lo puedes operar.

Pero el principal riesgo operativo no es técnico. Un clúster de Vitess o CockroachDB que solo una persona del equipo entiende es una bomba de tiempo. La documentación de las decisiones de diseño —qué shard key se eligió y por qué, qué estrategia de resharding se tiene prevista, qué pasó en la última migración y qué salió mal— importa tanto como el código. Cuando esa persona se va de vacaciones o cambia de empresa, el equipo necesita poder operar el sistema sin arqueología inversa.


Si estás evaluando si tu aplicación necesita sharding, o si ya has llegado al límite del escalado vertical y necesitas un plan concreto de migración, habla con nuestro equipo de arquitectura. Diseñamos la estrategia de datos que necesita tu proyecto, desde la elección del motor hasta la ejecución de la migración en producción.