Guia para auditar e otimizar jobs cron que causam bloqueios e deadlocks em PostgreSQL

Por · Publicado em · Atualizado em · 10 min de leitura

Mesa escura com múltiplos monitores exibindo terminais e mãos sobre o teclado
Estações de trabalho com terminais, representando monitoramento e diagnóstico de sistemas. Foto: Tima Miroshnichenko / Pexels

Guia para auditar e otimizar jobs cron que causam bloqueios e deadlocks em PostgreSQL: passo a passo para diagnosticar contenção, ajustar logs, usar pg_stat_activity/pg_locks, aplicar timeouts, lotes e SKIP LOCKED. Inclui queries prontas, correções rápidas e padrões de redesign para reduzir impacto sem reescrever tudo.

Quando o cron vira gargalo: sintomas e impacto

Jobs cron que batem em tabelas concorridas podem travar consultas e gerar filas de espera. Você percebe lentidão repentina, timeouts na aplicação e “idle in transaction” prolongado. O banco parece “segurar a respiração” até o cron terminar.

Nos logs, surgem mensagens de lock wait e, às vezes, deadlocks. Em picos, processos de negócio ficam presos aguardando um LOCK exclusivo. Isso amplifica o impacto do cron para além do próprio job.

Outro sinal é o aumento de transações longas. Uma atualização em massa com commit tardio segura locks por muito tempo. Em seguida, qualquer leitura ou escrita relacionada fica bloqueada.

Observe também autovacuum atrasado. Se o cron gera muita escrita e longas transações, o autovacuum sofre. Isso piora desempenho e aumenta contenção, criando um círculo vicioso.

Confusões com locks consultivos também aparecem. Um job que obtém advisory lock amplo demais pode bloquear tarefas não relacionadas. Revise escopo e chaves usadas para evitar bloqueios cruzados.

Ative e leia os logs certos antes de mudar código

Sem visibilidade, ajustes viram adivinhação. Comece validando parâmetros que revelam esperas por lock e consultas lentas. Faça isso em uma janela controlada ou ambiente de teste, quando possível.

Verifique valores atuais com comandos simples. Exemplo: SHOW deadlock_timeout; e SHOW log_lock_waits;. Em jobs psql, defina timeouts por sessão com SET statement_timeout = '30s'; e SET lock_timeout = '2s'; antes da query principal.

Use application_name para rastrear cada job no log. Em cron, passe PGAPPNAME=seu-job no ambiente ou defina application_name na string de conexão. Assim você casa eventos de log com processos específicos.

  • SHOW log_lock_waits; e SHOW deadlock_timeout; para confirmar diagnósticos.
  • No psql do job, rode primeiro: SET lock_timeout = '2s'; e SET statement_timeout = '30s';
  • Inclua PGAPPNAME=job-diario ao invocar psql ou configure application_name na conexão.
Parâmetro Para que serve Dica de uso no cron
statement_timeout Limita a duração de cada comando SQL Evita jobs presos por minutos; ajuste por etapa do job
lock_timeout Limita a espera por obtenção de lock Falha rápido quando há contenção; registre e reprograme
deadlock_timeout Intervalo para logar esperas que podem virar deadlock Valores entre 1s e 2s costumam expor bloqueios cedo
log_lock_waits Loga esperas por lock acima de deadlock_timeout Cria trilha de auditoria do que bloqueia quem
application_name Identifica o originador no log e nas views Atribua nome único por job e por etapa

Inspeção em tempo real com SQL: descubra quem bloqueia quem

Use as visões internas para ver sessões, locks e progresso. Comece por pg_stat_activity e pg_locks. Elas mostram quem está esperando e quem está segurando o recurso.

Descubra sessões esperando por lock. Foque em queries do cron e nos bloqueadores primários. Mapeie o relacionamento usando funções nativas.

Encontre transações longas e sessões em “idle in transaction”. Elas seguram locks por muito tempo e impedem autovacuum de avançar.

