Como impedir que workers do n8n processem o mesmo job do PostgreSQL simultaneamente?

OLÁ PESSOAL
Estou executando vários workers n8n que extraem jobs pendentes da mesma tabela PostgreSQL. Com tráfego normal funciona, mas quando vários workers consultam a tabela ao mesmo tempo, o mesmo job às vezes pode ser capturado por mais de um worker.
Atualmente estou fazendo:
SELECT id, payload
FROM jobs
WHERE status = ‘pending’
ORDER BY created_at
LIMIT 10;
Depois atualizo os jobs selecionados para processando.
Estou pensando em mudar para:
SELECT id, payload
FROM jobs
WHERE status = ‘pending’
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 10;
Qual seria o melhor padrão de produção para reivindicação atômica de jobs no n8n + PostgreSQL?
Devo usar FOR UPDATE SKIP LOCKED, um UPDATE atômico … RETURNING, ou uma combinação de ambos?
Além disso, como devo lidar com jobs quando um worker trava após reivindicá-los para que possam ser retentados com segurança sem criar processamentos duplicados?

Descreva o problema/erro/pergunta

Qual é a mensagem de erro (se houver)?

Por favor, compartilhe seu workflow

(Selecione os nós na sua tela e use os atalhos de teclado CMD+C/CTRL+C e CMD+V/CTRL+V para copiar e colar o workflow.)

Compartilhe a saída retornada pelo último nó

Informações sobre sua configuração n8n

  • Versão do n8n:
  • Banco de dados (padrão: SQLite):
  • Configuração EXECUTIONS_PROCESS do n8n (padrão: own, main):
  • Executando n8n por (Docker, npm, n8n cloud, aplicativo desktop):
  • Sistema operacional:

Eu faria os dois, apenas para ser seguro, algo assim pode ajudar!

Oi @Mary_Berry Para essa situação, use FOR UPDATE SKIP LOCKED ou um UPDATE … RETURNING atômico, com um timeout de lease para workers que travaram.

O objetivo principal é tornar a reivindicação de um job atômica, para que dois workers n8n não possam pegar o mesmo job.

Reivindicar jobs atomicamente
Uma boa abordagem é:
WITH jobs_to_claim AS (
SELECT id
FROM jobs
WHERE status = ‘pending’
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT 10
)
UPDATE jobs
SET
status = ‘processing’,
locked_at = NOW(),
worker_id = $1
FROM jobs_to_claim
WHERE jobs.id = jobs_to_claim.id
RETURNING jobs.*;

Se um worker travar, outro worker pode eventualmente reivindicar o job novamente.

Proteja contra workers obsoletos

Ao completar um job, verifique que o worker ainda é o proprietário:
UPDATE jobs
SET status = ‘completed’
WHERE id = $1
AND status = ‘processing’
AND worker_id = $2;

Se atualizar 0 linhas, esse worker não é mais proprietário do job e não deveria modificá-lo.

O princípio-chave é: o banco de dados deve controlar a propriedade do job, não os workers n8n em si.

Oi @Mary_Berry Bem-vindo!
No modo de fila, a duplicação pode vir do próprio n8n, antes da sua query de reclamação ser executada. Cada worker mantém um lease do Redis na execução por QUEUE_WORKER_LOCK_DURATION (60000 ms) e o renova a cada QUEUE_WORKER_LOCK_RENEW_TIME. Um worker que falha ou está muito ocupado para renovar no tempo, tem sua execução marcada como travada e reenviada para outro worker, que reexecuta o fluxo de trabalho desde o disparador e reclama um novo lote enquanto o lote travado fica em processing. No n8n 1.x essa repetição acontece uma vez por padrão, então desative-a e defina o lease acima do seu lote mais lento:

QUEUE_WORKER_MAX_STALLED_COUNT=0
QUEUE_WORKER_LOCK_DURATION=300000

No n8n 2.0 essa variável e a tentativa de travamento do Bull desapareceram, então remova a primeira linha após a atualização e conduza a recuperação dessas linhas da sua própria tabela.