ChaosSQL

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.

A5B

Desvio de Escrita Hospitalar (Write Skew)

Dois médicos cancelam plantão simultaneamente sob Snapshot Isolation porque seus conjuntos de escrita não colidem.

Dois médicos cancelam plantão simultaneamente sob Snapshot Isolation porque seus conjuntos de escrita não colidem.

Schema e dados iniciais

schema.sql & seed.sql
-- Schema
CREATE TABLE doctors (
    id INT PRIMARY KEY,
    name TEXT NOT NULL,
    on_duty BOOLEAN NOT NULL
);

-- Seed
INSERT INTO doctors VALUES (1, 'Dr. Alice', true), (2, 'Dr. Bob', true);

Carga concorrente (chaos.yaml)

chaos.yaml
version: "1.0"
name: "hospital_write_skew"
operations:
  - name: "sign_off_alice"
    steps:
      - "SELECT count(*) AS active FROM doctors WHERE on_duty = true -> act"
      - "UPDATE doctors SET on_duty = false WHERE id = 1 AND {act >= 2}"
  - name: "sign_off_bob"
    steps:
      - "SELECT count(*) AS active FROM doctors WHERE on_duty = true -> act"
      - "UPDATE doctors SET on_duty = false WHERE id = 2 AND {act >= 2}"
invariants:
  - name: "at_least_one_doctor_on_duty"
    query: "SELECT count(*) AS active FROM doctors WHERE on_duty = true;"
    assert: "active >= 1"

Por que a invariante quebra

Sob Snapshot Isolation, T1 e T2 leem o mesmo snapshot (2 médicos ativos). T1 altera Alice, T2 altera Bob. Como seus conjuntos de escrita são disjuntos, o SI padrão permite ambos os commits, deixando o hospital sem médicos.

T1 ──(rw)──► T2 ──(rw)──► T1

Delta debugging reduz 40 operações às 2 que causam a falha.

Correção em produção

Elevação para Serializable Snapshot Isolation (SSI) ou Bloqueio de Conflito

Write Skew (A5B) é o exemplo clássico de falha sob Snapshot Isolation (SI): como T1 altera a linha 1 e T2 altera a linha 2, os conjuntos de escrita não colidem (sem conflito ww), mas geram um ciclo de anti-dependência de leitura-escrita (rw). A solução canônica é configurar SERIALIZABLE (onde motores modernos com SSI rastreiam dependências rw e abortam transações concorrentes com serialization_failure) ou serializar os acessos através de um lock FOR UPDATE em uma linha mestre de escala.

✓ PostgreSQL (SSI)✓ CockroachDB✓ MySQL (SERIALIZABLE)

PostgreSQL (SSI), CockroachDB, MySQL (SERIALIZABLE)

remediation_fix.sql
-- Solução A: Elevação do Nível de Isolamento para SERIALIZABLE (SSI)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT count(*) FROM doctors WHERE on_duty = true;
-- Se count >= 2:
UPDATE doctors SET on_duty = false WHERE id = 1;
COMMIT; -- O motor PostgreSQL detectará o ciclo rw e abortará T2 com código 40001!

-- Solução B: Bloqueio Pessimista Compartilhado em Linha Pai de Plantão
BEGIN;
SELECT shift_id FROM duty_shifts WHERE shift_id = 1 FOR UPDATE;
SELECT count(*) FROM doctors WHERE on_duty = true;
UPDATE doctors SET on_duty = false WHERE id = 1;
COMMIT;