Skip to content

Add "requeue" deduplication mode to ensure at least one execution after last trigger #3427

@jtomaszewski

Description

@jtomaszewski

I need to ensure a job runs at least once after an action occurs, while avoiding unnecessary duplicate executions when multiple triggers happen in rapid succession.

Currently, BullMQ's deduplication modes have a critical gap:

  • Simple mode: Deduplicates until job completes, but if a trigger occurs while the job is actively processing, that trigger is lost forever
  • Throttle mode: Similar issue - triggers during job execution are ignored
  • Debounce mode: Requires a delay and doesn't handle triggers during active processing

This is a common pattern needed for:

  • Data synchronization: Multiple data changes should eventually sync, with at least one sync after the last change
  • Cache invalidation: Multiple updates should trigger invalidation, ensuring cache is refreshed after the last update
  • Webhook processing: Multiple webhook events for the same resource should be processed at least once after the last event

Related to #3151 (comment), which describes a similar need, but I think hasn't been completely fulfilled yet.

Describe the solution you'd like

A new deduplication mode that guarantees at least one job execution after the last trigger attempt:

await myQueue.add(
  'process-action',
  { data },
  {
    deduplication: {
      id: 'unique-job-id',
      requeueIfActive: true // new option
    }
  }
);

Behavior:

  1. If job with same dedup ID is waiting/delayed → Deduplicate (ignore new job)
  2. If job is active (processing) → Mark for automatic requeue after completion
  3. If job is completed/failed → Allow new job immediately
  4. While marking a job as complete → Check if requeue was requested and automatically schedule a new job

This ensures:

✅ No unnecessary duplicate processing when job is already queued
✅ No lost triggers when job is actively processing
✅ Automatic handling without manual event listeners
✅ Works across multiple Node.js processes

Example use case

// Deployment pipeline triggered by git webhooks
async function onGitPush(commitData) {
  await deployQueue.add(
    'deploy',
    { commit: commitData.sha },
    {
      deduplication: {
        id: `deploy-${commitData.repo}`,
        requeueIfActive: true
      }
    }
  );
}

// If multiple pushes happen:
// - Push 1 at 0ms: Job scheduled
// - Push 2 at 100ms (job waiting): Deduplicated
// - Push 3 at 5000ms (job active): Deduplicated but marked for requeue
// - Push 4 at 5100ms (job active): Deduplicated, requeue already marked
// - Job completes at 10000ms: New job automatically scheduled
// Result: Exactly 2 deployments run, last push is always deployed

Describe alternatives you've considered

  • Manual tracking with deduplicated events: Requires complex state management in Redis, prone to race conditions
  • Using job.updateData() during processing: Worker needs to check for updates, prone to race conditions too
  • Version-based deduplication: Every trigger creates a job (no deduplication benefit)
  • External pending queue: Adds complexity and potential for race conditions

None of these alternatives are as clean or reliable as having this built into BullMQ.

Additional context

I'm willing to contribute a PR for this feature if the maintainers agree with the approach. The implementation could:

  • Use atomic Redis operations to track requeue requests
  • Integrate with existing deduplication infrastructure
  • Include comprehensive tests
  • Maintain backward compatibility
  • Add minimal performance overhead (only for jobs using this mode)

Would the team be open to this feature? If so, I'm happy to discuss implementation details and submit a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    wontfixThis will not be worked on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions