Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(job): allow passing debounce as option #2666

Merged
merged 12 commits into from
Jul 29, 2024
Merged

feat(job): allow passing debounce as option #2666

merged 12 commits into from
Jul 29, 2024

Conversation

roggervalf
Copy link
Collaborator

@roggervalf roggervalf commented Jul 20, 2024

@@ -200,6 +205,8 @@ export class Job<
? { id: opts.parent.id, queueKey: opts.parent.queue }
: undefined;

this.debounceId = opts.debouncing ? opts.debouncing.id : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name of the option should be "debounce" instead of "debouncing".

@@ -323,6 +330,10 @@ export class Job<
job.repeatJobKey = json.rjk;
}

if (json.deid) {
job.debounceId = json.deid;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't it be better to use the same opts structure instead of having a new debounceId field in the job class? This will keep the class cleaner, specially if we add more options to the debounce option.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't save all the option because ttl is not reuse as debounceId, similar to repeatable jobs, we only save repeatJobKey instead of saving all repeat options in job instance.

@@ -0,0 +1,39 @@
# Debouncing

Debouncing a job implies delaying and deduplicating it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> Debouncing in BullMQ is a process where job execution is delayed and deduplicated based on specific identifiers. It ensures that within a specified period, or until a specific job is completed or failed, no new jobs with the same identifier will be added to the queue. Instead, these attempts will trigger a debounced event.

Debouncing a job implies delaying and deduplicating it.

## Fixed Mode

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> In the Fixed Mode, debouncing works by assigning a delay (Time to Live, TTL) to a job upon its creation. If a similar job (identified by a unique debouncer ID) is added during this delay period, it is ignored. This prevents the queue from being overwhelmed with multiple instances of the same task, thus optimizing the processing time and resource utilization.

);
```

For the next 5 seconds, after adding this job, next jobs added with same **debounce id** will be ignored and a _debounced_ event will be triggered by our QueueEvent class.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> In this example, after adding the house painting job with the debouncing parameters (id and ttl), any subsequent job with the same debouncing ID customValue added within 5 seconds will be ignored. This is useful for scenarios where rapid, repetitive requests are made, such as multiple users or processes attempting to trigger the same job.

```

While this job is not moved to completed or failed state, next jobs added with same **debounce id** will be ignored and a _debounced_ event will be triggered by our QueueEvent class.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mode is particularly useful for jobs that have a long running time or those that must not be duplicated until they are resolved, such as processing a file upload or performing a critical update that should not be repeated if the initial attempt is still in progress.

async removeDebounceKey(id: string): Promise<number> {
const client = await this.client;

return client.del(this.toKey('debounce:') + id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The postfix for this key could be just "de:" to keep it as small as possible.

@@ -86,10 +88,20 @@ else
end
end

local debounceId = opts['debo'] and opts['debo']['id']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code should be refactored as it is exactly the same on 4 different places.

return debouncedJobKey
end
end

-- Store the job.
local delay, priority = storeJob(eventsKey, jobIdKey, jobId, args[3], ARGV[2],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as we are storing the opts with the debounce options, why do we need to pass the debounceId again?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I can refactor it

Function to debounce a job.
]]

local function debounceJob(prefixKey, debounceId, ttl, jobId, eventsKey, maxEvents)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is more Redis friendly to pass the debounceKey as a command key instead of building it inside lua, as all the components of the key are known before calling this script.

Copy link
Contributor

@manast manast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought. What happens if you have a job with debounce id and some TTL, and the job fails but you have retries? should the TTL reset in this case?

local debounceId = debounceOpts and debounceOpts['id']
if debounceId then
local ttl = debounceOpts['ttl']
local isFirstSet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact "isFirstSet" is not a correct naming for this variable, as for example, you could have many jobs using the same debounceId, and when the debounceId expires a new job will be added and processed, so basically this variable should just be named "keyExists" or something like that.

--- @include "removeJobKeys"
--- @include "removeParentDependencyKey"

local function removeJob(jobId, hard, baseKey)
local function removeJob(jobId, hard, baseKey, shouldRemoveDebounceKey)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I think passing booleans as flags is worse than passing an object with optional flags in it, because the code is more legible, however, it would also be slower... so we can keep it like this I guess. But we should add a comment when used, for example:

    removeJob(key, hard, baseKey, true --[[remove debounce key]]--)

@roggervalf
Copy link
Collaborator Author

Just a thought. What happens if you have a job with debounce id and some TTL, and the job fails but you have retries? should the TTL reset in this case?

No in case of fixed I think, as It's name implies to keep it for that periodo of time. Better to use extended mode if they want to keep it in retries

@roggervalf roggervalf changed the title feat(job): allow passing a debounceId as option feat(job): allow passing a debounce as option Jul 24, 2024
Copy link
Contributor

@manast manast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@roggervalf roggervalf merged commit 163ccea into master Jul 29, 2024
10 checks passed
@roggervalf roggervalf deleted the debounced branch July 29, 2024 12:14
github-actions bot pushed a commit that referenced this pull request Jul 29, 2024
# [5.11.0](v5.10.4...v5.11.0) (2024-07-29)

### Features

* **job:** allow passing a debounce as option ([#2666](#2666)) ([163ccea](163ccea))
@jamesholcomb
Copy link

👏 👏 👏 wow almost 6 years from the original OptimalBits/bull#1034 but happy to finally see this!

@roggervalf roggervalf changed the title feat(job): allow passing a debounce as option feat(job): allow passing debounce as option Aug 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants