Catálogo de Concorrência
Cenários Canônicos & Mitigações
Explore 10 cenários de falhas de concorrência baseados em pesquisas acadêmicas de isolamento e casos reais de sistemas financeiros e distribuídos.
Lost Update Bancário
Saques concorrentes sob READ COMMITTED sobrescrevem saldos sem serialização transacional, perdendo quantias silenciosamente.
Saques concorrentes sob READ COMMITTED sobrescrevem saldos sem serialização transacional, perdendo quantias silenciosamente.
Schema e dados iniciais
-- Schema
CREATE TABLE accounts (
id INT PRIMARY KEY,
owner TEXT NOT NULL,
balance INT NOT NULL
);
-- Seed
INSERT INTO accounts VALUES (1, 'Alice', 1000);Carga concorrente (chaos.yaml)
version: "1.0"
name: "banking_lost_update"
database:
driver: "sqlite"
operations:
- name: "withdraw_100"
steps:
- "SELECT balance FROM accounts WHERE id = 1 -> cur"
- "UPDATE accounts SET balance = {cur - 100} WHERE id = 1"
invariants:
- name: "total_balance_check"
query: "SELECT balance FROM accounts WHERE id = 1;"
assert: "balance == 1000 - (total_completed * 100)"Por que a invariante quebra
Duas transações concorrentes de leitura-modificação-escrita leem o saldo de 1000 simultaneamente. T1 escreve 900, mas T2 sobrescreve com 900 com base em sua leitura defasada, perdendo silenciosamente $100.
T1 ──(rw)──► T2 ──(ww)──► T1Delta debugging reduz 20 operações às 2 que causam a falha.
Correção em produção
Update Atômico com Predicado de Guarda ou Bloqueio Pessimista
A anomalia ocorre pelo padrão vulnerável Read-Modify-Write sob READ COMMITTED: a aplicação lê o valor em um SELECT e computa a subtração na memória do servidor web. Se duas goroutines lerem balance=1000 ao mesmo tempo, ambas calculam 900 e o último UPDATE sobrescreve o primeiro. A mitigação transfere o cálculo para o motor de banco em uma única instrução atômica (UPDATE accounts SET balance = balance - 100 WHERE id = 1 AND balance >= 100), serializando a alteração sob o bloqueio exclusivo de linha.
PostgreSQL, SQLite (WAL), MySQL (InnoDB)
-- Opção 1: Update Atômico com Aritmética no Motor (Recomendado)
UPDATE accounts
SET balance = balance - 100
WHERE id = 1 AND balance >= 100;
-- Opção 2: Bloqueio Pessimista dentro de Transação Explícita
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- Aplicação valida balance >= 100 antes de submeter escrita:
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;