Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Optimistic Locking no Node.js: detecte conflitos

Atualizado em: 17 de outubro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Optimistic Locking no Node.js detecta alterações concorrentes sem manter locks de banco durante todo o tempo. Cada registro possui uma versão. A atualização só é aceita se a versão ainda for a esperada.

O padrão funciona bem quando conflitos são raros e operações precisam permanecer rápidas. Em vez de bloquear outros usuários, ele rejeita uma gravação obsoleta e permite recarregar, repetir ou pedir decisão ao usuário.

O problema da atualização perdida

Duas requisições leem o mesmo pedido com versão 4. A primeira altera o endereço e salva. A segunda altera o status usando o snapshot antigo e sobrescreve a mudança anterior.

Campo de versão

ALTER TABLE pedidos
ADD COLUMN version integer NOT NULL DEFAULT 1;

Update condicional

const result = await client.query(
  `UPDATE pedidos
   SET status = $1,
       version = version + 1,
       updated_at = now()
   WHERE id = $2 AND version = $3
   RETURNING *`,
  [novoStatus, pedidoId, expectedVersion]
);

if (result.rowCount === 0) {
  throw new ConcurrencyError('Pedido alterado por outra operação');
}

A condição evita sobrescrever um registro mais novo.

Entidade com versão

export class Pedido {
  constructor({ id, status, version }) {
    this.id = id;
    this.status = status;
    this.version = version;
  }

  confirmar() {
    if (this.status !== 'rascunho') throw new Error('Estado inválido');
    this.status = 'confirmado';
  }
}

O repository usa version durante a persistência e atualiza o objeto após sucesso.

ETag em APIs HTTP

Exponha a versão como ETag.

res.setHeader('ETag', `"pedido-${pedido.version}"`);

O cliente envia If-Match no update. Se a versão divergir, retorne 412 Precondition Failed ou 409 Conflict, conforme o contrato.

Veja ETag no Node.js.

Retry automático

Retry é seguro apenas quando a operação pode ser refeita sobre o estado novo. Um incremento ou decisão dependente do snapshot precisa ser recalculado.

for (let attempt = 0; attempt < 3; attempt++) {
  const pedido = await repo.obter(id);
  pedido.confirmar();
  try {
    return await repo.salvar(pedido);
  } catch (error) {
    if (!(error instanceof ConcurrencyError) || attempt === 2) throw error;
  }
}

Merge

Em interfaces de edição, talvez seja possível mesclar campos independentes. Não faça merge silencioso de regras sensíveis. Mostre diferenças ao usuário quando necessário.

Versão por aggregate

A versão deve proteger a unidade de consistência. Se itens pertencem ao aggregate Pedido, atualizações internas devem incrementar a mesma versão.

Timestamp como versão

updated_at pode perder precisão ou depender de relógio. Um inteiro monotônico é mais claro. UUIDs ou tokens opacos também funcionam quando usados corretamente.

Event Sourcing

Event stores usam a versão esperada do aggregate para impedir dois eventos na mesma posição.

UNIQUE (aggregate_id, aggregate_version)

Leia Event Sourcing no Node.js.

Mensagens fora de ordem

Projeções podem guardar a versão aplicada e ignorar eventos antigos.

UPDATE pedidos_resumo
SET status = $1, version = $2
WHERE id = $3 AND version < $2;

Quando usar

  • conflitos são incomuns;
  • edições duram mais que uma transação;
  • não é aceitável manter lock enquanto o usuário pensa;
  • APIs recebem updates concorrentes;
  • aggregates possuem versão natural.

Quando não basta

Se muitos workers disputam a mesma linha e quase todas as tentativas conflitam, locks pessimistas, serialização ou redesenho de particionamento podem ser melhores.

Observabilidade

Meça conflitos por entidade, rota, tenant e horário. Uma taxa alta pode indicar hot record ou fluxo mal particionado.

Testes

  • update com versão atual;
  • versão obsoleta;
  • duas requisições simultâneas;
  • retry seguro;
  • ETag e If-Match;
  • evento fora de ordem;
  • merge de campos independentes.

Erros comuns

  • ler versão e não usá-la no WHERE;
  • incrementar antes do sucesso;
  • retry sem recarregar;
  • timestamp impreciso;
  • retornar sucesso após rowCount zero;
  • proteger apenas tabela principal e ignorar aggregate;
  • não medir conflitos.

Conclusão

Optimistic Locking no Node.js protege contra atualizações perdidas sem manter locks longos. A versão transforma concorrência silenciosa em conflito explícito.

Use update condicional, erros claros, ETags e retry apenas quando seguro. Combine com Transações PostgreSQL no Node.js e Aggregates no Node.js.

Consulte a documentação RETURNING do PostgreSQL.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita