Contadores de referencias en DynamoDB
Un contador de referencias es un número que guardas en un item padre y que sigue cuántos items hijo apuntan a él — likes de un post, miembros de un workspace, replies de un comentario. Lo mantienes porque contar los hijos en cada lectura es demasiado caro.
¿Cómo mantienes un contador en DynamoDB?
Guarda el total corriente como un número en el item padre y actualízalo en la misma escritura que crea el hijo. Un hace que aterrizan ambos o ninguno, y una condición en la escritura del hijo evita que los reintentos cuenten doble — así un solo GetItem devuelve un conteo exacto.
- No cuentes hijos en tiempo de lectura. Un
Querypara contar likes paga por cada item like que escanea. Guarda el total en el post y lee un solo item. - Mantén el contador donde se escribe el hijo, no después. Súbelo en la misma operación que crea el hijo para que los dos nunca se desincronicen.
- Usa una cuando la escritura y el bump tocan items distintos. Un like es
un item, el contador vive en otro —
TransactWriteItemshace que aterrizan ambos o ninguno. - El pie es el doble conteo. Un like reintentado o duplicado que vuelve a ejecutar el incremento infla el número. Protege la escritura del hijo con una condición.
Por qué contar en absoluto
Si vienes de SQL, nunca guardarías un like-count — harías SELECT COUNT(*) FROM likes WHERE post_id = ? y dejarías que un índice lo hiciera barato. DynamoDB no tiene un COUNT(*)
que se salte leer items.
Un Query sobre los likes de un post lee — y factura — cada item like de esa
partición, aunque solo quieras el número. En un post viral son miles de
RCUs para responder «¿cuántos likes?». Ese es el pie de lectura que existen los
contadores de referencias para matar.
Así que : guardas el total corriente en el propio post. Leer el conteo
pasa a ser un solo GetItem. El coste es que ahora te toca mantenerlo exacto.
Modela los items
Dos tipos de item comparten una partición para que el post y sus likes vivan en una colección de items. Claves inventadas:
| PK | SK | attributes |
|---|---|---|
| POST#a91f | META | likeTally (Number), body, authorId, createdAt |
| PK | SK | attributes |
|---|---|---|
| POST#a91f | LIKE#USER#7c20 | likedAt |
El atributo likeTally del item META es el contador de referencias. Cada item
LIKE# es un hijo. Poner ambos bajo PK = "POST#a91f" significa que un solo Query
puede traer el post y sus likers juntos cuando sí quieres la lista.
Sube el contador de forma atómica
DynamoDB incrementa un número con un ADD (o SET x = x + :n) —
esto es un contador atómico: DynamoDB aplica el delta en el servidor sin que tú
leas primero el valor actual, así que incrementos concurrentes no se pisan.
(AWS: contadores atómicos)
Dar like a un post son dos escrituras a dos items — crear el item LIKE#,
y sumar 1 a likeTally en META. Si el like aterriza pero el bump falla, el
tally queda mal para siempre. Necesitas ambos o ninguno.
Eso es lo que garantiza TransactWriteItems — todo o nada entre varios items,
y cancela toda la transacción si algún item se modifica concurrentemente
(AWS: pessimistic locking con transacciones):
{
"TransactItems": [
{
"Put": {
"TableName": "Social",
"Item": {
"PK": {"S": "POST#a91f"},
"SK": {"S": "LIKE#USER#7c20"},
"likedAt": {"N": "1750636800"}
},
"ConditionExpression": "attribute_not_exists(SK)"
}
},
{
"Update": {
"TableName": "Social",
"Key": {
"PK": {"S": "POST#a91f"},
"SK": {"S": "META"}
},
"UpdateExpression": "ADD likeTally :one",
"ExpressionAttributeValues": {":one": {"N": "1"}}
}
}
]
}El Put y el Update se confirman juntos. Si falla cualquiera, DynamoDB hace rollback de
ambos y devuelve un TransactionCanceledException.
Protégelo del doble conteo
El bug de verdad no es un like a medias — la transacción lo evita. Es el
mismo usuario dando like dos veces, o un retry del cliente rejugando la petición. Cada replay
suma otro 1, y likeTally se desvía en silencio por encima del conteo real.
La ConditionExpression: attribute_not_exists(SK) del Put es la guarda. Si
el item LIKE# de ese usuario ya existe, la condición del Put falla, se cancela
toda la transacción y — crítico — el ADD nunca corre. Un like por usuario,
impuesto por la clave.
Construye y copia estas expresiones de update y condición — con los
ExpressionAttributeValues correctos y la guarda attribute_not_exists — en el
DynamoDB Expression Builder en vez de
montar el JSON a mano.
Unlike, y el coste
Quitar un like es la imagen espejo: Delete el item LIKE# con
ConditionExpression: attribute_exists(SK), y ADD likeTally :minusOne en la
misma transacción. La condición evita que un doble-unlike lleve el tally a negativo.
Conoce el precio. Una escritura transaccional cuesta 2 WCUs por item para items de hasta 1 KB — una para preparar, una para confirmar — frente a 1 WCU de una escritura simple. Un like son dos items, así que cada like son unas cuatro WCUs. Barato por acción, pero conviene saberlo antes de que un post de celebridad se lleve una tormenta de likes.
Míralo en DynoTable
Cuando sospechas que un tally se ha desviado, quieres comparar el likeTally guardado
con el número real de hijos LIKE# — sin lanzar un count query en prod.

Para una reconciliación de verdad sobre un conjunto acotado de posts — «¿qué tallies no
cuadran con sus conteos de hijos?» — el SQL Workbench de DynoTable ejecuta el GROUP BY
y el join en el cliente sobre las filas que has cargado, lo que PartiQL plano no puede
expresar.
Escollos y próximos pasos
- No mantengas el contador out-of-band (un Lambda que recuenta cada noche). Es un parche sobre un camino de escritura que debería haber sido transaccional desde el principio.
- Vigila las . Un solo post salvajemente popular concentra cada like — y cada bump de tally — en una sola clave de partición. El conteo es correcto; la partición aún puede hacer throttle.
- Reconcilia poco, repara con cirugía. El drift debería ser casi cero si cada mutación está condicionada. Trata un desajuste como un bug a encontrar, no como un número a sobrescribir.
Lectura relacionada: single-table design para por qué el post y los likes comparten partición, y Query vs Scan para por qué contar hijos en tiempo de lectura es el patrón que evitas.
Luego descarga DynoTable para inspeccionar estas colecciones de items y verificar tus tallies contra tus propias tablas.


