Skip to content
SP StackPractices
advanced Por StackPractices

Sharding de Base de Datos — Particionamiento Horizontal en la Práctica

Guía práctica sobre sharding de base de datos: elegir claves de shard, enrutar consultas, rebalancear datos y evitar errores comunes al escalar más allá de un solo nodo de base de datos.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Descripción General

El sharding de base de datos divide una única base de datos en múltiples bases de datos más pequeñas (shards) para distribuir carga y almacenamiento. Cuando el escalado vertical (máquinas más grandes) se vuelve demasiado costoso o alcanza límites físicos, el particionamiento horizontal permite que tu capa de base de datos crezca agregando nodos en lugar de actualizar los existentes.

Esta guía cubre cuándo hacer shard, cómo elegir claves de shard, enrutamiento de consultas, rebalanceo y consideraciones operativas.

Cuándo Usar

  • Tu base de datos excede 1TB de datos y los tiempos de respaldo/restauración son inaceptables
  • El throughput de escritura excede lo que un solo nodo puede manejar (>5k escrituras/seg)
  • Te has quedado sin CPU, memoria o I/O en tu instancia más grande disponible
  • Las réplicas de lectura no pueden mantenerse al día con el lag de replicación
  • Las operaciones de mantenimiento (reconstrucción de índices, cambios de esquema) toman horas
  • Necesitas distribución geográfica de datos por cumplimiento o latencia

Cuándo NO Usar

  • Tu base de datos tiene menos de 500GB — el escalado vertical y réplicas de lectura son más simples
  • Tu carga de trabajo es principalmente lectura — las réplicas y caché resuelven esto sin sharding
  • Tienes joins complejos entre shards — el sharding los hace prohibitivamente costosos
  • Tu equipo carece de experiencia operativa con bases de datos distribuidas
  • No has agotado las mejoras de optimización de consultas e índices

Conceptos Clave

ConceptoDescripción
ShardUna partición horizontal de datos almacenada en un nodo de base de datos separado
Clave de ShardLa(s) columna(s) usada(s) para determinar qué shard almacena una fila
EnrutamientoLa lógica que dirige una consulta al(los) shard(s) correcto(s)
Hot SpotUn shard que recibe una proporción desproporcionadamente mayor de carga que otros
RebalanceoMover datos entre shards para igualar carga o almacenamiento
Tabla GlobalUna tabla pequeña replicada a todos los shards para joins locales

Arquitecturas de Sharding

┌──────────────┐
│  Aplicación  │
└──────┬───────┘

  ┌────┴────┐
  │ Router  │  (Mapeo de clave de shard → shard)
  │ (Vitess)│
  └────┬────┘

   ┌───┼───┐
   │   │   │
┌──▼─┐│┌─▼─┐│┌─▼──┐
│Shard││Shard││Shard│
│  0  ││  1  ││  2  │
└─────┘└─────┘└─────┘

Implementación de Sharding Paso a Paso

1. Elige tu Clave de Shard

La clave de shard es la decisión más importante. Una mala elección crea hot spots y anula el propósito.

Características de una buena clave de shard:

  • Alta cardinalidad (muchos valores únicos)
  • Distribución uniforme (ningún valor domina)
  • Frecuentemente usada en cláusulas WHERE
  • Inmutable o raramente cambiada
Caso de UsoClave de ShardPor Qué
SaaS multi-tenanttenant_idAislamiento natural por cliente
Redes socialesuser_idDatos de usuario accedidos juntos
E-commercecustomer_id o order_idPedidos y datos de cliente co-ubicados
Series temporalestimestamp + device_idConsultas por rango de tiempo golpean pocos shards
Gamingplayer_idSesiones de jugador e inventario juntos
-- Ejemplo: Sharding basado en hash sobre user_id
-- Shard = hash(user_id) % número_de_shards

CREATE TABLE orders (
    order_id BIGINT,
    user_id BIGINT NOT NULL,
    amount DECIMAL(10,2),
    created_at TIMESTAMP,
    -- user_id es la clave de shard
    PRIMARY KEY (order_id, user_id)
);
# Ejemplo: Enrutamiento de shard a nivel de aplicación
def get_shard_for_user(user_id):
    """Enrutamiento de hash consistente."""
    return hash(user_id) % NUM_SHARDS

def get_shard_connection(user_id):
    shard = get_shard_for_user(user_id)
    return shard_connections[shard]

# Ejecución de consulta
def get_user_orders(user_id):
    conn = get_shard_connection(user_id)
    return conn.query("SELECT * FROM orders WHERE user_id = %s", user_id)

