Redlock no Node.js é um padrão de lock distribuído baseado em Redis usado para coordenar processos que disputam o mesmo recurso. Ele pode impedir que duas instâncias executem simultaneamente uma tarefa crítica, como gerar o mesmo relatório, processar uma reconciliação ou alterar um recurso externo.
Locks distribuídos são mais difíceis que mutexes locais. A rede pode atrasar, processos podem parar, o relógio pode avançar e o lock pode expirar enquanto o trabalho ainda está em execução. Por isso, o lock deve ser tratado como uma concessão temporária, não como garantia absoluta.
Quando usar
Use um lock quando a operação não puder ser resolvida apenas com restrições no banco, idempotência ou filas particionadas. Antes de adicionar Redis, verifique se uma constraint única, optimistic locking ou SELECT FOR UPDATE já resolve o problema com menos partes móveis.
Instalação
npm install redis redlock
import { createClient } from 'redis';
import Redlock from 'redlock';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
const redlock = new Redlock([redis], {
retryCount: 5,
retryDelay: 200,
retryJitter: 100,
automaticExtensionThreshold: 500
});Em implantações que exigem o algoritmo completo, use múltiplas instâncias independentes de Redis. Uma única instância oferece exclusão prática, mas não o mesmo modelo de tolerância a falhas.
Adquirindo um lock
const resource = 'locks:invoice:inv-123';
const ttlMs = 10_000;
const lock = await redlock.acquire([resource], ttlMs);
try {
await generateInvoice('inv-123');
} finally {
await lock.release().catch(() => {});
}O nome deve representar exatamente o recurso protegido. Evite um lock global quando é possível usar locks por entidade.
TTL obrigatório
Todo lock precisa expirar. Sem TTL, uma queda do processo pode bloquear o recurso indefinidamente. O TTL deve ser maior que a duração normal da seção crítica, incluindo margem para pausas do runtime e latência.
Um TTL muito longo aumenta o tempo de recuperação. Um TTL curto demais permite que outro processo adquira o recurso antes do primeiro terminar.
Extensão do lock
Para tarefas longas, renove a concessão enquanto o processo estiver saudável:
await redlock.using([resource], 10_000, async signal => {
if (signal.aborted) throw signal.error;
await runSteps({
signal,
onCheckpoint: () => {
if (signal.aborted) throw signal.error;
}
});
});Se a extensão falhar, pare antes de executar novos efeitos. Algumas operações não podem ser interrompidas no meio; nesses casos, use transações, idempotência e fencing tokens.
Fencing tokens
Um lock expirado pode deixar um processo antigo continuar trabalhando. Um fencing token é um número monotônico associado à aquisição. O recurso protegido rejeita comandos com token menor que o último aceito.
const token = await redis.incr('fence:invoice:inv-123');
await storage.updateInvoice({
invoiceId: 'inv-123',
fencingToken: token,
status: 'generated'
});No banco, aplique uma condição:
UPDATE invoices
SET status = $2, fencing_token = $3
WHERE id = $1
AND fencing_token < $3;O fencing reduz o risco de um dono antigo sobrescrever um dono mais novo.
Não dependa do relógio da aplicação
Evite calcular validade apenas com Date.now(). Pausas de garbage collection, suspensão da máquina e deriva de relógio podem distorcer a percepção local. A biblioteca aplica margens, mas o desenho ainda deve tolerar expiração inesperada.
Retries com jitter
Quando várias instâncias disputam o mesmo recurso, retries sincronizados criam um efeito manada. Use atraso aleatório e um limite pequeno. Se o lock não for adquirido, retorne conflito, reagende o job ou tente depois.
try {
await redlock.acquire([resource], ttlMs);
} catch {
throw new Error('Recurso ocupado; tente novamente');
}Locks e idempotência
O lock reduz concorrência, mas não substitui idempotência. O processo pode concluir o efeito e cair antes de liberar ou registrar sucesso. Uma repetição precisa reconhecer que o efeito já ocorreu.
Use chaves idempotentes, constraints únicas e registros de processamento. Consulte Idempotency Keys no Node.js.
Locks e banco de dados
Se a seção crítica altera somente PostgreSQL, prefira transação e locking do próprio banco. Veja Pessimistic Locking no Node.js e Advisory Locks no Node.js.
Redis é útil quando o recurso atravessa serviços ou não pertence ao banco.
Locks para jobs
Um cron executado em várias réplicas pode usar lock por janela:
const key = `locks:daily-report:${businessDate}`;
await redlock.using([key], 60_000, async signal => {
await generateDailyReport({ businessDate, signal });
});Mesmo assim, grave um registro único do relatório. Se o lock expirar, a restrição final impede duplicação.
Multi-tenancy
Inclua o tenant na chave quando o recurso for isolado:
locks:tenant:t-7:report:2026-10-27Não permita que um cliente controle diretamente o nome completo da chave. Normalize identificadores e aplique tamanho máximo.
Granularidade
Locks muito amplos reduzem throughput. Locks muito específicos podem permitir conflitos que deveriam ser evitados. Modele o menor recurso que contém a invariável.
Deadlock entre múltiplos recursos
Quando uma operação precisa de vários locks, adquira todos em uma única chamada ou em ordem global estável:
await redlock.acquire([
'locks:account:a',
'locks:account:b'
], ttlMs);Ordenar IDs evita que dois processos adquiram recursos em ordem inversa.
Falha do Redis
Defina comportamento seguro. Para operações críticas, falhar fechado costuma ser melhor: se não é possível verificar exclusão, não execute. Para tarefas de cache ou manutenção, pode ser aceitável seguir sem lock, desde que o efeito seja idempotente.
Topologias e failover
Um Redis com réplica assíncrona pode perder a chave durante failover. O algoritmo Redlock foi proposto para usar maioria de instâncias independentes. Avalie os requisitos e as críticas ao algoritmo antes de tratá-lo como garantia de consenso.
Para alta criticidade, prefira sistemas com primitivas de consenso ou use o banco que já guarda a verdade do recurso.
Observabilidade
Monitore:
- tempo para adquirir;
- taxa de sucesso e conflito;
- extensões;
- falhas de extensão;
- duração da seção crítica;
- liberações falhas;
- locks expirados;
- fencing tokens rejeitados.
Não use a chave completa em métricas de alta cardinalidade. Registre tipo de recurso e resultado.
Shutdown
Ao receber SIGTERM, pare de adquirir novos locks, aguarde seções ativas dentro da janela e libere o que for possível. Se o processo morrer, o TTL continua sendo a defesa principal.
Testes
- duas instâncias disputando o mesmo recurso;
- recursos diferentes em paralelo;
- expiração durante trabalho;
- falha de extensão;
- queda antes da liberação;
- retry com jitter;
- fencing rejeitando dono antigo;
- Redis indisponível;
- shutdown com lock ativo.
Erros comuns
- lock sem TTL;
- TTL menor que a operação;
- assumir exclusão absoluta após expiração;
- não usar idempotência;
- lock global desnecessário;
- retry infinito;
- não tratar falha de extensão;
- usar Redis para proteger somente uma transação SQL;
- não observar failover.
Checklist
- recurso bem definido;
- TTL medido;
- retry limitado e com jitter;
- operação idempotente;
- fencing quando necessário;
- falha segura;
- métricas de disputa;
- testes de expiração e queda;
- alternativas no banco avaliadas.
Conclusão
Redlock no Node.js pode coordenar processos distribuídos, mas deve ser usado como uma concessão temporária sujeita a falhas. A segurança não vem apenas da chave no Redis.
Combine TTL, renovação, idempotência e fencing tokens. Para dados mantidos em um banco, prefira as primitivas transacionais do próprio banco. Use locks distribuídos somente quando a fronteira do recurso realmente atravessa processos ou sistemas.
Consulte a documentação do Redis sobre distributed locks e a documentação do node-redlock.




