Pular para o conteúdo
Vamos conversar

ATLAS / POSTGRESQL E CONCORRÊNCIA / FABRÍCIO CASTRO

Duas decisões disputam.
Só uma pode vencer.

Um teste com objetos em memória pode detectar erros de regras, mas não demonstra concorrência real. Este laboratório descreve uma operação SQL condicionada por organização, estado e versão, com um teste Node que abre conexões PostgreSQL de verdade para observar competição, rollback e integridade de auditoria.

01 / UMA INVARIANTE, DOIS ESCRITORES

Ler antes de gravar não reserva a versão lida.

Considere uma solicitação fictícia que aguarda decisão. Dois processos consultam o mesmo status pending e a mesma versão 0. O primeiro tenta aprovar; o segundo, recusar. Uma verificação apenas na memória pode permitir que ambos decidam sobre uma leitura obsoleta, especialmente se o código usa um UPDATE sem a versão esperada.

RISCO / LOST UPDATE

Quem leu primeiro não possui direito de gravar.

A verificação correta deve ocorrer na instrução de escrita e ser avaliada pelo banco. Não basta consultar o status, executar uma promessa JavaScript ou incrementar uma versão local.

Resultado exigido: no máximo uma mudança de estado por versão observada; a tentativa vencida deve reconhecer conflito.

ESCOPO / CASO FICTÍCIO

Pedido, tenant, versão e uma decisão terminal.

Este laboratório não implementa ERP, financeiro, autorização JWT nem a política Strategy/State. Seu objetivo específico é provar que PostgreSQL aplica uma condição de versão com concorrência. A integração com a aplicação exige requisitos próprios.

Banco: esquema isolado com chaves compostas por organização e pedido.

02 / SQL PARAMETRIZADO / COMPARE-AND-SWAP

A decisão depende da versão ainda estar atual.

O comando de referência fica em scripts/atlas/postgresql/conditional-decision.sql. Os parâmetros $1 a $4 representam tenant, pedido, versão esperada e nova decisão. Em um banco real, o identificador do tenant deve vir de contexto autenticado e a decisão deve ter sido autorizada anteriormente pelo domínio.

UPDATE purchase_requests
SET status = $4,
    version = version + 1,
    updated_at = clock_timestamp()
WHERE tenant_id = $1
  AND request_id = $2
  AND version = $3
  AND status = 'pending'
RETURNING tenant_id, request_id, status, version;

A instrução recebe o nome do esquema de teste gerado pelo executor. O SQL de referência não é uma migração de produção e não deve ser executado diretamente sobre bancos empresariais.

  1. 01
    Escopo por organização e identificador

    A chave composta (tenant_id, request_id) permite pedidos com o mesmo ID em diferentes organizações. Tanto a consulta quanto o UPDATE exigem o tenant correto.

  2. 02
    Estado e versão no mesmo WHERE

    Se o pedido já não está pendente ou a versão foi modificada por outra transação, a instrução não atualiza nenhuma linha. O servidor não deve tratar 0 linhas como aprovação.

  3. 03
    Resultado da escrita como evidência

    O teste examina rowCount e RETURNING para distinguir escrita aceita de conflito. Uma transação que retornou uma linha ainda pode falhar e ser revertida antes do commit; resultado local não significa durabilidade definitiva.

03 / TRANSAÇÕES READ COMMITTED

A segunda tentativa aguarda. Depois, reavalia a condição.

Em PostgreSQL, um UPDATE concorrente no mesmo registro pode aguardar o término da transação que já o modificou. Sob READ COMMITTED, depois de um commit concorrente, o PostgreSQL reavalia o WHERE contra a versão atualizada. A versão 0 deixa de corresponder à linha e a segunda atualização retorna zero linhas. O ensaio usa duas conexões de verdade, não um Promise.all sobre Map.

