aws.crafter.run

Datos

Write sharding

También llamado Write sharding

Repartir las escrituras de una clave muy caliente entre varias claves sintéticas, para dejar de chocar contra el techo de una sola partición de DynamoDB.

DelicadoVerificado el

El problema

Tu tabla está en modo bajo demanda, la gráfica de consumo total va holgada, y aun así DynamoDB empieza a devolver ProvisionedThroughputExceededException.

No es un error de configuración ni una cuota de cuenta. Es que el techo que estás tocando no es el de la tabla: es el de una partición.

Una sola clave4 000 escrituras/s contra un techo de 1 000 · ProvisionedThroughputExceededExceptionPEDIDOS#2026-09-06Diez sufijos400 escrituras/s por clave · el ancho de cada bloque es su carga, y ninguno llega al techotecho de una partición · 1 000/sPrecio a pagar: leer un día completo exige consultar los diez sufijos y unir los resultados
El techo de la partición no se amplía: se esquiva. Son 1 000 unidades de escritura por segundo y no hay ticket que lo suba. La única palanca es repartir las escrituras entre más valores de clave.

Cada partición de DynamoDB está diseñada para dar como máximo 3 000 unidades de lectura y 1 000 de escritura por segundo. Y como la partición se decide pasando la partition key por una función hash, todos los elementos con la misma clave viven en el mismo sitio físico y comparten ese techo.

Así que basta con una clave popular para agotarlo mientras el resto de la tabla está ociosa. Los sospechosos habituales son siempre los mismos:

La solución

Ampliar el espacio de claves añadiendo un sufijo. AWS describe dos formas, y la diferencia entre ellas decide si podrás volver a leer un elemento concreto.

Sufijo aleatorio

Al escribir, eliges un número al azar entre 1 y N y lo concatenas: la clave 2026-09-06 se convierte en 2026-09-06.1, 2026-09-06.2, y así hasta 2026-09-06.200. Las escrituras se reparten solas y el throughput se multiplica por N.

El precio: no sabes en qué sufijo quedó un elemento concreto. Para leerlo, tendrías que consultar los N.

Sufijo calculado

En vez de un número al azar, se calcula a partir de algo por lo que ya vas a consultar. Si tus elementos tienen un OrderId, el sufijo sale de una función determinista sobre él — AWS propone algo tan simple como el producto de los puntos de código UTF-8 del identificador, módulo 200, más 1.

El reparto es igual de bueno, y además puedes reconstruir la clave: dado un OrderId, recalculas el sufijo y haces un GetItem directo.

Lo que no cambia entre las dos estrategias: para leer todos los elementos de un día sigues teniendo que consultar los N sufijos y unir los resultados en tu código.

Cuándo usarlo

Cuándo NO usarlo

Cómo implementarlo

  1. Identifica la clave caliente con Contributor Insights y con la métrica de peticiones estranguladas.
  2. Elige N con margen: escrituras por segundo previstas dividido entre 1 000, por un factor de holgura. Con 4 000/s, diez sufijos dejan 400 por clave.
  3. Decide la estrategia: calculado si necesitas leer elementos sueltos; aleatorio si solo escribes y lees por lotes.
  4. Encapsula el cálculo del sufijo en una función y no lo repitas por ahí: el día que cambie N, tiene que cambiar en un solo sitio.
  5. Escribe también la lectura por lotes —consultar los N y unir— porque alguien la va a necesitar.
  6. Documenta N junto al modelo. Sin ese número, la tabla es ilegible.

El código

const SUFIJOS = 10;

// Calculado, no aleatorio: dado el pedido podemos volver a encontrarlo.
// La formula es la que sugiere AWS — suma de puntos de codigo, modulo N.
const sufijo = (id: string) =>
  ([...id].reduce((a, c) => a + c.codePointAt(0)!, 0) % SUFIJOS) + 1;

const claveDe = (fecha: string, pedidoId: string) =>
  `PEDIDOS#${fecha}.${sufijo(pedidoId)}`;

// ── Escribir: reparte solo ─────────────────────────────────────────
await ddb.send(new PutCommand({
  TableName: 'app',
  Item: { PK: claveDe(fecha, pedido.id), SK: `PEDIDO#${pedido.id}`, ...pedido },
}));

// ── Leer uno: la clave se reconstruye ──────────────────────────────
const uno = await ddb.send(new GetCommand({
  TableName: 'app',
  Key: { PK: claveDe(fecha, pedidoId), SK: `PEDIDO#${pedidoId}` },
}));

// ── Leer el dia entero: N consultas y una union ────────────────────
// Este es el precio del patron, y conviene tenerlo escrito una sola vez.
const dia = async (fecha: string) => {
  const partes = await Promise.all(
    Array.from({ length: SUFIJOS }, (_, i) =>
      ddb.send(new QueryCommand({
        TableName: 'app',
        KeyConditionExpression: 'PK = :pk',
        ExpressionAttributeValues: { ':pk': `PEDIDOS#${fecha}.${i + 1}` },
      })),
    ),
  );
  return partes.flatMap((p) => p.Items ?? []);
};

Te va a morder

Coste

Concepto Efecto
Escrituras igual — el mismo número de elementos, repartidos
Lectura de un elemento igual, con sufijo calculado
Lectura del conjunto × N consultas
Almacenamiento igual
Complejidad del modelo sube, y es permanente

El sharding no cuesta más en escritura: escribes lo mismo, solo que en sitios distintos. Lo que sube es el coste de las lecturas agregadas y, sobre todo, el coste humano — el modelo deja de ser evidente y hay que documentar N.

Y una comparación que conviene hacer antes: repartir la clave es gratis; reordenar el modelo para que la consulta caliente no exista puede ser mejor. Si ESTADO#pendiente está caliente porque consultas los pendientes cada segundo, quizá el arreglo no es trocear esa clave sino mantener la lista de pendientes en otro sitio.

Fuentes

Techo de 3 000 unidades de lectura y 1 000 de escritura por partición, y el ejemplo del elemento de 20 KB: Best practices for designing and using partition keys. Estrategias de sufijo aleatorio y sufijo calculado, la fórmula sugerida y el coste de leer el conjunto completo: Using write sharding to distribute workloads evenly. Capacidad por tabla y por cuenta: Quotas in Amazon DynamoDB.

Patrones relacionados

Un enlace sin la relación nombrada es un "ver también". Aquí cada uno dice qué relación tiene y por qué.