ChaosSQL

Seus testes passam. Seus saldos não fecham.

O ChaosSQL força as corridas entre transações no PostgreSQL, MySQL e SQLite e entrega o menor teste que reproduz o bug.

banking_lost_updateseed 42 · READ_UNCOMMITTED
  1. T1: BEGIN
  2. T2: BEGIN
  3. T1: SELECT balance FROM accounts WHERE id = 1;
  4. T2: SELECT balance FROM accounts WHERE id = 1;
  5. T1: UPDATE accounts SET balance = 981 WHERE id = 1;
  6. T1: INSERT INTO ledger (account_id, amount, type) VALUES (1, 19, 'DEBIT');
  7. T1: COMMIT
  8. T2: UPDATE accounts SET balance = 989 WHERE id = 1;
  9. T2: INSERT INTO ledger (account_id, amount, type) VALUES (1, 11, 'DEBIT');
  10. T2: COMMIT
Saldo gravado
$989
Esperado
$970
Invariante
falha: $989 ≠ $970
  • Funciona com PostgreSQL, MySQL e SQLite
  • Open source, licença MIT
  • Um binário Go estático, sem CGO
  • GitHub

Um lost update em quatro passos

Esta é uma execução real do exemplo bancário, gravada com a seed 42. Nada nesta página é simulado.

  1. Dois saques leem o mesmo saldo

    As duas transações leem $1,000 antes de qualquer uma escrever. Cada uma, sozinha, está correta.

  2. Cada uma grava o seu resultado

    A primeira debita $19 e salva $981. A segunda debita $11 e salva $989.

  3. A segunda escrita apaga a primeira

    A conta termina em $989 em vez de $970. Um débito de $19 sumiu e nenhum erro chega aos logs.

  4. O ChaosSQL prova e entrega o teste

    A invariante falha, o motor repete a ordem exata e o delta debugging mantém só as 2 transações que causam o bug, em repro_test.go.

banking_lost_updateseed 42 · READ_UNCOMMITTED
  1. T1: BEGIN
  2. T2: BEGIN
  3. T1: SELECT balance FROM accounts WHERE id = 1;
  4. T2: SELECT balance FROM accounts WHERE id = 1;
Saldo gravado
$1,000
Esperado
$970
Invariante
verificada quando as duas fazem commit

repro_test.goDe 20 para 2 transações

Termos usados aqui

Invariante
Uma regra que seus dados precisam obedecer sempre, escrita como consulta SQL. Por exemplo: o saldo é igual ao saldo inicial menos todos os débitos.
Nível de isolamento
Quanto uma transação enxerga de outra que ainda está rodando. Níveis mais fracos são mais rápidos e permitem mais bugs como este.
Seed
Um número que fixa a ordem de todas as operações, para que a mesma seed repita a mesma execução.

Não acontece só em banco

Cada exemplo vem com o schema, a invariante e uma execução com falha gravada.

Lost update

Dois saques ao mesmo tempo e um deles some do saldo.

A regra que quebra

O saldo é sempre o saldo inicial menos todos os débitos registrados.

Abrir no playground
Trace mínimo com falha
T1  BEGIN
T2  BEGIN
T1  SELECT balance FROM accounts WHERE id = 1;
T2  SELECT balance FROM accounts WHERE id = 1;
T1  UPDATE accounts SET balance = 981 WHERE id = 1;
T1  INSERT INTO ledger (account_id, amount, type) VALUES (1, 19, 'DEBIT');
T1  COMMIT
T2  UPDATE accounts SET balance = 989 WHERE id = 1;
T2  INSERT INTO ledger (account_id, amount, type) VALUES (1, 11, 'DEBIT');
T2  COMMIT
Gravado com a seed 42 a partir de examples/banking_lost_update/chaos.yaml

Ver todos os cenários

De 20 transações para as 2 que importam

Uma execução com falha tem muito ruído. O delta debugging remove transações até não dar para tirar mais nada sem o bug sumir, e confirma o resultado no banco.