Olhe o uso de índices e progressos do autovacuum. Se há muitas varreduras sequenciais, o cron pode estar sem índices de apoio. Ajustes simples já reduzem o tempo de lock.

  • Esperando por lock: SELECT pid, wait_event_type, wait_event, query FROM pg_stat_activity WHERE wait_event_type = 'Lock';
  • Quem bloqueia quem: SELECT pid, pg_blocking_pids(pid) AS blockers, query FROM pg_stat_activity WHERE wait_event_type = 'Lock';
  • Transações longas: SELECT pid, now()-xact_start AS age, state, query FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY age DESC;
  • Idle em transação: SELECT pid, now()-xact_start AS age, query FROM pg_stat_activity WHERE state = 'idle in transaction';
  • Detalhes de locks: SELECT pid, locktype, relation::regclass, mode, granted FROM pg_locks WHERE NOT granted OR mode LIKE '%Exclusive%';
  • Índices vs varreduras: SELECT relname, idx_scan, seq_scan FROM pg_stat_user_tables ORDER BY seq_scan DESC LIMIT 20;
  • Autovacuum em progresso: SELECT relid::regclass, phase FROM pg_stat_progress_vacuum;
  • Se suspeitar de query ineficiente, faça EXPLAIN ANALYZE em ambiente seguro ou baixo tráfego. Veja também otimização de consultas SQL no PostgreSQL.

Relacione sessões do banco aos seus cron jobs

Sem identificar o originador, você resolve sintomas e não a causa. Nomeie os jobs. Em cron, defina PGAPPNAME=job-rotina ao invocar o cliente do banco. Isso aparece em application_name e no log.

Exemplo com psql e “falha rápida” por lock. Defina timeouts antes da chamada principal do job. Deixe o log mostrar o que foi bloqueado e por quem.

Crie uma tabela de job_runs para registrar início, fim, pid e status. No começo do job, insira um registro e salve pg_backend_pid() e txid_current(). Ao finalizar, atualize com sucesso ou erro. Isso facilita auditoria.

  • Exemplo cron: PGAPPNAME=recalc-daily psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "SET lock_timeout='2s'; SELECT run_recalc();"
  • Defina application_name também via string de conexão: ...?application_name=recalc-daily
  • Tabela de controle ajuda a medir duração, falhas e concorrência acidental entre instâncias.

Correções rápidas que reduzem bloqueios hoje

Antes de reescrever, tente táticas de contenção e de performance locais. Alivie a disputa e diminua a janela de lock. Combine timeouts, escalonamento e índices. Quando a query melhora, o lock dura menos.

Otimize a consulta e o plano. Pareto ajuda. Indexe colunas de filtro e junção. Use CREATE INDEX CONCURRENTLY para evitar parada de escrita. Veja ideias em redução de latência e otimização de queries.

  • Escalone horários. Evite que jobs pesados rodem juntos em tabelas quentes.
  • Garanta exclusão mútua com flock: flock -n /var/lock/meujob.lock -c "/path/meujob.sh".
  • Use locks consultivos: SELECT pg_try_advisory_lock(chave); e desista se não conseguir.
  • Defina SET lock_timeout = '1-3s' e SET statement_timeout compatível com seu SLA.
  • Divida atualizações em lotes. Exemplo: processe 500 a 2.000 linhas por transação.
  • Prefira SELECT ... FOR UPDATE SKIP LOCKED para filas de trabalho concorrentes.
  • Use NOWAIT quando quiser falhar rápido sem esperar por lock.
  • Evite transações longas; faça commits frequentes e limite escopo de writes.

Redesenhe os jobs para segurança e escala sustentáveis

Alguns bloqueios vêm do desenho do job, não só da query. Idempotência, reentrância e lotes previsíveis tornam execução segura. Um bom padrão é fila com “claim” usando SKIP LOCKED.

Delimite a transação. Abra logo antes do lote e feche ao concluir. Faça checkpoints por lote. Assim, você reduz a duração do lock e o risco de deadlock entre operações diferentes.

Evite DDL em horário de pico. Se precisar criar índices, use CREATE INDEX CONCURRENTLY. Para grandes transformações, planeje janelas e estratégias de migração segura, como em migrações com zero‑downtime.

  • Use tabela de tarefas com colunas status, taken_at e worker_id. Faça claim com FOR UPDATE SKIP LOCKED e LIMIT.
  • Defina retries com backoff exponencial e jitter para evitar rebatidas simultâneas.
  • Garanta chaves e índices que suportem UPSERTs previsíveis.
  • Evite SERIALIZABLE sem necessidade. READ COMMITTED costuma bastar para jobs batch.
  • Considere particionamento e processamento por partição para reduzir contenção.

Entenda MVCC e tipos de lock que afetam seus jobs

O PostgreSQL usa MVCC para permitir leituras consistentes sem bloquear escritas na maioria dos casos. Ainda assim, certas operações exigem locks mais fortes e podem conflitar com jobs concorrentes.

