⏱️ Lectura: 13 min

Una tabla de usuarios que crece más rápido que la capacidad de un solo servidor deja de ser un problema de índices: se convierte en un problema de arquitectura. Cuando ni el disco más grande ni la memoria RAM más generosa alcanzan, la única salida es partir los datos entre varias máquinas. A eso se le llama sharding.

📑 En este artículo
  1. TL;DR
  2. Qué es el sharding y por qué importa
  3. Cómo funciona: estrategias de particionado
    1. Sharding por rango
    2. Sharding por hash
    3. Sharding por directorio
  4. Ejemplos prácticos
  5. Cómo implementarlo paso a paso
  6. Casos de uso reales
  7. Errores comunes y buenas prácticas
  8. Comparativa con alternativas
  9. Profundizando (avanzado)
  10. Preguntas frecuentes
    1. ¿Sharding y particionamiento son lo mismo?
    2. ¿Cuándo conviene usar sharding en lugar de escalar verticalmente?
    3. ¿Cómo se hacen consultas que cruzan varios shards?
    4. ¿Qué pasa cuando hay que agregar más shards (resharding)?
    5. ¿Sharding por hash o por rango: cuál elegir?
    6. ¿El sharding reemplaza a los índices o a la replicación?
  11. Referencias

Esta guía explica cómo funciona el sharding de bases de datos, qué estrategias existen para repartir los datos (por hash, por rango, por directorio), cómo implementarlo hoy mismo en PostgreSQL, y los errores que convierten un sharding bien intencionado en una migración de meses.

TL;DR

  • Vas a entender la diferencia entre sharding, particionamiento y replicación y cuándo usar cada uno.
  • Vas a poder calcular a qué shard pertenece un registro con una función de hash en pocas líneas de código.
  • Vas a saber crear particiones por hash en PostgreSQL con CREATE TABLE … PARTITION BY HASH.
  • Vas a identificar los tres gotchas que rompen un sharding mal diseñado: hot keys, joins cruzados y resharding.
  • Vas a comparar sharding por rango contra sharding por hash con una tabla de decisión clara.
  • Vas a conocer cómo Vitess resuelve el sharding de MySQL sin reescribir la aplicación completa.
  • Vas a poder verificar con EXPLAIN si PostgreSQL está usando partition pruning en tus consultas.

Qué es el sharding y por qué importa

El sharding de bases de datos consiste en dividir una tabla (o un conjunto de tablas relacionadas) en fragmentos horizontales, llamados shards, y distribuir esos fragmentos entre servidores independientes. Cada shard guarda un subconjunto de las filas, no de las columnas, y puede responder la mayoría de las consultas sin depender de los demás shards.

Es fácil confundir sharding con particionamiento. El particionamiento divide una tabla en fragmentos que siguen viviendo en el mismo servidor y bajo el mismo motor de base de datos; el sharding va un paso más allá y distribuye esos fragmentos entre máquinas físicas distintas, cada una con su propio disco, su propia memoria y su propio proceso de base de datos. PostgreSQL soporta particionamiento declarativo nativo desde la versión 10, y particionamiento por hash específicamente desde la versión 11.

Importa porque escalar verticalmente (comprar un servidor más grande) tiene un techo: existe un límite físico de CPU, RAM y disco que un solo servidor puede ofrecer, y ese límite se vuelve carísimo mucho antes de alcanzarse. El sharding cambia el problema: en lugar de un servidor más grande, se usan más servidores medianos, cada uno responsable de una porción de los datos.

📌 Nota: el sharding no es gratis. Agrega complejidad operativa (más servidores que monitorear, más conexiones que gestionar) y complejidad de consultas (los joins y las transacciones que cruzan shards dejan de ser triviales). Es una herramienta para un problema específico: cuando un solo servidor ya no alcanza, no un default de arquitectura.

Cómo funciona: estrategias de particionado

Sharding por rango

