Condition Expressions no DynamoDB
Uma condition expression é um predicado que o DynamoDB avalia no item existente
antes de commitar sua escrita. Se o predicado for falso, a escrita é rejeitada e
nada muda. É a coisa mais próxima que o DynamoDB tem de uma cláusula WHERE em uma
escrita — e a única forma segura de impor uma invariante.
Como funcionam as condition expressions do DynamoDB?
Uma condition expression é um predicado que o DynamoDB avalia no servidor sobre o item atual antes de commitar uma escrita. Se for verdadeiro, a escrita prossegue; se for falso, a escrita é rejeitada com ConditionalCheckFailedException e nada muda. Ela funde a verificação e a mutação em uma única operação atômica, então chamadores concorrentes não podem competir sobre uma leitura obsoleta.
- É uma guarda, não um filtro. A
ConditionExpressionroda no servidor sobre o item atual; um resultado falso faz a escrita falhar comConditionalCheckFailedException. - Substitui o ler-depois-escrever. Sem uma ida e volta de
SELECTdepoisUPDATE— a verificação e a mutação são uma operação atômica, então dois chamadores não podem competir. - É de graça rejeitar, não de graça rodar. Uma escrita condicional que falha ainda consome capacidade de escrita. A garantia custa o mesmo que a escrita que ela bloqueia.
Vindo do SQL, você leria a linha, a checaria no código do app, depois atualizaria. No DynamoDB essa lacuna entre leitura e escrita é um bug de corrupção de dados esperando um chamador concorrente. A condition expression fecha a lacuna.
Onde elas se aplicam
Você anexa uma ConditionExpression a PutItem, UpdateItem, DeleteItem, e a cada
ação dentro de TransactWriteItems. Ela não faz parte de Query ou Scan —
esses usam FilterExpression, que é uma coisa diferente no caminho de leitura.
Essa distinção confunde as pessoas, então seja preciso:
ConditionExpression | FilterExpression | |
|---|---|---|
| Caminho | Escritas (Put/Update/Delete) | Leituras (Query/Scan) |
| Efeito na falha | Rejeita a escrita inteira | Remove o item dos resultados |
| Vê | O item atual, pré-escrita | Cada item candidato, pós-leitura |
| Custo | Escrita que falha ainda é cobrada | Itens filtrados ainda são cobrados na leitura |
As duas rodam no servidor. A diferença é o que "falso" faz: uma condição aborta uma mutação; um filtro só esconde uma linha que você já pagou para ler. (AWS: Condition Expressions)
As funções que você de fato vai usar
A linguagem de condição é pequena. Os cavalos de batalha:
attribute_exists(path)/attribute_not_exists(path)— este atributo existe no item? O idioma clássico para "criar só se ausente" / "atualizar só se presente".- Comparadores —
=,<>,<,<=,>,>=— contra um valor ou outro atributo. attribute_type,begins_with,contains,size— verificações de tipo e de string/set.BETWEEN … AND …,IN (…)— intervalo e pertencimento.AND,OR,NOT, parênteses — para combinar os acima.
attribute_not_exists na chave de partição é a forma canônica de fazer PutItem se
comportar como um insert que não vai sobrescrever um item existente — o DynamoDB não tem
uma operação "insert" separada, então a condição é a semântica de insert.
(AWS: Comparison Operator and Function Reference)
Um exemplo prático: protegendo um livro-razão contra saldo negativo
Pegue um livro-razão bancário. Cada conta é um item:
PK = "ACCT#a7f3"
SK = "BALANCE"
clearedCents = 50000
holdCents = 0A invariante: um débito nunca deve empurrar o saldo disponível abaixo de zero, e você nunca deve debitar uma conta que não existe. Duas regras, ambas impostas na própria escrita.
O jeito errado (a cilada)
GetItem ACCT#a7f3 / BALANCE → clearedCents = 50000
if (50000 >= 30000) ... ← app-side check
UpdateItem SET clearedCents = 20000
Entre o GetItem e o UpdateItem, um segundo débito pode ler os mesmos 50000, passar
na sua própria verificação, e escrever também. Os dois sucedem; a conta fica negativa.
Essa é uma corrida de read-modify-write, e nenhuma quantidade de validação no lado do app
a resolve — a verificação e a escrita são operações separadas.
O jeito certo
Dobre a verificação para dentro da escrita. Debite 30000 centavos, condicional a a conta existir e ter o suficiente:
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = clearedCents - :amt
ConditionExpression:
attribute_exists(PK) AND clearedCents >= :amtcom :amt = 30000. Se o saldo for baixo demais, ou o item nunca tiver sido criado, o
DynamoDB rejeita a escrita com ConditionalCheckFailedException e o saldo fica intacto.
O débito concorrente ou vê o saldo original e é checado contra ele, ou vê o atualizado —
nunca uma leitura obsoleta sobre a qual ele agiu.
Você pode construir e copiar a expressão exata — nomes, valores e tudo — com o
DynamoDB expression builder em vez de montar à mão
o mapa ExpressionAttributeValues.
Inspecionando a guarda no DynoTable
Quando uma escrita condicional falha, você quer ver o estado real do item, não adivinhá-lo.
Puxe o item da conta e leia clearedCents diretamente.

Leia a rejeição, não tente de novo às cegas
ConditionalCheckFailedException não é um erro transitório — tentar a mesma escrita de
novo não muda nada. Significa que uma regra de negócio disparou: fundos insuficientes,
criação duplicada, versão obsoleta. Trate-a como um resultado de domínio, não um soluço
de infraestrutura.
Duas coisas tornam as falhas depuráveis:
ReturnValuesOnConditionCheckFailure: ALL_OLD— o DynamoDB retorna o item atual junto com a falha, então você pode mostrar "o saldo era 20000, você pediu 30000" sem uma segunda leitura. (AWS: Working with Items)- Distinguir as duas razões de falha.
attribute_exists(PK) AND clearedCents >= :amtcolapsa "sem conta" e "sem fundos" em uma só exceção. Se os chamadores precisam diferenciá-las, separe em duas escritas ou inspecione o item retornado.
Optimistic locking é o mesmo truque
O padrão de número de versão é só uma condition expression vestindo outro chapéu.
Armazene um atributo version; toda escrita afirma a versão que você leu e a incrementa:
UpdateItem ACCT#a7f3 / BALANCE
SET clearedCents = :new, version = :next
ConditionExpression: version = :seenSe outro escritor se moveu primeiro, version = :seen é falso, a escrita é rejeitada, e
você relê e tenta de novo. É assim que o DynamoDB faz controle de concorrência sem
locks — afirme o que você viu, falhe se mudou. (AWS: Optimistic Locking with Version
Number)
Armadilhas e próximos passos
- Nomes que colidem com palavras reservadas.
status,size,name, e ~570 outros são reservados. Aliase-os comExpressionAttributeNames(#s = status) ou sua expressão silenciosamente falha ao parsear. - Uma condição não pode referenciar outro item. Ela só vê o item sendo escrito.
Invariantes entre itens precisam de
TransactWriteItemscom umaConditionExpressionpor ação, ou umConditionCheckcontra um item sentinela. - Escritas que falham ainda custam WCUs. Uma guarda que rejeita 90% das vezes ainda cobra por essas rejeições. Seguro barato, mas não de graça.
Para modelar as chaves contra as quais essas guardas rodam, veja single-table design e Query vs Scan. Quando você estiver pronto para emitir escritas condicionais contra dados reais, baixe o DynoTable e rode-as contra suas próprias tabelas.


