Os Advisory Locks no Node.js permitem coordenar operações usando bloqueios definidos pela própria aplicação. Diferentemente de SELECT FOR UPDATE, o PostgreSQL não associa automaticamente o lock a uma linha ou tabela. A aplicação escolhe uma chave numérica que representa um recurso, tarefa, tenant, relatório ou processo.
Esse recurso é útil para impedir duas migrations simultâneas, garantir que apenas um worker execute uma rotina, serializar processamento por cliente e proteger tarefas que envolvem várias tabelas. Porém, advisory locks são cooperativos: apenas o código que também tenta adquirir o mesmo lock respeita a exclusão.
Neste guia, você aprenderá locks de sessão e de transação, funções pg_advisory_lock, pg_try_advisory_lock, chaves, hashing, timeouts, pools, deadlocks, observabilidade e testes.
O que são Advisory Locks?
Advisory locks são bloqueios consultivos mantidos pelo PostgreSQL. A documentação oficial de Advisory Locks descreve comportamento, funções e limites. A documentação das funções administrativas lista as variações disponíveis.
Para bloqueios de linha, consulte Lock Pessimista no Node.js. Para transações, veja Transações PostgreSQL no Node.js.
Lock de sessão
SELECT pg_advisory_lock($1);O lock permanece até ser liberado explicitamente ou até a conexão terminar.
Liberando lock de sessão
SELECT pg_advisory_unlock($1);Use finally. Se a conexão voltar ao pool com o lock ativo, outra requisição pode herdar uma sessão bloqueada.
Exemplo com node-postgres
async function withAdvisoryLock(pool, key, callback) {
const client = await pool.connect();
try {
await client.query(
'SELECT pg_advisory_lock($1)',
[key]
);
return await callback(client);
} finally {
try {
await client.query(
'SELECT pg_advisory_unlock($1)',
[key]
);
} finally {
client.release();
}
}
}A aquisição e liberação precisam usar a mesma conexão.
Lock de transação
SELECT pg_advisory_xact_lock($1);Esse lock é liberado automaticamente no commit ou rollback. Ele reduz o risco de esquecer unlock.
Exemplo transacional
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query(
'SELECT pg_advisory_xact_lock($1)',
[lockKey]
);
await updateRelatedTables(client, input);
await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}O lock acompanha a transação e não exige unlock manual.
Lock bloqueante
pg_advisory_lock e pg_advisory_xact_lock aguardam até o recurso ficar disponível. Sem timeout, a requisição pode esperar indefinidamente.
Try lock
SELECT pg_try_advisory_lock($1) AS acquired;A função retorna true ou false imediatamente.
Exemplo sem espera
const result = await client.query(
'SELECT pg_try_advisory_lock($1) AS acquired',
[key]
);
if (!result.rows[0].acquired) {
throw new ResourceBusyError();
}Try lock transacional
SELECT pg_try_advisory_xact_lock($1) AS acquired;Essa versão não bloqueia e libera automaticamente no final da transação.
Chave bigint
Uma função aceita um inteiro assinado de 64 bits. Outra variação aceita dois inteiros de 32 bits:
SELECT pg_advisory_lock($1, $2);O primeiro valor pode representar o tipo do recurso e o segundo, o ID.
Namespace de chaves
const LOCK_NAMESPACES = {
MIGRATION: 1,
TENANT_JOB: 2,
REPORT: 3,
RECONCILIATION: 4
};Use namespaces para evitar colisões acidentais entre funções diferentes.
Chave por tenant
SELECT pg_advisory_xact_lock($1, $2);O namespace pode ser 2 e o segundo argumento o tenant ID, permitindo apenas uma operação crítica por organização.
IDs acima de 32 bits
Quando o identificador não cabe em integer, use bigint ou gere uma chave estável por hash.
Hash de string
function lockKeyFromString(value) {
const digest = createHash('sha256')
.update(value)
.digest();
return digest.readBigInt64BE(0);
}O driver precisa enviar bigint de forma compatível. Você também pode gerar dois inteiros de 32 bits.
Colisões
Reduzir hash para 64 bits torna colisões improváveis, mas não impossíveis. Para recursos críticos, prefira IDs numéricos e namespace explícito.
Migration única
SELECT pg_advisory_lock(918273645);
O pipeline adquire o lock antes de consultar a tabela de migrations. Outra execução aguarda ou falha.
Consulte Migrações de Banco no Node.js.
Job único
Um cron pode executar em várias réplicas. Use try lock:
const acquired = await tryLock(client, DAILY_REPORT_KEY);
if (!acquired) {
logger.info('Outro processo já executa o relatório');
return;
}
await generateDailyReport();Para lock de sessão, mantenha a conexão durante todo o job e libere no finally.
Custo de manter conexão
Um job longo com lock de sessão ocupa uma conexão do pool. Planeje capacidade e não use a mesma conexão para trabalho não relacionado.
Lock por usuário
Você pode serializar uma importação por usuário, mas não use dados fornecidos diretamente sem normalização e namespace.
Lock por recurso composto
Combine tenant e tipo de operação em uma chave estável. A regra precisa ser idêntica em todos os serviços.
Cooperação obrigatória
Advisory lock não impede um UPDATE comum. Se outro código altera o recurso sem adquirir o mesmo lock, a proteção não existe.
Constraints continuam essenciais
Use UNIQUE, CHECK e FOREIGN KEY como última linha de defesa. Advisory locks coordenam fluxo, não validam o banco.
Lock de sessão e pool
A conexão não pode ser liberada antes do unlock. O pool reutiliza sessões, portanto um lock esquecido afeta outras operações.
Veja Pool PostgreSQL no Node.js.
Conexão quebrada
Se a conexão encerra, locks de sessão são liberados pelo servidor. Isso evita lock permanente, mas o job pode ter produzido efeito parcial.
Heartbeats
O lock indica que a conexão existe, não que o processo progride. Jobs longos precisam de métricas, progresso e timeout.
Timeout com statement_timeout
SET LOCAL statement_timeout = '2s';Em uma transação, a aquisição bloqueante falha após o prazo.
lock_timeout
SET LOCAL lock_timeout = '2s';Verifique como a versão do PostgreSQL aplica o parâmetro a advisory locks e teste o comportamento real.
Timeout no cliente
Abortar apenas a Promise local não garante cancelamento da query. Use mecanismo de cancelamento suportado pelo driver e faça cleanup da conexão.
Deadlocks
Advisory locks participam da detecção de deadlock. Duas operações que adquirem chaves em ordem oposta podem gerar SQLSTATE 40P01.
Ordem consistente
const keys = [firstKey, secondKey]
.sort((a, b) => a < b ? -1 : 1);
for (const key of keys) {
await client.query(
'SELECT pg_advisory_xact_lock($1)',
[key]
);
}Não adquira locks demais
Cada lock consome memória compartilhada. Não crie milhões de locks simultâneos nem um lock por item de grande lote.
Lock global
Uma única chave global é simples, mas limita toda concorrência. Prefira granularidade por tenant, recurso ou partição.
Granularidade fina
Locks muito finos melhoram throughput, porém aumentam complexidade e risco de adquirir múltiplas chaves em ordem errada.
Advisory lock versus Redis lock
- PostgreSQL: ideal quando o efeito principal está no banco e a conexão é confiável.
- Redis: útil entre sistemas sem banco compartilhado, mas exige desenho cuidadoso de TTL, ownership e falhas.
Evite coordenar no Redis e confirmar no PostgreSQL como se fosse uma transação distribuída.
Advisory lock versus fila
Uma fila organiza trabalho e retries. Um advisory lock apenas impede concorrência. Jobs que precisam persistência, atraso e redelivery se beneficiam de uma fila real.
Advisory lock versus idempotência
O lock impede duas execuções simultâneas, mas não impede uma repetição depois da liberação. Combine com idempotência para operações únicas.
Consulte Idempotência em APIs Node.js.
Advisory lock e outbox
Um worker de outbox pode usar SKIP LOCKED ou advisory lock por partição. Prefira mecanismos que permitem paralelismo controlado.
Multi-tenant
Use namespace e tenant ID. Não use um lock global para todos os clientes quando as operações são independentes.
Autorização
O lock não concede permissão. Valide o usuário antes de adquirir e não exponha informação sobre recursos de outros tenants.
Observabilidade
Registre:
- nome lógico do lock;
- namespace;
- tempo de espera;
- aquisição ou falha;
- duração;
- unlock;
- deadlock;
- timeout.
Não registre chaves derivadas de dados pessoais em texto claro.
Métricas
Crie counters para sucesso, falha e timeout, além de histogram de espera. Consulte Métricas Prometheus no Node.js.
Consultando locks
pg_locks mostra advisory locks. Filtre locktype = 'advisory' e associe ao PID em pg_stat_activity.
SELECT l.pid,
l.granted,
a.state,
a.query,
a.xact_start
FROM pg_locks l
JOIN pg_stat_activity a ON a.pid = l.pid
WHERE l.locktype = 'advisory';Runbook
Defina como identificar o processo, confirmar que está travado e encerrar a sessão com segurança. Não mate sessões apenas porque um lock existe.
Testes
Use duas conexões reais. A primeira adquire o lock e a segunda tenta o mesmo.
Teste try lock
test('segunda conexão não adquire', async () => {
const first = await pool.connect();
const second = await pool.connect();
try {
await first.query(
'SELECT pg_advisory_lock($1)',
[123]
);
const result = await second.query(
'SELECT pg_try_advisory_lock($1) AS acquired',
[123]
);
assert.equal(result.rows[0].acquired, false);
} finally {
await first.query(
'SELECT pg_advisory_unlock($1)',
[123]
);
first.release();
second.release();
}
});Teste de liberação automática
Adquira pg_advisory_xact_lock, faça rollback e confirme que outra conexão consegue adquirir.
Teste de conexão encerrada
Adquira lock de sessão, encerre a conexão e verifique liberação. Use apenas em ambiente de teste.
Erros comuns
- Liberar client antes do unlock: outra requisição herda o lock.
- Chave sem namespace: operações diferentes colidem.
- Esperar proteção automática: código que não coopera ignora o lock.
- Lock global: throughput cai.
- Sem timeout: jobs aguardam indefinidamente.
- Ordem variável: deadlocks aumentam.
- Usar lock como idempotência: repetições posteriores continuam possíveis.
Boas práticas
- Prefira lock transacional quando possível.
- Use a mesma conexão.
- Defina namespaces.
- Escolha chave estável.
- Use try lock em jobs que podem ser ignorados.
- Defina timeout.
- Adquira múltiplos locks em ordem.
- Mantenha constraints.
- Monitore espera e duração.
- Teste com conexões reais.
Conclusão
Os Advisory Locks no Node.js oferecem uma forma flexível de coordenar operações que não correspondem diretamente a uma única linha. Eles funcionam bem para migrations, cron jobs, processamento por tenant e tarefas exclusivas.
A flexibilidade exige disciplina: chave estável, namespace, mesma conexão, timeout e liberação garantida. Com locks transacionais, métricas e constraints, a aplicação coordena trabalho concorrente sem depender de um serviço externo apenas para exclusão mútua.



