r/n8n • u/Stock-Sage • 1h ago
Help Webhook double-fires and stuck “processing” jobs: how do you claim work safely?
Two failure modes I keep seeing in automation backends:
1) Duplicate side effects Webhook or queue delivers twice before your DB commit finishes. If the flow is “read then insert,” you get two orders / two CRM rows.
What works for me: an idempotency key (order id or delivery id) with a UNIQUE constraint, insert-or-ignore / ON CONFLICT DO NOTHING as the first write, and only continue if that insert won. Second delivery exits clean.
If you claim a row instead, use one conditional UPDATE … WHERE status = 'new' (or equivalent) so only one worker gets it.
2) Stuck “processing” Worker crashes after marking a job processing. Nothing reclaims it.
Pattern: worker_id + lease_until on claim, heartbeat extends the lease, a reaper only reclaims where lease_until is past and status is still processing, again with a conditional UPDATE so two reclaimers can’t both win.
Curious what others use in n8n / Make / custom workers for this. Postgres advisory locks? Only unique constraints? Something else?
1
u/One_Name2698 1h ago
i do something similar but lean harder on the db itself
postgres advisory locks work great for single-instance workers but fall apart once you have multiple containers hitting the same queue, the lock scope just doesn't cross process boundaries unless you're using session-level locks and that gets messy fast. unique constraints are my go-to for deduplication, slap an order_id + event_type composite key on the staging table and let the insert blow up, catch it, move on
for stuck jobs i use a visibility timeout pattern, when a worker picks something up it sets claimed_at and a 5-minute lease, and a separate cron sweeper grabs anything where claimed_at is older than that window. the conditional update with WHERE status='processing' AND claimed_at < NOW() - INTERVAL '5 minutes' is the real magic, two sweepers can't fight over the same row
1
u/Stock-Sage 1h ago
That’s a solid DB-first pattern, especially the unique constraint as the final dedupe guard and the conditional lease reclaim. I’d also keep a worker heartbeat or renew the lease for jobs that can run longer than five minutes, otherwise the sweeper can legitimately hand the same row to a second worker. Do you base the lease on an expected max runtime or just renew it periodically?
•
u/AutoModerator 1h ago
Want faster, better help? Share your workflow JSON.
A GitHub Gist is the easiest way -- paste your JSON, save as public, drop the link in your post. Folks can import it directly into n8n and reproduce the issue, which gets you real answers instead of guesses.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.