Locks de linha (row-level) surgem com UPDATE/DELETE e SELECT FOR UPDATE/SHARE. Eles coexistem com leituras em READ COMMITTED, mas conflitam entre writes na mesma linha.

Locks de relação (tabela) incluem modos como ACCESS EXCLUSIVE, que bloqueiam leituras e escritas. DDL tende a pegar locks mais agressivos. Evite-os junto com jobs batch.

Deadlocks nascem quando duas transações adquirem locks em ordens diferentes. Padronize a ordem de acesso a tabelas e índices dentro dos jobs para reduzir ciclos de dependência.

  • Prefira READ COMMITTED para batch; SERIALIZABLE só quando a correção exigir.
  • Padronize a ordem de updates: Tabela A depois Tabela B, sempre na mesma sequência.
  • Faça índices que suportem filtros; menos linhas tocadas, menos locks mantidos.
  • Reveja gatilhos (triggers) que podem realizar DML extra e alongar a transação.

Checklist de auditoria e otimização de cron no PostgreSQL

Use esta lista como guia operacional. Adapte ao seu ambiente e às janelas permitidas. Revise periodicamente, pois dados e carga mudam com o tempo.

  • Inventarie todos os cron jobs. Documente tabela(s), janelas e SLOs.
  • Atribua application_name único a cada job e etapa.
  • Ative log_lock_waits e defina deadlock_timeout adequado.
  • Colete amostras de pg_stat_activity e pg_locks em picos e em dias tranquilos.
  • Identifique blockers primários com pg_blocking_pids e trate-os primeiro.
  • Defina lock_timeout e statement_timeout por job.
  • Reduza concorrência via escalonamento, flock ou advisory locks.
  • Otimize queries críticas. Indices em colunas de filtro e junção.
  • Use CREATE INDEX CONCURRENTLY para evitar bloqueios de escrita.
  • Divida writes em lotes pequenos e transações curtas.
  • Evite DDL em horários quentes; planeje janelas seguras.
  • Monitore autovacuum e evite “idle in transaction”.
  • Implemente fila com SKIP LOCKED para execução paralela segura.
  • Documente decisões e resultados. Ajuste periodicamente os limites.

Perguntas frequentes

Qual a diferença entre bloqueio e deadlock em PostgreSQL?

Bloqueio é quando uma transação espera um recurso travado por outra. Deadlock é um ciclo de dependências em que duas ou mais transações esperam entre si, sem saída. O PostgreSQL detecta deadlocks e aborta uma delas. Já bloqueios comuns podem durar muito, se não houver timeout.

Devo matar sessões para resolver bloqueios?

Só como último recurso e com contexto. Prefira reduzir concorrência, aplicar timeouts e otimizar queries. Se precisar, use pg_terminate_backend(pid) em quem bloqueia e não pode concluir. Verifique dependências e o risco de rollback caro. Sempre registre a ação e monitore impacto.

SKIP LOCKED ou NOWAIT: quando usar cada um?

NOWAIT falha de imediato se o lock não puder ser obtido. Útil quando você quer desistir rápido. SKIP LOCKED ignora linhas bloqueadas e segue com outras. É ideal para filas de trabalho e processamento em paralelo sem contenção, desde que a lógica seja idempotente.

Como definir o tamanho ideal do lote em jobs batch?

Depende do tempo alvo, índices e I/O. Comece pequeno, como 500 a 2.000 linhas, e meça duração e impacto de lock. Ajuste até ficar estável dentro do seu SLO. Use LIMIT com SKIP LOCKED, commits por lote e monitore tempo médio e p95 por execução.

Ajustar autovacuum ajuda a reduzir bloqueios?

Ajuda quando há muita escrita e longas transações. Transações longas atrapalham o autovacuum. Monitore pg_stat_progress_vacuum e evite “idle in transaction”. Ajustes finos variam por tabela e carga, mas reduzir duração de transações já melhora a eficiência do autovacuum.

Como evitar que DDL do cron bloqueie produção?

Evite DDL em horário de pico. Para índices, use CREATE INDEX CONCURRENTLY. Planeje mudanças em etapas, com janelas controladas. Teste o plano em ambiente de staging. Para mudanças extensas, avalie estratégias de migração segura e por partes, evitando lock exclusivo prolongado.

Precisa auditar e otimizar seus cron jobs no PostgreSQL?

Se quer reduzir bloqueios e deadlocks sem reescrever tudo, podemos estruturar um plano de ajustes, testes e monitoramento focado no seu ambiente.

Falar sobre auditoria de cron jobs

Veja também