Sharding divide un conjunto de datos en partes que se distribuyen entre servidores o grupos de almacenamiento. Cada shard conserva una porción del conjunto y atiende las operaciones correspondientes. El objetivo es repartir capacidad cuando una sola unidad ya no sostiene el volumen o la carga requerida.
Dividir datos también divide su localidad. Una consulta o regla que antes cabía en una base puede empezar a necesitar varios participantes. La clave de partición y el enrutamiento deben elegirse junto con esas operaciones, no únicamente por el número de registros.
Antes de dividir la base
Primero se identifica el límite: CPU, almacenamiento, I/O, escritura, lectura o contención sobre determinados datos. Una consulta sin índice y una base que agotó su capacidad de escritura requieren respuestas distintas.
Revisar índices, planes de ejecución, tamaño de los resultados y consultas repetidas puede reducir trabajo sin cambiar la distribución. El escalado vertical añade recursos a una unidad, dentro de sus límites. Las réplicas pueden repartir lecturas compatibles con su retraso, pero no eliminan automáticamente la carga de escritura del primario.
Sharding encaja cuando repartir datos y operaciones permite superar el cuello de botella identificado y su coste operativo es aceptable. Las mediciones deben incluir latencia p95 y p99, crecimiento, concurrencia y margen durante fallos. Añadir nodos no garantiza una mejora proporcional si el trabajo sigue concentrado en una sola entidad.
Dimensionar antes de migrar
La decisión se apoya en capacidad sostenible medida, crecimiento esperado y tiempo necesario para migrar. Conviene reservar margen antes de alcanzar un límite absoluto. La localidad o el aislamiento de cargas también pueden justificar una separación anterior, con sus costes explícitos.
Como ejemplo hipotético, un crecimiento neto de datos nuevos de 20 GB por día, retenidos durante 365 días, produce 7,300 GB = 7.3 TB de datos brutos. Se utilizan unidades decimales: 1 TB equivale a 1,000 GB. Suponer un 40% adicional para índices y sobrecarga añade 2.92 TB y lleva el conjunto a 10.22 TB por copia. Un primario y dos réplicas completas sumarían 30.66 TB físicos agregados para esas copias. Logs, backups y espacio temporal de migración se presupuestan aparte; esos porcentajes son supuestos, no propiedades universales.
El cálculo dimensiona almacenamiento. Lecturas y escrituras deben probarse con su mezcla real de consultas, índices, bytes modificados, concurrencia, replicación y durabilidad. Se mide la carga que sostiene la latencia objetivo y el margen bajo fallos; dividir un promedio de peticiones entre nodos no demuestra esa capacidad.
Cifras como cientos de TB o miles de escrituras por segundo necesitan esas condiciones para interpretarse. El tamaño, la tasa y el coste medidos determinan si optimizar, escalar una unidad o repartir el trabajo resuelve el requisito.
Elegir la unidad y la clave
La clave de partición determina qué datos se agrupan. Puede ser tenant_id, user_id u otra unidad que coincida con las consultas y transacciones importantes. La elección debe favorecer la colocación de datos que se leen o modifican juntos.
Por ejemplo, agrupar por tenant_id puede mantener los usuarios y órdenes de un tenant en una misma partición. Una consulta que conoce ese tenant puede dirigirse a su propietario. Buscar órdenes por un criterio global, sin tenant, puede requerir fanout o un índice adicional que también debe mantenerse.
La cardinalidad alta aporta muchas unidades para distribuir, pero no garantiza balance. Un tenant puede tener más datos, peticiones o escrituras costosas que miles de otros. Deben observarse tamaño y trabajo por clave, además del número de claves.
Colocar datos relacionados permite conservar ciertas decisiones en una transacción local. Las restricciones también tienen alcance: comprobar unicidad dentro de un shard no demuestra unicidad global. Si la regla cruza tenants o particiones, necesita otra autoridad o coordinación explícita. La clave se elige considerando esas invariantes y el crecimiento esperado.
Particionado y enrutamiento
La agrupación lógica y la ubicación física son decisiones relacionadas. Hash o rangos pueden definir grupos; un directorio puede asignarlos a propietarios. Estas técnicas pueden combinarse.
| Mecanismo | Qué resuelve | Compromiso principal |
|---|---|---|
| Hash de la clave | Agrupa claves por una función de particionado. | Dispersa claves, pero puede convertir consultas por rango en fanout. |
| Rangos | Agrupa intervalos de una clave ordenada. | Facilita ciertas consultas; rangos desiguales o el extremo más reciente pueden concentrar carga. |
| Directorio de particiones lógicas | Mapea una partición a su propietario físico. | Permite mover grupos, pero exige mantener y actualizar rutas. |
Con claves monotónicas, como identificadores crecientes, las nuevas escrituras pueden acumularse en el último rango. Con hash, un usuario muy activo sigue concentrando trabajo si toda su entidad se asigna a un único grupo.
Consistent hashing puede reducir el movimiento de asignaciones al cambiar la topología frente a un esquema que remapea gran parte del conjunto. No elimina la migración, la transferencia de escrituras ni la coordinación de rutas. Los nodos virtuales permiten repartir más unidades de asignación; no garantizan uniformidad de bytes, peticiones o coste.
--- config: look: handDrawn --- flowchart TD app["Aplicación<br/>Consulta + clave"] --> router["Router<br/>Clave a partición lógica"] router -.->|"Resolver ubicación"| directory[("Directorio versionado<br/>Partición a propietario")] directory -.->|"Propietario + versión"| router router -->|"Grupo A"| shardA[("Shard 1<br/>Particiones asignadas")] router -->|"Grupo B"| shardB[("Shard 2<br/>Particiones asignadas")] router -->|"Grupo C"| shardC[("Shard 3<br/>Particiones asignadas")]
El diagrama representa una consulta con clave conocida y tres posibles propietarios. El directorio aporta metadatos de ubicación; el router dirige la operación al shard correspondiente. Las rutas pueden cachearse, pero deben poder refrescarse y detectar versiones antiguas durante una redistribución.
Hotspots y entidades grandes
Un usuario con una audiencia masiva o un tenant grande puede saturar su shard. Un shard dedicado aísla esa carga y permite asignarle recursos propios, pero no aporta capacidad ilimitada ni divide automáticamente una entidad.
Una clave compuesta como (user_id, bucket) puede repartir sus datos si el bucket realmente interviene en el particionado y separa unidades de trabajo. Añadirlo sin cambiar la asignación no redistribuye nada. La división también introduce costes: reunir el historial puede tocar varios shards y mantener orden o atomicidad entre buckets requiere coordinación.
Si una regla serializa todas las actualizaciones sobre el mismo saldo o registro, repartir otros datos no elimina esa contención. Debe decidirse qué operaciones son independientes y cuáles necesitan conservar una autoridad común.
Para lecturas repetidas, SD: Caching describe copias reutilizables y los límites de las hot keys. La caché puede descargar consultas compatibles con su frescura; no hace atómicas las escrituras distribuidas ni elimina el coste de reconstruir resultados. Aislar carga y limitar admisión siguen siendo decisiones de capacidad.
Consultas y transacciones entre shards
Una consulta sin una clave que permita localizar los datos puede enviarse a varios shards. Ese fanout añade solicitudes, trabajo y una fase de combinación. La latencia puede depender del participante más lento; un fallo parcial exige decidir entre rechazar, reintentar o devolver un resultado incompleto admitido por el producto.
Ordenar y paginar globalmente requiere un criterio estable y una forma de continuar sobre los participantes. Si cambian los datos entre páginas, pueden aparecer omisiones o repeticiones. Cuando se necesita una vista coherente del conjunto, el diseño debe definir un snapshot o un contrato de lectura que coordine esa consistencia.
La denormalización, los índices globales y las proyecciones pueden evitar parte del fanout en consultas concretas. Añaden almacenamiento, escrituras de mantenimiento y posibles ventanas de obsolescencia. No vuelven barata cualquier consulta ni aportan atomicidad por sí mismos.
Para una invariante fuerte, conviene colocar los datos juntos cuando sea posible o usar un sistema que soporte las transacciones distribuidas necesarias, revisando sus garantías y costes.
Una transferencia entre Bob y Alice
Supóngase que Bob envía $5 a Alice, en la misma moneda y sin comisiones. En una sola base autoritativa, una transacción puede descontar $5 de Bob, acreditar $5 a Alice y registrar los asientos del ledger de forma durable. La atomicidad confirma esos cambios juntos o los aborta. El aislamiento, los bloqueos o las condiciones de actualización deben proteger el saldo disponible frente a transferencias concurrentes y evitar sobregiros. El identificador de transferencia también permite reconocer un reintento.
Si las cuentas viven en shards distintos, esa frontera local ya no cubre ambos cambios. Un sistema con participantes compatibles puede usar 2PC para preparar su estado de forma durable y acordar una decisión de commit o abort. Añade coordinación y latencia; un participante preparado puede retener recursos mientras recupera una decisión incierta. El fallo del coordinador no significa que toda la base se vuelva inutilizable. Las garantías de aislamiento siguen dependiendo de la implementación.
Una Saga requiere un contrato diferente. Puede confirmar un débito local junto con una transferencia pendiente, después acreditar a Alice y finalmente cerrar el flujo. Durante ese intervalo, los $5 deben representarse como fondos en tránsito y la operación tiene un estado observable. Encaja únicamente si el negocio admite esos estados intermedios y su recuperación; no ofrece atomicidad global.
Cada paso conserva transfer_id, su resultado y el efecto local con coordinación atómica para tolerar reintentos. Si el crédito a Alice devuelve timeout, puede haber ocurrido. Antes de decidir una compensación se recupera o consulta su resultado, o se reintenta bajo el contrato idempotente. La decisión debe coordinarse con créditos tardíos: observar que todavía no aparece un crédito no basta si aún puede completarse.
Compensar puede requerir un asiento nuevo que devuelva fondos a Bob, sin borrar el débito original. Hacerlo automáticamente ante un resultado incierto podría devolverle dinero mientras Alice ya lo recibió. La compensación también puede fallar y quedar pendiente de reconciliación. La recuperación necesita registros durables, reintentos y resolución de fallos; no garantiza convergencia automática.
Si la regla exige que ambos lados cambien atómicamente, se conserva una autoridad o ledger con el límite transaccional adecuado, o se eligen garantías distribuidas que la satisfagan. La compensación no sustituye esa regla. SD: 2PC vs Saga explica la separación entre atomicidad, aislamiento y recuperación.
Migración y operación
Mover una partición requiere copiar datos y resolver las escrituras concurrentes. Mientras el origen conserva la autoridad, los cambios producidos durante la copia deben transferirse mediante un mecanismo recuperable, con orden o versiones e idempotencia. Verificar solo la copia inicial no demuestra que el destino esté actualizado.
El cutover establece cuándo cambia el propietario y qué versión de ruta lo identifica. El origen debe dejar de aceptar operaciones como propietario anterior o aplicar la redirección prevista. Los clientes con rutas antiguas deben refrescarlas o recibir una respuesta que permita resolverlas. El diseño necesita evitar que dos propietarios acepten escrituras incompatibles.
La transferencia debe verificarse antes del cambio y conservar una vía de recuperación. Puede requerir acotar o pausar escrituras durante parte del proceso. Después del cutover, volver a una copia antigua sin reconciliar las nuevas escrituras puede perder datos. Redistribuir tiene costes de red, almacenamiento y capacidad incluso con consistent hashing.
Sharding y replicación son distintos. El primero reparte porciones; la segunda conserva copias de una porción. Cada shard necesita su estrategia de alta disponibilidad, replicación, backups y restauración. Tener más shards no proporciona automáticamente esas garantías y añade unidades que pueden fallar.
Qué verificar
Las pruebas deben concentrar tráfico en una clave, llenar un rango reciente y comparar consultas locales con fanout. También deben mover una partición mientras llegan escrituras, introducir rutas antiguas y detener participantes antes y después del cutover. La recuperación debe explicar qué propietario tiene autoridad y qué operaciones quedaron confirmadas o pendientes.
Las métricas útiles se observan por shard y por partición: bytes, crecimiento, CPU, I/O, peticiones, coste de consulta y latencia p95 y p99. Importan también la concentración por clave, el retraso de replicación, el trabajo de migración pendiente y las operaciones que requieren varios participantes. Un promedio equilibrado puede ocultar un shard saturado.
En una entrevista, el diseño debe justificar la clave, las operaciones que permanecen locales y la recuperación cuando cambia la ubicación. La capacidad ganada se evalúa junto con las consultas, reglas de negocio y tareas operativas que ahora cruzan esa frontera.