Advisory Locks no Node.js usam locks cooperativos do PostgreSQL para coordenar tarefas que não correspondem diretamente a uma linha específica. Eles são úteis para impedir dois workers de executar o mesmo job, serializar uma operação administrativa, proteger um recurso lógico ou eleger temporariamente uma instância responsável.
O PostgreSQL não associa significado ao número usado como chave. A aplicação define o protocolo: qual recurso a chave representa, quando o lock é adquirido, quanto tempo pode durar e como falhas são tratadas. Todos os participantes precisam respeitar a mesma convenção.
Locks de sessão e de transação
Existem duas famílias principais:
- sessão: permanece até ser liberado ou a conexão terminar;
- transação: é liberado automaticamente no commit ou rollback.
Em aplicações com pool, locks de transação costumam ser mais seguros porque o ciclo de vida acompanha a operação.
Primeiro exemplo transacional
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query(
'SELECT pg_advisory_xact_lock($1)',
[recursoIdNumerico]
);
await executarOperacaoCritica(client);
await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}Use a mesma conexão do início ao fim. Adquirir o lock em uma conexão e executar a operação em outra não protege a seção crítica.
Tentativa sem espera
const result = await client.query(
'SELECT pg_try_advisory_xact_lock($1) AS acquired',
[lockKey]
);
if (!result.rows[0].acquired) {
return { status: 'busy' };
}A função pg_try_advisory_xact_lock retorna imediatamente. Ela é apropriada para jobs que podem ser ignorados ou reagendados quando outro worker já executa a tarefa.
Gerando uma chave estável
A API aceita um inteiro de 64 bits ou dois inteiros de 32 bits. Não use hashCode instável ou valores dependentes do processo. A mesma entidade precisa produzir a mesma chave em todas as instâncias.
import { createHash } from 'node:crypto';
function advisoryKey(namespace, resourceId) {
const digest = createHash('sha256')
.update(`${namespace}:${resourceId}`)
.digest();
return digest.readBigInt64BE(0);
}O driver precisa enviar BigInt no formato esperado. Outra opção é dividir a chave em dois inteiros de 32 bits e manter um namespace explícito.
Namespace e colisões
Use namespaces para separar operações.
const namespaces = {
billingClose: 10,
tenantMigration: 20,
reportGeneration: 30
};
await client.query(
'SELECT pg_try_advisory_xact_lock($1, $2) AS acquired',
[namespaces.reportGeneration, tenantNumericId]
);Chaves de 32 bits facilitam leitura, mas o identificador precisa caber no intervalo. UUIDs exigem transformação controlada ou tabela de mapeamento.
Job agendado único
async function executarFechamentoDiario(pool, dateKey) {
const client = await pool.connect();
try {
await client.query('BEGIN');
const lock = await client.query(
'SELECT pg_try_advisory_xact_lock($1, $2) acquired',
[100, dateKey]
);
if (!lock.rows[0].acquired) {
await client.query('ROLLBACK');
return { skipped: true };
}
await fecharDia(client, dateKey);
await client.query('COMMIT');
return { completed: true };
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}O lock evita execução simultânea, mas não prova que o job já foi concluído. Mantenha também um registro idempotente de execução.
Lock não substitui idempotência
Uma conexão pode cair depois de produzir um efeito externo e antes de registrar o resultado. O lock será liberado, outro worker poderá repetir a tarefa e o efeito será duplicado. Use chaves idempotentes, constraints e estados persistidos.
Consulte Idempotency Keys no Node.js e Consumer Idempotente no Node.js.
Locks de sessão em pools
const client = await pool.connect();
try {
await client.query('SELECT pg_advisory_lock($1)', [key]);
await trabalhoLongo(client);
} finally {
try {
await client.query('SELECT pg_advisory_unlock($1)', [key]);
} finally {
client.release();
}
}Se o código liberar a conexão para o pool sem desbloquear, outra requisição pode herdar o lock. Por isso, evite locks de sessão quando um lock transacional resolve o problema.
Timeout
Um lock bloqueante pode esperar indefinidamente. Configure lock_timeout localmente.
await client.query('BEGIN');
await client.query("SET LOCAL lock_timeout = '3s'");
await client.query('SELECT pg_advisory_xact_lock($1)', [key]);Trate timeout como conflito recuperável ou falha controlada. Não faça retry rápido sem jitter, pois várias instâncias podem continuar colidindo.
Deadlocks
Advisory locks participam da detecção de deadlock. Se uma operação precisa de várias chaves, adquira sempre em ordem determinística.
const keys = [...resourceKeys].sort((a, b) => a < b ? -1 : 1);
for (const key of keys) {
await client.query('SELECT pg_advisory_xact_lock($1)', [key]);
}Mantenha a seção crítica curta. Não aguarde APIs externas ou interação humana enquanto segura locks de banco.
Advisory lock versus row lock
Use SELECT ... FOR UPDATE quando a coordenação corresponde a uma linha existente. O lock de linha é visível no modelo e protege atualizações concorrentes.
Use advisory lock quando:
- o recurso lógico não possui linha;
- a operação envolve várias tabelas;
- um job global precisa de exclusão mútua;
- uma migration por tenant deve rodar uma vez;
- é necessária uma eleição simples.
Veja Pessimistic Locking no Node.js.
Eleição simples de líder
Uma instância pode manter um lock de sessão e executar uma tarefa enquanto a conexão estiver ativa. Se o processo morrer, o PostgreSQL libera o lock.
async function tentarLideranca(client) {
const result = await client.query(
'SELECT pg_try_advisory_lock($1) AS leader',
[9001]
);
return result.rows[0].leader;
}Essa técnica é simples, mas depende da saúde da conexão, não necessariamente da saúde completa da aplicação. Para coordenação crítica, avalie sistemas de consenso e leases explícitos.
PgBouncer
Em transaction pooling, locks de sessão são incompatíveis com a expectativa de manter a mesma sessão. Locks transacionais são mais adequados. Confirme a arquitetura antes de implantar.
Consulte PgBouncer com Node.js.
Multi-tenancy
Inclua o tenant na chave para não bloquear organizações independentes.
const key = advisoryKey('invoice-close', `${tenantId}:${period}`);Não use um lock global quando a operação pode ser paralela por tenant. Veja Multi-Tenancy no Node.js.
Observabilidade
Registre namespace, recurso, tempo de espera, duração da seção crítica e resultado. Não exponha IDs sensíveis. Monitore conexões aguardando locks e operações que mantêm locks por muito tempo.
Consultas em pg_locks ajudam no diagnóstico, mas exigem interpretação cuidadosa.
SELECT pid, locktype, granted, objid, classid
FROM pg_locks
WHERE locktype = 'advisory';Testes concorrentes
Mocks não demonstram exclusão mútua. Execute testes de integração com duas conexões reais:
- a primeira adquire o lock;
- a segunda tenta adquirir;
- confirme bloqueio ou retorno falso;
- faça commit ou rollback;
- confirme que a segunda consegue continuar.
Teste também queda de conexão, timeout e aquisição em ordem diferente.
Erros comuns
- usar conexão diferente na seção crítica;
- liberar conexão com lock de sessão ativo;
- não configurar timeout;
- segurar lock durante chamada externa;
- usar chave instável;
- não separar namespaces;
- achar que lock garante exactly-once;
- adquirir múltiplos locks sem ordem;
- ignorar PgBouncer.
Checklist
- protocolo documentado;
- chave determinística;
- namespace definido;
- lock transacional preferido;
- mesma conexão usada;
- timeout configurado;
- seção crítica curta;
- idempotência persistida;
- métricas de espera;
- testes com concorrência real.
Conclusão
Advisory Locks no Node.js são uma ferramenta leve para coordenação distribuída quando a aplicação já depende do PostgreSQL. Eles funcionam bem para jobs únicos, operações por recurso e exclusão mútua de curta duração.
Prefira locks transacionais, use chaves estáveis e mantenha idempotência independente. Com timeout, observabilidade e testes concorrentes, advisory locks resolvem muitos problemas de coordenação sem adicionar outro componente à infraestrutura.
Consulte a documentação oficial de Advisory Locks do PostgreSQL e a documentação de transações do node-postgres.


