Escrever uma regra observável
Começa com um serviço fictício que dispõe de 100 unidades e não recebe reposições durante o ensaio. A regra é simples: consumo aceite mais saldo restante tem de continuar a somar 100. Dois clientes leem 100 antes de qualquer escrita; cada um calcula 20 e grava esse valor, registando também 80 aceites. As duas transações de escrita confirmam. O resultado local é 160 aceites e 20 restantes, o que viola a regra. Não precisas de um saldo negativo para detetar o erro. O laboratório reproduz esta sequência deliberadamente e considera o teste passado quando observa a anomalia esperada, sem afirmar que o desenho é correto.
Ler a sequência de snapshots
O primeiro par de ensaios mantém uma transação A aberta enquanto B confirma uma alteração de 100 para 60. Em Read Committed, A observa 100 e depois 60. Na variante Repeatable Read, A observa 100 nas duas leituras e só vê 60 depois de terminar a transação. As observações foram recolhidas em PostgreSQL 18.6, através de duas sessões e uma ordem controlada. Não houve réplicas, caches ou failover. Antes de escolher um nível de isolamento, pergunta que conjunto de leituras tem de sustentar a decisão. Uma leitura estável pode ser útil para essa decisão, mas não demonstra por si só que todas as alterações concorrentes respeitam uma regra entre linhas.
Juntar condição e atualização
A variante de orçamento executa UPDATE allowance SET units=units-80 WHERE id=1 AND units>=80 RETURNING units. O código só regista aceitação quando recebe uma linha e conserva esse registo na mesma transação. A primeira tentativa devolve uma linha e deixa 20; a segunda devolve zero linhas. Neste cenário, zero significa que não houve reserva elegível, não um erro de sintaxe ou uma aceitação que deve ser comunicada como sucesso. O resultado é 80 aceites e 20 restantes. A proteção demonstrada tem âmbito local e de uma linha de orçamento. Não prova que um débito noutro serviço ou uma mensagem externa participa atomicamente na mesma operação.
Observar write skew entre linhas
O segundo problema exige manter pelo menos um elemento disponível. Existem duas linhas, ambas disponíveis. Duas transações Repeatable Read leem a contagem dois e cada uma retira um elemento diferente. Como as escritas afetam linhas diferentes, a sequência do laboratório permite confirmar ambas e termina com zero disponíveis. A regra foi violada apesar de cada transação ter usado um snapshot estável. Regista as leituras, os identificadores alterados e os commits; sem essa sequência, uma captura do estado final não explica a decisão. Este exemplo deve motivar uma revisão do mecanismo que protege a regra global, não uma conclusão de que qualquer utilização de Repeatable Read está errada.
Recalcular após a rejeição
Na variante Serializable, ambas as sessões começam por ler dois elementos disponíveis. Uma confirma a retirada e a outra recebe SQLSTATE 40001 no commit, nesta sequência concreta. O cliente rejeitado inicia uma nova transação, volta a ler e encontra apenas um disponível. A decisão recalculada recusa a segunda retirada; o estado final conserva um elemento. Reenviar apenas o UPDATE antigo perderia o propósito da repetição. No código de serviço, define também limites de tentativas e tratamento do resultado funcional. Não associes efeitos externos irreversíveis a uma tentativa que pode ser rejeitada sem desenhar a sua coordenação. O laboratório não executa mensagens, pagamentos ou qualquer efeito remoto.
Entregar uma decisão com limites claros
Conclui a oficina com um registo de decisão: regra funcional, sequência que a viola, variante proposta, resultado observado e trabalho ainda necessário. O ensaio usa PostgreSQL 18.6 compilado de fonte oficial, um cluster temporário, socket Unix privado e libpq através de Python. O script cria e termina apenas o seu próprio cluster. Guarda a versão, os hashes e a sequência controlada; isso permite discutir exatamente o que foi observado. Uma equipa fictícia de produção deve ainda rever contenção, limites de repetição, compatibilidade com outros escritores e recuperação. Não transfiras a evidência local para afirmações sobre latência em produção, replicação, transações distribuídas ou resiliência entre regiões.
python3 content/labs/design-evidence/run.py --postgres-prefix /path/to/postgresql-18.6
# Requires PostgreSQL 18.6 binaries and libpq; private disposable cluster
# No TCP listener; local Unix socket only
# Six actual PostgreSQL groups; three synthetic arithmetic groups
# Output includes runtime versions, hashes, observations and scope.Dois clientes fictícios leem 100, aceitam 80 cada e deixam saldo 20. O registo e o saldo violam a conservação apesar dos commits bem-sucedidos.
Armadilhas comuns
Confundir sucesso SQL com aceitação funcional, snapshot estável com qualquer invariante preservada ou repetição com reenvio cego da última escrita.
Tópicos relacionados: SQL · Microsserviços · Suporte L3
Define a regra, observa a sequência e decide sobre o estado atual. Um erro de serialização exige reavaliar a transação inteira, podendo mudar a decisão.
Referência: Transaction isolation · System design patterns; PostgreSQL18 scoped examples; primary guidance consulted 2026-09-30