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

Advisory Locks no Node.js

Atualizado em: 25 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

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.

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