Intermedio6 min de lectura

Condition Expressions en DynamoDB: la guía completa (con ejemplos)

Una expresión de condición es un predicado que DynamoDB evalúa sobre el elemento existente antes de confirmar tu escritura. Si el predicado es falso, la escritura se rechaza y nada cambia. Es lo más parecido que tiene DynamoDB a una cláusula WHERE en una escritura — y la única forma segura de imponer un invariante.

¿Cómo funcionan las expresiones de condición de DynamoDB?

Una expresión de condición es un predicado que DynamoDB evalúa del lado del servidor contra el elemento actual antes de confirmar una escritura. Si es verdadero, la escritura continúa; si es falso, la escritura se rechaza con ConditionalCheckFailedException y nada cambia. Fusiona la comprobación y la mutación en una sola operación atómica, de modo que los llamadores concurrentes no pueden competir con una lectura obsoleta.

  • Es una guarda, no un filtro. ConditionExpression se ejecuta del lado del servidor sobre el elemento actual; un resultado falso hace fallar la escritura con ConditionalCheckFailedException.
  • Reemplaza al leer-luego-escribir. Sin un viaje de ida y vuelta SELECT y luego UPDATE — la comprobación y la mutación son una sola operación atómica, de modo que dos llamadores no pueden competir.
  • Es gratis rechazar, no gratis ejecutar. Una escritura condicional fallida aún consume capacidad de escritura. Una escritura rechazada factura WCU por el tamaño del elemento existente contra el que se comprobó (mínimo 1) — un crear-si-ausente fallido cuesta 1 WCU.

Viniendo de SQL, leerías la fila, la comprobarías en el código de la aplicación y luego actualizarías. En DynamoDB ese hueco entre la lectura y la escritura es un error de corrupción de datos esperando a un llamador concurrente. La expresión de condición cierra el hueco.

Dónde se aplican

Adjuntas una ConditionExpression a PutItem, UpdateItem, DeleteItem y a cada acción dentro de TransactWriteItems. No forma parte de Query ni Scan — esos usan FilterExpression, que es algo diferente en la ruta de lectura.

Esa distinción confunde a la gente, así que sé preciso:

ConditionExpressionFilterExpression
RutaEscrituras (Put/Update/Delete)Lecturas (Query/Scan)
Efecto en caso de falloRechaza toda la escrituraDescarta el elemento de los resultados
VeEl elemento actual, previo a la escrituraCada elemento candidato, tras la lectura
CostoLa escritura fallida aún facturaLos elementos filtrados aún se facturan por la lectura

Ambos se ejecutan del lado del servidor. La diferencia está en lo que hace "falso": una condición aborta una mutación; un filtro solo oculta una fila que ya pagaste por leer. (AWS: Expresiones de condición)

Las funciones que realmente usarás

El lenguaje de condiciones es pequeño. Las herramientas de trabajo:

  • attribute_exists(path) / attribute_not_exists(path) — ¿este existe en el elemento? El modismo clásico para "crear solo si está ausente" / "actualizar solo si está presente".
  • Comparadores — =, <>, <, <=, >, >= — contra un valor u otro atributo.
  • attribute_type, begins_with, contains, size — comprobaciones de tipo y de cadena/conjunto.
  • BETWEEN … AND …, IN (…) — rango y pertenencia.
  • AND, OR, NOT, paréntesis — para combinar lo anterior.

attribute_not_exists sobre la es la forma canónica de hacer que PutItem se comporte como una inserción que no sobrescribirá un elemento existente — DynamoDB no tiene una operación de "inserción" separada, así que la condición es la semántica de inserción. (AWS: Referencia de operadores de comparación y funciones)

Un ejemplo trabajado: proteger un libro mayor contra el descubierto

Toma un libro mayor bancario. Cada cuenta es un elemento:

PK = "ACCT#a7f3"
SK = "BALANCE"
clearedCents = 50000
holdCents    = 0

El invariante: un débito nunca debe empujar el saldo disponible por debajo de cero, y nunca debes debitar una cuenta que no existe. Dos reglas, ambas exigibles en la propia escritura.

La forma incorrecta (la trampa)

GetItem ACCT#a7f3 / BALANCE     → clearedCents = 50000
if (50000 >= 30000) ...         ← app-side check
UpdateItem  SET clearedCents = 20000

Entre el GetItem y el UpdateItem, un segundo débito puede leer el mismo 50000, pasar su propia comprobación y escribir también. Ambos tienen éxito; la cuenta se pone en negativo. Esto es una carrera de leer-modificar-escribir, y ninguna cantidad de validación del lado de la aplicación lo arregla — la comprobación y la escritura son operaciones separadas.

La forma correcta

Fusiona la comprobación en la escritura. Debita 30000 céntimos, condicionado a que la cuenta exista y tenga suficiente:

UpdateItem  ACCT#a7f3 / BALANCE
  SET clearedCents = clearedCents - :amt
  ConditionExpression:
    attribute_exists(PK) AND clearedCents >= :amt

con :amt = 30000. Si el saldo es demasiado bajo, o el elemento nunca se creó, DynamoDB rechaza la escritura con ConditionalCheckFailedException y el saldo queda intacto. El débito concurrente o bien ve el saldo original y se comprueba contra él, o ve el actualizado — nunca una lectura obsoleta sobre la que actuó.

Puedes construir y copiar la expresión exacta — nombres, valores y todo — con el Generador de expresiones de DynamoDB en lugar de ensamblar a mano el mapa ExpressionAttributeValues.

Pruébalo aquí mismo — este generador viene preconfigurado con un PutItem protegido (attribute_not_exists) para que puedas leer la ConditionExpression generada:

Construye tu solicitud
Código generado
new PutItemCommand({
  "TableName": "AuditLog",
  "Item": {
    "pk": {
      "S": "TENANT#acme"
    },
    "sk": {
      "S": "EVENT#2026-06-24T10:00:00Z"
    },
    "action": {
      "S": "login"
    }
  },
  "ConditionExpression": "attribute_not_exists(#cond0)",
  "ExpressionAttributeNames": {
    "#cond0": "pk"
  }
})

Inspeccionar la guarda en DynoTable

Cuando una escritura condicional falla, quieres ver el estado real del elemento, no adivinarlo. Abre el elemento de la cuenta y lee clearedCents directamente.

La colección del libro mayor en DynoTable — el elemento BALANCE muestra clearedCents por encima de los elementos de transacción de la cuenta.
La colección del libro mayor en DynoTable — el elemento BALANCE muestra clearedCents por encima de los elementos de transacción de la cuenta.

Lee el rechazo, no reintentes a ciegas

ConditionalCheckFailedException no es un error transitorio — reintentar la misma escritura no cambia nada. Significa que se disparó una regla de negocio: fondos insuficientes, creación duplicada, versión obsoleta. Preséntalo como un resultado de dominio, no como un fallo de infraestructura.

Dos cosas hacen depurables los fallos:

  • ReturnValuesOnConditionCheckFailure: ALL_OLD — DynamoDB devuelve el elemento actual junto con el fallo, para que puedas mostrar "el saldo era 20000, pediste 30000" sin una segunda lectura. (AWS: Trabajar con elementos)
  • Distinguir las dos razones de fallo. attribute_exists(PK) AND clearedCents >= :amt colapsa "no hay cuenta" y "no hay fondos" en una sola excepción. Si los llamadores necesitan diferenciarlos, divídelo en dos escrituras o inspecciona el elemento devuelto.

El bloqueo optimista es el mismo truco

El patrón de número de versión es solo una expresión de condición con otro sombrero. Almacena un atributo version; cada escritura afirma la versión que leíste y la incrementa:

UpdateItem  ACCT#a7f3 / BALANCE
  SET clearedCents = :new, version = :next
  ConditionExpression: version = :seen

Si otro escritor se movió primero, version = :seen es falso, la escritura se rechaza, y vuelves a leer y reintentas. Así hace DynamoDB el control de concurrencia sin bloqueos — afirma lo que viste, falla si se movió. (AWS: Bloqueo optimista con número de versión) El área de preparación de DynoTable ejecuta este patrón por ti — una edición concurrente aparece como un conflicto que resolver, no como una escritura perdida.

Escollos y próximos pasos

  • Nombres que colisionan con palabras reservadas. status, size, name y unas ~570 más están reservadas. Aliasízalas con ExpressionAttributeNames (#s = status) o la solicitud se rechaza con una ValidationException ('Attribute name is a reserved keyword'). El comprobador de palabras reservadas toma tus nombres de atributo y te devuelve el mapa de alias listo para pegar.
  • Una condición no puede referenciar otro elemento. Solo ve el elemento que se está escribiendo. Los invariantes entre elementos necesitan TransactWriteItems con una ConditionExpression por acción, o un ConditionCheck contra un elemento centinela.
  • Las escrituras fallidas aún cuestan WCU. Una guarda que rechaza el 90% de las veces aún factura esos rechazos. Un seguro barato, pero no gratis.

Para modelar las claves contra las que se ejecutan estas guardas, consulta diseño de tabla única y Query frente a Scan. Cuando estés listo para emitir escrituras condicionales contra datos reales, descarga DynoTable y ejecútalas contra tus propias tablas.

Actualizado