Cada shard recibe un rango contiguo de valores de la clave de particionado (la shard key). Por ejemplo, los pedidos con id 1 a 1.000.000 van al shard 0, los pedidos con id 1.000.001 a 2.000.000 van al shard 1, y así sucesivamente. La ventaja es que las consultas por rango (“todos los pedidos de esta semana”) tocan pocos shards. La desventaja es el hot shard: si la clave crece de forma secuencial (un id autoincremental, una fecha), todas las escrituras nuevas terminan en el último shard mientras los demás quedan casi inactivos.

Sharding por hash

Se aplica una función de hash sobre la shard key y el resultado, módulo el número de shards, decide a qué shard va cada fila. La distribución es uniforme casi siempre, así que no hay hot shards por claves secuenciales. La desventaja aparece cuando el número de shards cambia: con un módulo simple, agregar un shard más reubica la mayoría de las filas existentes. Por eso los sistemas grandes suelen usar hashing consistente o shards virtuales (más shards lógicos que físicos) para minimizar cuántos datos se mueven al escalar.

Sharding por directorio

Un servicio de lookup (una tabla pequeña, casi siempre replicada y cacheada) guarda explícitamente a qué shard pertenece cada clave. Es el más flexible: permite mover una clave puntual sin tocar una fórmula matemática, útil para aislar a un cliente muy pesado en su propio shard. La desventaja es que ese servicio de lookup se convierte en una pieza crítica adicional que también hay que mantener disponible.

flowchart TD
    A["Aplicacion"] --> B["Funcion de hash sobre la shard key"]
    B --> C{"hash mod N"}
    C -->|"resultado 0"| D[("Shard 0")]
    C -->|"resultado 1"| E[("Shard 1")]
    C -->|"resultado 2"| F[("Shard 2")]

Ejemplos prácticos

El caso más simple: calcular a qué shard pertenece un registro a partir de su clave. Esto es lo mínimo que necesita cualquier capa de routing de sharding por hash.

const crypto = require("crypto");

function shardId(clienteId, totalShards) {
  const hash = crypto.createHash("md5").update(String(clienteId)).digest();
  return hash.readUInt32BE(0) % totalShards;
}

console.log(shardId(42, 4));   // devuelve un numero entre 0 y 3
console.log(shardId(1001, 4)); // clientes distintos, shards distintos

Esta función toma un clienteId, calcula su hash MD5, y usa los primeros 4 bytes como un entero para aplicar el módulo. El resultado es determinístico: el mismo clienteId siempre cae en el mismo shard mientras totalShards no cambie.

El siguiente paso realista es usar ese cálculo para elegir una conexión de base de datos concreta, no solo un número:

const pools = [pool0, pool1, pool2, pool3]; // un pool pg.Pool por shard

async function getPedidosDeCliente(clienteId) {
  const shard = shardId(clienteId, pools.length);
  const db = pools[shard];
  const { rows } = await db.query(
    "SELECT * FROM pedidos WHERE cliente_id = $1",
    [clienteId]
  );
  return rows;
}

Esta capa de routing es, en esencia, lo que hacen por dentro herramientas como Vitess: reciben una consulta, identifican la shard key, y la enrutan al servidor correcto sin que la aplicación tenga que saber cuántos shards existen.

Sharding de bases de datos representado con servidores conectados
Cada shard corre en su propio proceso de base de datos, con su propio disco. Foto de Markus Winkler en Unsplash

Cómo implementarlo paso a paso

No hace falta montar una infraestructura distribuida completa para empezar a experimentar con sharding: PostgreSQL trae particionamiento declarativo por hash listo para usar.

Primero, se crea la tabla padre indicando la estrategia de particionado y la columna que actúa como shard key:

CREATE TABLE pedidos (
    id bigint NOT NULL,
    cliente_id bigint NOT NULL,
    creado_en timestamptz NOT NULL,
    monto numeric(10,2) NOT NULL
) PARTITION BY HASH (cliente_id);

Después se crean las particiones, cada una responsable de un resto del módulo:

CREATE TABLE pedidos_p0 PARTITION OF pedidos
    FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE pedidos_p1 PARTITION OF pedidos
    FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE pedidos_p2 PARTITION OF pedidos
    FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE pedidos_p3 PARTITION OF pedidos
    FOR VALUES WITH (MODULUS 4, REMAINDER 3);

Para confirmar que la tabla realmente quedó particionada, \d+ pedidos en psql lista las cuatro particiones hijas. Y para confirmar que una consulta puntual toca solo la partición que le corresponde (partition pruning), alcanza con:

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM pedidos WHERE cliente_id = 42;

El plan de ejecución debe mostrar un único Seq Scan o Index Scan sobre pedidos_p0 (o la partición que corresponda), no un recorrido de las cuatro tablas. Si aparecen las cuatro particiones en el plan, la consulta no está usando la shard key en el WHERE y PostgreSQL no puede podar el resto.

💡 Tip: este particionamiento vive en un solo servidor Postgres. Para sharding real entre máquinas distintas hace falta además una capa de routing (aplicación, Vitess, Citus) que sepa a qué servidor conectarse, no solo a qué tabla.

Casos de uso reales

El patrón más común en SaaS multi-tenant es shardear por tenant_id: cada cliente empresarial queda en un shard, lo que además simplifica el aislamiento de datos entre clientes. Las redes sociales suelen shardear por user_id, porque casi todas las consultas frecuentes (perfil, publicaciones propias, configuración) filtran por ese mismo campo.

Los sistemas de series temporales (métricas, logs, eventos de sensores) suelen shardear por fecha, aceptando el riesgo de hot shard en el rango más reciente a cambio de poder borrar shards completos cuando vencen los datos antiguos, en vez de ejecutar DELETE fila por fila.

flowchart LR
    subgraph Rango de fechas
    S0["Shard 0: enero-marzo"]
    S1["Shard 1: abril-junio"]
    S2["Shard 2: julio-septiembre (activo)"]
    end
    Q["Consulta: eventos de julio"] --> S2

Errores comunes y buenas prácticas

El error más frecuente es elegir una shard key que no es la que realmente se usa para filtrar en producción. Si el 90% de las consultas filtran por cliente_id pero se shardeó por fecha, cada consulta típica termina siendo un scatter-gather que golpea todos los shards, lo que anula buena parte del beneficio.

El segundo error es subestimar el resharding. Agregar shards nuevos casi siempre implica mover datos entre servidores mientras el sistema sigue recibiendo tráfico. Sin una estrategia de shards virtuales o hashing consistente, ese movimiento de datos puede tomar semanas y requiere una ventana de doble escritura (escribir en el shard viejo y en el nuevo a la vez) para no perder actualizaciones durante la migración.

El tercero son las transacciones cruzadas: actualizar dos filas que viven en shards distintos dentro de una sola transacción ACID no es posible de forma nativa. Hace falta un protocolo de dos fases (2PC) o rediseñar la operación como una saga con compensaciones, y ambos agregan latencia y superficie de fallo.

⚠️ Ojo: un join entre dos tablas que están en shards distintos no lo resuelve el motor de base de datos por vos. Hay que traer los datos a la capa de aplicación y unirlos ahí, o desnormalizar de antemano para evitar el join.

Comparativa con alternativas

EstrategiaQué resuelveVentajaLimitación
ShardingVolumen de datos y escritura que excede un servidorEscala horizontal casi sin techoJoins y transacciones cruzadas complejos
Réplicas de lecturaVolumen de lecturasSimple de configurar, sin cambios de esquemaNo ayuda si el cuello de botella es la escritura
Particionamiento (single-server)Tablas grandes en un solo servidorMejora mantenimiento e índices sin infraestructura nuevaNo resuelve límites de CPU o RAM del servidor
Escalado verticalFalta puntual de recursosCero cambios de códigoTecho físico y costo creciente de forma no lineal

Profundizando (avanzado)

Cuando una consulta necesita datos de varios shards a la vez (por ejemplo, un reporte agregado), el patrón habitual es scatter-gather: la capa de routing envía la misma consulta a todos los shards en paralelo y combina los resultados en memoria antes de devolverlos al cliente.

sequenceDiagram
    participant App as Aplicacion
    participant R as Router
    participant S0 as Shard 0
    participant S1 as Shard 1
    App->>R: consulta agregada
    R->>S0: subconsulta
    R->>S1: subconsulta
    S0-->>R: resultado parcial
    S1-->>R: resultado parcial
    R-->>App: resultado combinado

Vitess, creado originalmente en YouTube para escalar MySQL, implementa exactamente esta capa de routing (llamada VTGate) para que la aplicación siga hablando el protocolo MySQL estándar sin enterarse de cuántos shards existen por debajo. El proyecto también resuelve el resharding en caliente moviendo datos shard por shard mientras mantiene ambas copias sincronizadas hasta el corte final.

Para minimizar cuánto se mueve al agregar o quitar shards, los sistemas maduros no shardean directamente sobre N servidores físicos: crean muchos más shards lógicos (por ejemplo 4.096) de los que hay servidores físicos, y asignan varios shards lógicos a cada servidor. Agregar un servidor nuevo solo implica reasignar algunos shards lógicos, no recalcular el hash de cada fila individual.

💭 Clave: el mismo problema de “agregar un nodo no debería remapear todo” que resuelve el hashing consistente en sistemas de caché es, en el fondo, el mismo problema que resuelve el resharding con shards virtuales en una base de datos.

📖 Resumen en Telegram: Ver resumen

Tu próximo paso: montá una tabla con las cuatro particiones por hash de este artículo en una instancia local de PostgreSQL y confirmá con EXPLAIN (ANALYZE, BUFFERS) que una consulta por cliente_id solo toca una partición.

Preguntas frecuentes

¿Sharding y particionamiento son lo mismo?

No. El particionamiento divide una tabla en fragmentos dentro del mismo servidor y motor de base de datos. El sharding distribuye esos fragmentos entre servidores físicos distintos. Se puede tener particionamiento sin sharding, pero el sharding casi siempre usa particionamiento como base dentro de cada nodo.

¿Cuándo conviene usar sharding en lugar de escalar verticalmente?

Cuando el volumen de datos o de escritura ya supera lo que un solo servidor puede ofrecer de forma razonable, o cuando el costo de seguir creciendo verticalmente es mayor que el costo operativo de administrar varios servidores más chicos.

¿Cómo se hacen consultas que cruzan varios shards?

Con un patrón scatter-gather: la capa de routing envía la consulta a todos los shards relevantes en paralelo y combina los resultados. Es más lento y más complejo que una consulta a un solo shard, así que conviene diseñar la shard key para minimizar cuántas consultas lo necesitan.

¿Qué pasa cuando hay que agregar más shards (resharding)?

Con un módulo simple sobre el número de shards, agregar uno nuevo obliga a mover la mayoría de los datos existentes. Por eso los sistemas en producción usan shards virtuales o hashing consistente, que solo reubican una fracción de los datos al escalar.

¿Sharding por hash o por rango: cuál elegir?

Por hash si las escrituras son secuenciales (ids autoincrementales, timestamps) y se quiere evitar un hot shard. Por rango si las consultas típicas piden rangos contiguos (todos los eventos de una semana) y se puede tolerar o gestionar el hot shard del rango más reciente.

¿El sharding reemplaza a los índices o a la replicación?

No. Son complementarios: cada shard sigue necesitando sus propios índices para responder rápido, y suele tener sus propias réplicas de lectura para tolerancia a fallos. El sharding resuelve el volumen total; los índices y la replicación resuelven la velocidad y la disponibilidad dentro de cada shard.

Referencias

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Imagen destacada: Foto de Kirill Sh en Unsplash


Andrés Morales

Desarrollador e investigador en inteligencia artificial. Escribe sobre modelos de lenguaje, frameworks, herramientas para devs y lanzamientos open source. Cubre papers de ML, ecosistema de startups tech y tendencias de programación.

0 Comentarios

Deja un comentario

Marcador de posición del avatar

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.