Pessimistic Locking no Node.js bloqueia registros durante uma transação para impedir que outras operações os alterem ao mesmo tempo. No PostgreSQL, comandos como SELECT ... FOR UPDATE são usados quando o conflito é provável e a regra precisa de exclusividade imediata.
O padrão é útil para estoque escasso, filas no banco, alocação de recursos e saldos concorridos. Porém, locks aumentam contenção e podem causar deadlocks. A transação deve ser curta e previsível.
Lock de linha
await transaction(pool, async client => {
const result = await client.query(
`SELECT id, quantidade
FROM estoque
WHERE sku = $1
FOR UPDATE`,
[sku]
);
const item = result.rows[0];
if (!item || item.quantidade < quantidade) {
throw new Error('Estoque insuficiente');
}
await client.query(
`UPDATE estoque
SET quantidade = quantidade - $1
WHERE sku = $2`,
[quantidade, sku]
);
});A linha fica bloqueada até commit ou rollback.
FOR NO KEY UPDATE
É uma forma mais fraca que FOR UPDATE e pode permitir mais concorrência quando chaves não serão alteradas. Escolha o modo conforme o conflito real.
FOR SHARE e FOR KEY SHARE
Esses modos protegem leituras contra determinadas alterações. São úteis em relacionamentos e validações de referências, mas exigem entendimento das compatibilidades de lock.
NOWAIT
SELECT * FROM recursos
WHERE id = $1
FOR UPDATE NOWAIT;NOWAIT falha imediatamente se a linha estiver bloqueada. É adequado quando esperar não faz sentido.
SKIP LOCKED
SELECT id, payload
FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 20
FOR UPDATE SKIP LOCKED;Vários workers podem selecionar trabalhos diferentes sem aguardar os mesmos registros.
Fila baseada em banco
await transaction(pool, async client => {
const jobs = await client.query(`
SELECT id, payload
FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 10
FOR UPDATE SKIP LOCKED
`);
const ids = jobs.rows.map(job => job.id);
await client.query(
`UPDATE jobs
SET status = 'processing', started_at = now()
WHERE id = ANY($1::uuid[])`,
[ids]
);
});O processamento longo deve ocorrer depois do commit. O lock serve apenas para reservar o lote.
Lock timeout
SET LOCAL lock_timeout = '2s';Um timeout evita espera indefinida. Trate o erro como contenção, não como falha desconhecida.
Deadlocks
Dois processos podem travar recursos em ordem inversa. PostgreSQL detecta o ciclo e aborta uma transação.
Reduza deadlocks:
- trave recursos sempre na mesma ordem;
- mantenha transações curtas;
- use índices para não bloquear linhas desnecessárias;
- evite chamadas externas dentro da transação;
- implemente retry limitado.
Ordem determinística
const accountIds = [originId, destinationId].sort();
for (const id of accountIds) {
await client.query(
'SELECT id FROM contas WHERE id = $1 FOR UPDATE',
[id]
);
}Lock e pool
Todas as queries precisam usar a mesma conexão. Se um repository pegar outra conexão do pool, o lock não protege a operação esperada.
Transação curta
Não mantenha lock enquanto chama gateway, espera fila ou realiza trabalho de CPU. Reserve, atualize e confirme rapidamente. Efeitos externos podem ser coordenados com Outbox.
Pessimista ou otimista?
Use locking pessimista quando conflitos são frequentes e esperar é aceitável. Use otimista quando conflitos são raros e operações podem ser refeitas.
Leia Optimistic Locking no Node.js.
Advisory Locks
PostgreSQL oferece advisory locks para recursos que não correspondem diretamente a uma linha. Eles exigem disciplina, porque o banco não conhece a regra protegida.
SELECT pg_try_advisory_xact_lock($1);Prefira locks transacionais para liberação automática no fim da transação.
Índices
Uma query sem índice pode examinar e bloquear mais linhas, aumentar duração e causar contenção. Analise o plano de execução.
Observabilidade
Monitore espera por locks, transações longas, deadlocks, lock timeouts, conexões ocupadas e rotas com maior contenção.
Testes concorrentes
- dois workers disputando a mesma linha;
NOWAITfalhando;SKIP LOCKEDdistribuindo jobs;- deadlock e retry;
- rollback liberando lock;
- timeout;
- ordem estável de múltiplos recursos.
Erros comuns
- lock fora de transação;
- queries em conexões diferentes;
- trabalho externo com lock aberto;
- sem timeout;
- ordem de locks inconsistente;
- usar
SKIP LOCKEDonde todos os registros precisam ser vistos; - não medir contenção;
- bloquear mais linhas por falta de índice.
Conclusão
Pessimistic Locking no Node.js oferece exclusividade imediata para regras concorridas. Ele funciona melhor com transações curtas, ordem determinística e timeouts.
Use o lock mais fraco que preserve a regra e evite chamadas externas durante a transação. Consulte Transações PostgreSQL no Node.js.