Linha do tempo: T1 e T2 leem a versão zero; T1 grava versão um e mantém lock; T2 espera; após commit de T1 a condição de versão zero falha para T2 e somente uma decisão fica gravada.
Diagrama original de concorrência em READ COMMITTED. Os resultados descritos são a expectativa do ensaio e só devem ser registrados como PASS depois de execução real.
-- T1: BEGIN; UPDATE ... WHERE version = 0;  -- 1 linha, mantém o lock
-- T2: BEGIN; UPDATE ... WHERE version = 0;  -- aguarda T1
-- T1: COMMIT;                               -- publica version = 1
-- T2: reavalia WHERE após a espera            -- 0 linhas
-- T2: ROLLBACK (ou COMMIT sem alteração)
-- Resultado aceitável: 1 decisão; nunca duas sobre a mesma versão.

O teste automatizado utiliza Pool do driver pg, clientes distintos, BEGIN, COMMIT, ROLLBACK, constraints e consultas ao estado final.

04 / ISOLAMENTO POR ORGANIZAÇÃO

Uma chave composta ajuda. Não substitui autorização.

O laboratório cria a chave primária (tenant_id, request_id) e nunca faz um UPDATE apenas por identificador do pedido. Assim, dois tenants podem possuir o mesmo ID sem escrever na linha um do outro. Esse controle SQL é necessário, mas não suficiente para um produto multi-tenant seguro.

TESTE / CROSS-TENANT

Mesmo ID, duas organizações.

O ensaio prepara dois registros distintos e executa decisões independentes. Também tenta usar uma organização inexistente e uma string com aparência de injeção SQL; a operação parametrizada não deve modificar outra organização.

LIMITE / IDENTIDADE E RLS

O banco não adivinha a identidade de quem requisitou.

Um ator que controla o parâmetro de tenant ainda poderia informar outra organização. Em produção, autenticação, contexto confiável e autorização por recurso precisam ser garantidos; Row-Level Security e políticas de acesso exigem desenho e testes separados.

05 / AUDITORIA NA MESMA TRANSAÇÃO

Atualização e registro devem confirmar ou falhar juntos.

O esquema de laboratório possui uma tabela de decisões auditadas, com uma linha por organização, pedido e versão resultante. Após um UPDATE aceito, o teste insere o registro de decisão dentro da mesma transação local. Se o registro falhar por constraint, o ROLLBACK deve desfazer também a mudança de status.

ATOMICIDADE / TESTE NEGATIVO

Falha no registro, nenhuma decisão persistida.

O ensaio tenta gravar um valor de auditoria proibido. A transação precisa falhar e deixar pedido ainda pendente, versão zero e auditoria vazia. Isso observa rollback no PostgreSQL, e não apenas uma exceção JavaScript.

LIMITE / TRANSACTIONAL OUTBOX

Auditoria local não é publicação ao ERP.

Uma outbox é outra responsabilidade: persistir intenção de publicação junto ao agregado, depois entregar com retries e idempotência. Este laboratório não cria worker, broker ou reconciliação; esses requisitos precisam de estudo independente.

06 / MATRIZ DE PROVAS ADVERSARIAIS

Testar o conflito. Não fabricar status verde.

A suíte está em apps/web/tests/integration/atlas-concurrency.postgres.test.mjs. O ambiente executável desta sessão não disponibiliza PostgreSQL, Docker nem o driver instalado; portanto, nenhum caso de banco foi marcado como aprovado nesta entrega.

  1. 01
    Escrita condicionada por versão

    Uma aprovação válida gera uma linha e versão 1; escrita com versão obsoleta precisa resultar em zero linhas.

  2. 02
    Duas transações com a mesma versão

    T1 mantém o lock de atualização enquanto T2 tenta decidir. Depois do commit de T1, T2 não pode gravar o status conflitante e a auditoria deve conter um único efeito.

  3. 03
    Rollback do primeiro escritor

    Se T1 reverte a transação, outra decisão com versão zero pode prosseguir e se tornar a única confirmação final.

  4. 04
    Isolamento por tenant e parâmetros

    Dois pedidos com mesmo ID em organizações diferentes podem ser decididos independentemente; um parâmetro de tenant malicioso não altera outra organização.

  5. 05
    Falha de auditoria aborta a decisão

    A restrição SQL rejeita um evento inválido; após rollback, a linha principal deve permanecer pendente com versão zero.

  6. 06
    Preflight impede ambiente errado

    Host remoto, banco chamado production ou postgres, parâmetros que redirecionam conexão e falta de confirmação precisam falhar antes de conectar ou criar esquema.