Cada quadrado é uma transação da execução com falha. O delta debugging mantém as 2 destacadas e prova que nada mais é necessário.

menor
90%
repetições para provar
7
para reduzir e verificar
< 1 s

Números da execução bancária gravada. Nossa suíte de avaliação exige pelo menos 85% de redução em todo trace com falha. Como medimos

A mesma execução no relatório HTML, exportado com --export-html.
A mesma execução no relatório HTML, exportado com --export-html.

Rode em todo pull request

A GitHub Action executa seus cenários, gera um relatório JUnit e um resumo do job e anexa a reprodução.

Uma violação reprova o job: torne-o um check obrigatório e o bug não entra. Com o ChaosSQL Cloud você pode reprovar só regressões: ele compara cada pull request com a main e comenta o trace mínimo.

.github/workflows/concurrency.yml
name: Concurrency Guard
on:
  pull_request:
    branches: [main]

permissions:
  contents: read
  pull-requests: write

jobs:
  concurrency:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: bregaldahq/chaossql@v1.6.0
        with:
          spec-path: chaos/transfers.yaml
          seed: '42'
          export-junit: chaossql-junit.xml
          export-repro: 'true'
          # Optional, ChaosSQL Cloud: baseline against main and PR comments
          cloud-token: ${{ secrets.CHAOSSQL_CLOUD_TOKEN }}

Comece de graça. Pague quando proteger produção.

Open source

Grátis

O motor completo, todos os exportadores e a GitHub Action. Licença MIT.

Cloud

A partir de $39 por mês

Histórico, baseline contra a main, comentários em pull requests e alertas. Em acesso antecipado.

Entrar na lista do Cloud

Auditoria de concorrência

$1,490, uma semana

Mapeamos suas cinco transações mais críticas, exploramos mais de 100.000 escalonamentos e entregamos os testes que falham com as correções.

Agendar auditoria

Comparar planos

O que engenheiros perguntam primeiro

Ele roda contra meu banco de produção?

Não, e não deve. Toda execução recria o schema e os dados iniciais, então aponte para um banco descartável: um container de serviço no CI ou uma cópia local.

Quais bancos são suportados?

PostgreSQL, MySQL e SQLite, além de um driver em memória para o playground no navegador. O SQLite serializa escritas no nível padrão, então a maioria das anomalias aparece no PostgreSQL, no MySQL ou em níveis de isolamento mais fracos.

Qual a diferença para um teste de carga?

Um teste de carga mede throughput e torce para uma corrida acontecer. O ChaosSQL escolhe o intercalamento a partir de uma seed, verifica uma regra de negócio ao fim de cada execução e repete a ordem exata que quebrou.

É determinístico de verdade?

A mesma especificação com a mesma seed sempre gera o mesmo escalonamento. O banco ainda decide parte do timing sozinho, e o ChaosSQL reporta essa diferença em vez de escondê-la.

Uma violação vai quebrar meu build?

Sim. O chaossql run retorna 1 em uma violação, depois de gravar o relatório JUnit e o resumo do job, e a GitHub Action reprova o step. Use fail-on: regression com o ChaosSQL Cloud para reprovar só problemas novos, ou fail-on: never para apenas reportar.

O que preciso instalar?

Só o binário: o go install entrega um único programa Go estático, sem CGO. Os SDKs de Python e TypeScript chamam o mesmo binário.

O que a auditoria inclui?

Um kickoff de 45 minutos, as cinco transações mais críticas mapeadas, mais de 100.000 escalonamentos explorados, testes de reprodução em Go, um relatório final apresentado ao time e três meses de Cloud Team.

Encontre a corrida antes dos seus clientes.

Instale a CLI com um comando, ou agende uma auditoria antes do próximo lançamento.

shell
go install github.com/bregaldahq/chaossql/cmd/chaossql@latest

Entre na lista do Cloud

Avisamos quando o seu convite estiver pronto. Sem spam, sem cartão.