Anti-patrones de clave de shard:

  • IDs autoincrementales: Las inserciones secuenciales golpean el mismo shard (problema de escritura monotónica)
  • Claves de baja cardinalidad: Género, estado, booleano — crean hot spots masivos
  • Claves solo de tiempo: Los datos recientes golpean un solo shard (las series temporales necesitan claves compuestas)
  • Claves frecuentemente actualizadas: Cambiar la clave de shard requiere mover datos entre shards

2. Implementa Enrutamiento de Consultas

Cada consulta debe saber qué shard(s) golpear:

# Ejemplo: Middleware de enrutamiento para consultas sharded
class ShardRouter:
    def __init__(self, shards):
        self.shards = shards
    
    def route(self, query, params):
        """Enruta consulta al shard apropiado."""
        shard_key = self.extract_shard_key(query, params)
        
        if shard_key:
            # Consulta de un solo shard
            shard = hash(shard_key) % len(self.shards)
            return [self.shards[shard]]
        else:
            # Scatter-gather: consulta todos los shards
            return self.shards
    
    def extract_shard_key(self, query, params):
        # Parsear consulta para encontrar clave de shard en cláusula WHERE
        if 'user_id' in params:
            return params['user_id']
        return None

# Shard único (rápido)
orders = router.route("SELECT * FROM orders WHERE user_id = ?", {"user_id": 123})

# Multi-shard (lento, evitar en producción)
all_orders = router.route("SELECT * FROM orders WHERE amount > ?", {"amount": 100})

Estrategias de enrutamiento:

EstrategiaCómo FuncionaMejor Para
Basado en hashshard = hash(clave) % NDistribución uniforme, sin metadatos
Basado en rangoShard 0: 1-1M, Shard 1: 1M-2MSeries temporales, acceso secuencial
Basado en directorioTabla de búsqueda mapea clave → shardFlexible, permite rebalanceo
Hash consistenteRedistribución mínima al agregar/removerTamaño dinámico de cluster

3. Maneja Operaciones Cross-Shard

Las consultas cross-shard son el mayor punto de dolor del sharding:

-- EVITAR: JOIN cross-shard (costoso)
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.amount > 1000;
-- Si users y orders tienen shards diferentes, esto requiere
-- traer datos de múltiples shards y unir en la aplicación

-- PREFERIR: Desnormalizar o join a nivel de aplicación
-- Código de aplicación:
high_value_orders = shard.query("SELECT user_id, amount FROM orders WHERE amount > 1000")
user_ids = [o.user_id for o in high_value_orders]
users = user_shard.query("SELECT id, name FROM users WHERE id IN %s", user_ids)
# Unir en memoria de aplicación

Estrategias cross-shard:

ProblemaSoluciónTrade-off
JOINs cross-shardDesnormalizar, join de aplicación, o tablas globalesMás almacenamiento, complejidad
Agregaciones (SUM, COUNT)Pre-agregar o usar un data warehouseDatos obsoletos, sistema extra
Restricciones únicasVerificación a nivel de aplicación o UUIDConsistencia eventual
TransaccionesPatrón Saga o evitar transacciones multi-shardComplejidad, sin ACID
IDs autoincrementalesIDs Snowflake, UUID, o tablas de secuenciaOverhead de coordinación

4. Planifica para Rebalanceo

Los shards inevitablemente se desequilibran. Planifica el rebalanceo desde el día uno:

# Ejemplo: Script de rebalanceo (simplificado)
def rebalance_shards():
    """Mover datos de shards sobrecargados a subcargados."""
    shard_sizes = [get_shard_size(i) for i in range(NUM_SHARDS)]
    avg_size = sum(shard_sizes) / NUM_SHARDS
    
    for shard_id in range(NUM_SHARDS):
        if shard_sizes[shard_id] > avg_size * 1.2:
            # Este shard está sobrecargado
            excess = shard_sizes[shard_id] - avg_size
            target_shard = find_underloaded_shard()
            
            # Mover un rango de datos
            move_data_range(shard_id, target_shard, excess)
    
def move_data_range(source, target, bytes_to_move):
    """Mover datos en lotes para minimizar downtime."""
    batch_size = 1000
    cursor = get_cursor(source)
    
    while bytes_moved < bytes_to_move:
        rows = cursor.fetchmany(batch_size)
        insert_into_shard(target, rows)
        delete_from_shard(source, rows)
        bytes_moved += estimate_size(rows)

Enfoques de rebalanceo:

EnfoqueDowntimeComplejidadCaso de Uso
Rebalanceo onlineNingunoAltaSistemas de producción (Vitess, Citus)
Migración de doble escrituraNingunoMediaCambio gradual con validación
Snapshot + replayBreve solo-lecturaBajaBases de datos pequeñas, ventana de mantenimiento
Hash consistenteNingunoMediaAgregar/remover shards dinámicamente

5. Usa Middleware de Sharding

No construyas tu propio router de shard a menos que tengas que hacerlo:

SoluciónBase de DatosTipoMejor Para
VitessMySQLProxy/routerMySQL a gran escala (YouTube, Slack)
CitusPostgreSQLExtensiónSharding de PostgreSQL con cambios mínimos
MongoDBMongoDBNativoDocument-based, esquema flexible
CockroachDBCompatible PostgreSQLNativoDistribución global, consistencia fuerte
TiDBCompatible MySQLNativoHTAP (híbrido transaccional/analítico)
YugabyteDBCompatible PostgreSQL/CQLNativoCloud-native, escala planetaria
-- Ejemplo: Citus (extensión PostgreSQL)
-- Convertir una tabla en tabla distribuida

-- Agregar extensión Citus
CREATE EXTENSION IF NOT EXISTS citus;

-- Crear tabla distribuida
SELECT create_distributed_table('orders', 'user_id');

-- Citus maneja enrutamiento, rebalanceo y consultas distribuidas
-- La mayoría de consultas funcionan sin cambios
SELECT * FROM orders WHERE user_id = 123;  -- Enrutado a un solo shard
# Ejemplo: Fragmento de configuración de Vitess
# vschema.json define lógica de sharding
{
  "sharded": true,
  "vindexes": {
    "hash": {
      "type": "hash"
    }
  },
  "tables": {
    "orders": {
      "column_vindexes": [
        {
          "column": "user_id",
          "name": "hash"
        }
      ]
    }
  }
}

Mejores Prácticas

  • Comienza con enrutamiento basado en directorio. Es más fácil de rebalancear que el basado en hash.
  • Mantén shards lo más grandes posible. Shards más grandes pero menos son más fáciles de manejar que muchos pequeños.
  • Diseña para el evento de rebalanceo. Sucederá. Ten runbooks listos.
  • Evita transacciones cross-shard. Usa sagas, patrón outbox, o diseña alrededor de la necesidad.
  • Monitorea balance de shards. Alerta cuando cualquier shard exceda 120% del tamaño o QPS promedio.
  • Prueba con volúmenes de datos similares a producción. Datasets de prueba pequeños ocultan problemas de hot spots.
  • Planifica tus tablas globales. Tablas de búsqueda pequeñas (países, monedas) deberían replicarse a todos los shards.

Errores Comunes

  • Hacer shard demasiado temprano. El sharding añade complejidad masiva. Agota el escalado vertical y réplicas de lectura primero.
  • Mala elección de clave de shard. Una mala clave es peor que no hacer shard. Prueba la distribución con datos de producción.
  • Ignorar consultas cross-shard. Consultas que funcionaban en un solo nodo fallan o se vuelven lentas después del sharding.
  • Sin plan de rebalanceo. Shards desiguales crean hot spots que anulan los beneficios del sharding.
  • Perder semánticas ACID. Las transacciones multi-shard requieren coordinación a nivel de aplicación.
  • Subestimar el overhead operativo. Las bases de datos sharded son más difíciles de respaldar, monitorear y diagnosticar.

Variantes

  • Sharding funcional: Dividir por dominio (base de datos de usuarios, base de datos de pedidos) en lugar de por fila — más simple, no requiere router
  • Sharding zonal: Shard por geografía (datos de UE en shards de UE) para cumplimiento
  • Sharding híbrido: Shard tablas grandes, replica tablas pequeñas — el patrón más común
  • Auto-sharding: Servicios gestionados (Amazon Aurora, Google Spanner, Azure Cosmos DB) manejan sharding transparentemente

FAQ

P: ¿Cuántos shards debería usar para empezar? Empieza con 4-8 shards. Menos shards son más fáciles de manejar. Puedes dividir shards más tarde (Vitess, Citus lo soportan).

P: ¿Cuál es la diferencia entre sharding y particionamiento? El particionamiento divide datos dentro de una sola instancia de base de datos. El sharding divide datos entre múltiples instancias independientes. El particionamiento es más simple pero no escala más allá de una máquina.

P: ¿Puedo cambiar mi clave de shard más tarde? Cambiar una clave de shard requiere migrar todos los datos. Es posible pero doloroso. Invierte en elegir la clave correcta desde el principio.

P: ¿Necesito un router de shard? Sí, a menos que uses una base de datos con sharding nativo (MongoDB, CockroachDB, YugabyteDB). Para PostgreSQL y MySQL, usa Citus o Vitess.

Conclusión

El sharding de base de datos es una estrategia de escalado potente pero compleja. Al elegir la clave de shard correcta, implementar enrutamiento robusto y planificar para rebalanceo, puedes escalar tu capa de base de datos horizontalmente. Pero haz shard solo cuando sea necesario — el overhead operativo es significativo y muchas cargas de trabajo pueden resolverse con enfoques más simples.

Recursos Relacionados