07 / EXECUÇÃO REPRODUZÍVEL / SEM PRODUÇÃO

Banco descartável, porta local, evidência com versão.

O executor Docker cria um PostgreSQL 17 temporário, banco com prefixo fc_atlas_test_ e credenciais efêmeras, vincula a porta somente à interface 127.0.0.1 e remove o container ao sair. É preciso ter Docker e dependências já instaladas no projeto; não executa instalação remota nem ignora o lockfile.

# Dentro do repositório, com Docker local e dependências instaladas:
bash scripts/atlas/postgresql/run-local-docker.sh

# Alternativa: PostgreSQL local já descartável e conhecido:
ATLAS_PG_TEST_URL="postgresql://atlas_lab:...@127.0.0.1:5432/fc_atlas_test_local" \
ATLAS_PG_TEST_CONFIRM=I_UNDERSTAND_DISPOSABLE_DATABASE \
pnpm --filter fanpage-frontend test:db:concurrency

# Sem banco: somente contratos estáticos e preflight, NÃO a corrida SQL:
pnpm --filter fanpage-frontend test:db:preflight
pnpm --filter fanpage-frontend test:db:contract

Executar a partir da raiz do repositório. O script de banco exige autorização explícita e confere current_database() antes de criar um esquema aleatório, que será eliminado na finalização. Se o PostgreSQL não estiver disponível, a suíte falha em vez de produzir PASS fictício.

RESULTADO SÓ DEPOIS DA EXECUÇÃO

Registrar comando, versão, SHA e contagem.

Uma prova reproduzível precisa incluir versão do servidor, identificação segura do banco, casos executados, falhas, logs minimizados, commit de origem e limpeza do esquema. O documento de evidência desta implementação está marcado como NOT_RUN até isso acontecer.

NO DATABASENão executar testes reais e não fingir conclusão.

HOSTSomente localhost, 127.0.0.1 ou ::1.

SCHEMANome aleatório, próprio do ensaio; sem tabelas do ERP.

FAILURERollback e limpeza em finally.

PASSSomente saída real do PostgreSQL no SHA validado.

08 / REFERÊNCIAS, ESCOLHAS E LIMITES

Optimistic locking é uma escolha. O comportamento real é do banco.

Compare-and-swap é adequado quando a operação pode falhar por versão e o caso de uso consegue comunicar conflito ao usuário. Locks pessimistas como SELECT FOR UPDATE, níveis mais fortes de isolamento e transações serializáveis podem atender a cenários diferentes, com custos e semânticas próprios. A correção deve ser demonstrada sob carga representativa e regras reais.

FONTE PRIMÁRIA / POSTGRESQL 17

Transaction Isolation.

Explica o comportamento de READ COMMITTED, espera por transação concorrente e reavaliação do predicado de UPDATE após o commit do primeiro escritor.

Ver documentação oficial

FONTE PRIMÁRIA / POSTGRESQL 17

UPDATE e RETURNING.

O comando comunica quantas linhas foram modificadas e permite retornar a versão resultante. Zero linhas não é erro SQL, mas pode significar conflito de domínio.

Consultar UPDATE

FONTE PRIMÁRIA / LOCKS

Row-Level Locking.

Escritores concorrentes podem aguardar lock de uma linha; o comportamento de espera e liberação precisa ser tratado com timeouts e respostas de erro apropriadas.

Consultar locking

CONEXÃO / ARQUITETURA

O teste SQL complementa o desenho do domínio.

Compare o ensaio com as responsabilidades de negócio, versionamento, políticas de aprovação e requisitos de falha já documentados na arquitetura do Atlas.

Estudar decisões de arquitetura

Laboratório educacional fictício: scripts e testes registrados em branch. A criação desses arquivos NÃO constitui execução de PostgreSQL, prova de segurança multi-tenant, teste de RLS, deploy, integração ERP ou certificação operacional. A revisão de código, a execução real e a homologação continuam pendentes.

Continue explorando.Engenharia de software ↗
Vamos conversar?

Explore o portfólio

26 páginas encontradas · Esc para fechar