| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.cleanJobsInSet = void 0;
- const content = `--[[
- Remove jobs from the specific set.
- Input:
- KEYS[1] set key,
- KEYS[2] events stream key
- KEYS[3] repeat key
- ARGV[1] jobKey prefix
- ARGV[2] timestamp
- ARGV[3] limit the number of jobs to be removed. 0 is unlimited
- ARGV[4] set name, can be any of 'wait', 'active', 'paused', 'delayed', 'completed', or 'failed'
- ]]
- local rcall = redis.call
- local repeatKey = KEYS[3]
- local rangeStart = 0
- local rangeEnd = -1
- local limit = tonumber(ARGV[3])
- -- If we're only deleting _n_ items, avoid retrieving all items
- -- for faster performance
- --
- -- Start from the tail of the list, since that's where oldest elements
- -- are generally added for FIFO lists
- if limit > 0 then
- rangeStart = -1 - limit + 1
- rangeEnd = -1
- end
- -- Includes
- --[[
- Function to clean job list.
- Returns jobIds and deleted count number.
- ]]
- -- Includes
- --[[
- Function to get the latest saved timestamp.
- ]]
- local function getTimestamp(jobKey, attributes)
- if #attributes == 1 then
- return rcall("HGET", jobKey, attributes[1])
- end
- local jobTs
- for _, ts in ipairs(rcall("HMGET", jobKey, unpack(attributes))) do
- if (ts) then
- jobTs = ts
- break
- end
- end
- return jobTs
- end
- --[[
- Function to check if the job belongs to a job scheduler and
- current delayed job matches with jobId
- ]]
- local function isJobSchedulerJob(jobId, jobKey, jobSchedulersKey)
- local repeatJobKey = rcall("HGET", jobKey, "rjk")
- if repeatJobKey then
- local prevMillis = rcall("ZSCORE", jobSchedulersKey, repeatJobKey)
- if prevMillis then
- local currentDelayedJobId = "repeat:" .. repeatJobKey .. ":" .. prevMillis
- return jobId == currentDelayedJobId
- end
- end
- return false
- end
- --[[
- Function to remove job.
- ]]
- -- Includes
- --[[
- Function to remove deduplication key if needed
- when a job is being removed.
- ]]
- local function removeDeduplicationKeyIfNeededOnRemoval(prefixKey,
- jobId, deduplicationId)
- if deduplicationId then
- local deduplicationKey = prefixKey .. "de:" .. deduplicationId
- local currentJobId = rcall('GET', deduplicationKey)
- if currentJobId and currentJobId == jobId then
- rcall("DEL", deduplicationKey)
- -- Also clean up any pending dedup-next data for this dedup ID
- rcall("DEL", prefixKey .. "dn:" .. deduplicationId)
- return 1
- end
- end
- end
- --[[
- Function to remove job keys.
- ]]
- local function removeJobKeys(jobKey)
- return rcall("DEL", jobKey, jobKey .. ':logs', jobKey .. ':dependencies',
- jobKey .. ':processed', jobKey .. ':failed', jobKey .. ':unsuccessful')
- end
- --[[
- Check if this job has a parent. If so we will just remove it from
- the parent child list, but if it is the last child we should move the parent to "wait/paused"
- which requires code from "moveToFinished"
- ]]
- -- Includes
- --[[
- Function to add job in target list and add marker if needed.
- ]]
- -- Includes
- --[[
- Add marker if needed when a job is available.
- ]]
- local function addBaseMarkerIfNeeded(markerKey, isPausedOrMaxed)
- if not isPausedOrMaxed then
- rcall("ZADD", markerKey, 0, "0")
- end
- end
- local function addJobInTargetList(targetKey, markerKey, pushCmd, isPausedOrMaxed, jobId)
- rcall(pushCmd, targetKey, jobId)
- addBaseMarkerIfNeeded(markerKey, isPausedOrMaxed)
- end
- --[[
- Functions to destructure job key.
- Just a bit of warning, these functions may be a bit slow and affect performance significantly.
- ]]
- local getJobIdFromKey = function (jobKey)
- return string.match(jobKey, ".*:(.*)")
- end
- local getJobKeyPrefix = function (jobKey, jobId)
- return string.sub(jobKey, 0, #jobKey - #jobId)
- end
- --[[
- Function to check for the meta.paused key to decide if we are paused or not
- (since an empty list and !EXISTS are not really the same).
- ]]
- local function getTargetQueueList(queueMetaKey, activeKey, waitKey, pausedKey)
- local queueAttributes = rcall("HMGET", queueMetaKey, "paused", "concurrency", "max", "duration")
- if queueAttributes[1] then
- return pausedKey, true, queueAttributes[3], queueAttributes[4]
- else
- if queueAttributes[2] then
- local activeCount = rcall("LLEN", activeKey)
- if activeCount >= tonumber(queueAttributes[2]) then
- return waitKey, true, queueAttributes[3], queueAttributes[4]
- else
- return waitKey, false, queueAttributes[3], queueAttributes[4]
- end
- end
- end
- return waitKey, false, queueAttributes[3], queueAttributes[4]
- end
- local function _moveParentToWait(parentPrefix, parentId, emitEvent)
- local parentTarget, isPausedOrMaxed = getTargetQueueList(parentPrefix .. "meta", parentPrefix .. "active",
- parentPrefix .. "wait", parentPrefix .. "paused")
- addJobInTargetList(parentTarget, parentPrefix .. "marker", "RPUSH", isPausedOrMaxed, parentId)
- if emitEvent then
- local parentEventStream = parentPrefix .. "events"
- rcall("XADD", parentEventStream, "*", "event", "waiting", "jobId", parentId, "prev", "waiting-children")
- end
- end
- local function removeParentDependencyKey(jobKey, hard, parentKey, baseKey, debounceId)
- if parentKey then
- local parentDependenciesKey = parentKey .. ":dependencies"
- local result = rcall("SREM", parentDependenciesKey, jobKey)
- if result > 0 then
- local pendingDependencies = rcall("SCARD", parentDependenciesKey)
- if pendingDependencies == 0 then
- local parentId = getJobIdFromKey(parentKey)
- local parentPrefix = getJobKeyPrefix(parentKey, parentId)
- local numRemovedElements = rcall("ZREM", parentPrefix .. "waiting-children", parentId)
- if numRemovedElements == 1 then
- if hard then -- remove parent in same queue
- if parentPrefix == baseKey then
- removeParentDependencyKey(parentKey, hard, nil, baseKey, nil)
- removeJobKeys(parentKey)
- if debounceId then
- rcall("DEL", parentPrefix .. "de:" .. debounceId)
- end
- else
- _moveParentToWait(parentPrefix, parentId)
- end
- else
- _moveParentToWait(parentPrefix, parentId, true)
- end
- end
- end
- return true
- end
- else
- local parentAttributes = rcall("HMGET", jobKey, "parentKey", "deid")
- local missedParentKey = parentAttributes[1]
- if( (type(missedParentKey) == "string") and missedParentKey ~= ""
- and (rcall("EXISTS", missedParentKey) == 1)) then
- local parentDependenciesKey = missedParentKey .. ":dependencies"
- local result = rcall("SREM", parentDependenciesKey, jobKey)
- if result > 0 then
- local pendingDependencies = rcall("SCARD", parentDependenciesKey)
- if pendingDependencies == 0 then
- local parentId = getJobIdFromKey(missedParentKey)
- local parentPrefix = getJobKeyPrefix(missedParentKey, parentId)
- local numRemovedElements = rcall("ZREM", parentPrefix .. "waiting-children", parentId)
- if numRemovedElements == 1 then
- if hard then
- if parentPrefix == baseKey then
- removeParentDependencyKey(missedParentKey, hard, nil, baseKey, nil)
- removeJobKeys(missedParentKey)
- if parentAttributes[2] then
- rcall("DEL", parentPrefix .. "de:" .. parentAttributes[2])
- end
- else
- _moveParentToWait(parentPrefix, parentId)
- end
- else
- _moveParentToWait(parentPrefix, parentId, true)
- end
- end
- end
- return true
- end
- end
- end
- return false
- end
- local function removeJob(jobId, hard, baseKey, shouldRemoveDeduplicationKey)
- local jobKey = baseKey .. jobId
- removeParentDependencyKey(jobKey, hard, nil, baseKey)
- if shouldRemoveDeduplicationKey then
- local deduplicationId = rcall("HGET", jobKey, "deid")
- removeDeduplicationKeyIfNeededOnRemoval(baseKey, jobId, deduplicationId)
- end
- removeJobKeys(jobKey)
- end
- local function cleanList(listKey, jobKeyPrefix, rangeStart, rangeEnd,
- timestamp, isWaiting, jobSchedulersKey)
- local jobs = rcall("LRANGE", listKey, rangeStart, rangeEnd)
- local deleted = {}
- local deletedCount = 0
- local jobTS
- local deletionMarker = ''
- local jobIdsLen = #jobs
- for i, job in ipairs(jobs) do
- if limit > 0 and deletedCount >= limit then
- break
- end
- local jobKey = jobKeyPrefix .. job
- if (isWaiting or rcall("EXISTS", jobKey .. ":lock") == 0) and
- not isJobSchedulerJob(job, jobKey, jobSchedulersKey) then
- -- Find the right timestamp of the job to compare to maxTimestamp:
- -- * finishedOn says when the job was completed, but it isn't set unless the job has actually completed
- -- * processedOn represents when the job was last attempted, but it doesn't get populated until
- -- the job is first tried
- -- * timestamp is the original job submission time
- -- Fetch all three of these (in that order) and use the first one that is set so that we'll leave jobs
- -- that have been active within the grace period:
- jobTS = getTimestamp(jobKey, {"finishedOn", "processedOn", "timestamp"})
- if (not jobTS or jobTS <= timestamp) then
- -- replace the entry with a deletion marker; the actual deletion will
- -- occur at the end of the script
- rcall("LSET", listKey, rangeEnd - jobIdsLen + i, deletionMarker)
- removeJob(job, true, jobKeyPrefix, true --[[remove debounce key]])
- deletedCount = deletedCount + 1
- table.insert(deleted, job)
- end
- end
- end
- rcall("LREM", listKey, 0, deletionMarker)
- return {deleted, deletedCount}
- end
- --[[
- Function to clean job set.
- Returns jobIds and deleted count number.
- ]]
- -- Includes
- --[[
- Function to loop in batches.
- Just a bit of warning, some commands as ZREM
- could receive a maximum of 7000 parameters per call.
- ]]
- local function batches(n, batchSize)
- local i = 0
- return function()
- local from = i * batchSize + 1
- i = i + 1
- if (from <= n) then
- local to = math.min(from + batchSize - 1, n)
- return from, to
- end
- end
- end
- --[[
- We use ZRANGEBYSCORE to make the case where we're deleting a limited number
- of items in a sorted set only run a single iteration. If we simply used
- ZRANGE, we may take a long time traversing through jobs that are within the
- grace period.
- ]]
- local function getJobsInZset(zsetKey, rangeEnd, limit)
- if limit > 0 then
- return rcall("ZRANGEBYSCORE", zsetKey, 0, rangeEnd, "LIMIT", 0, limit)
- else
- return rcall("ZRANGEBYSCORE", zsetKey, 0, rangeEnd)
- end
- end
- local function cleanSet(
- setKey,
- jobKeyPrefix,
- rangeEnd,
- timestamp,
- limit,
- attributes,
- isFinished,
- jobSchedulersKey)
- local jobs = getJobsInZset(setKey, rangeEnd, limit)
- local deleted = {}
- local deletedCount = 0
- local jobTS
- for i, job in ipairs(jobs) do
- if limit > 0 and deletedCount >= limit then
- break
- end
- local jobKey = jobKeyPrefix .. job
- -- Extract a Job Scheduler Id from jobId ("repeat:job-scheduler-id:millis")
- -- and check if it is in the scheduled jobs
- if not (jobSchedulersKey and isJobSchedulerJob(job, jobKey, jobSchedulersKey)) then
- if isFinished then
- removeJob(job, true, jobKeyPrefix, true --[[remove debounce key]] )
- deletedCount = deletedCount + 1
- table.insert(deleted, job)
- else
- -- * finishedOn says when the job was completed, but it isn't set unless the job has actually completed
- jobTS = getTimestamp(jobKey, attributes)
- if (not jobTS or jobTS <= timestamp) then
- removeJob(job, true, jobKeyPrefix, true --[[remove debounce key]] )
- deletedCount = deletedCount + 1
- table.insert(deleted, job)
- end
- end
- end
- end
- if (#deleted > 0) then
- for from, to in batches(#deleted, 7000) do
- rcall("ZREM", setKey, unpack(deleted, from, to))
- end
- end
- return {deleted, deletedCount}
- end
- local result
- if ARGV[4] == "active" then
- result = cleanList(KEYS[1], ARGV[1], rangeStart, rangeEnd, ARGV[2], false --[[ hasFinished ]],
- repeatKey)
- elseif ARGV[4] == "delayed" then
- rangeEnd = "+inf"
- result = cleanSet(KEYS[1], ARGV[1], rangeEnd, ARGV[2], limit,
- {"processedOn", "timestamp"}, false --[[ hasFinished ]], repeatKey)
- elseif ARGV[4] == "prioritized" then
- rangeEnd = "+inf"
- result = cleanSet(KEYS[1], ARGV[1], rangeEnd, ARGV[2], limit,
- {"timestamp"}, false --[[ hasFinished ]], repeatKey)
- elseif ARGV[4] == "wait" or ARGV[4] == "paused" then
- result = cleanList(KEYS[1], ARGV[1], rangeStart, rangeEnd, ARGV[2], true --[[ hasFinished ]],
- repeatKey)
- else
- rangeEnd = ARGV[2]
- -- No need to pass repeat key as in that moment job won't be related to a job scheduler
- result = cleanSet(KEYS[1], ARGV[1], rangeEnd, ARGV[2], limit,
- {"finishedOn"}, true --[[ hasFinished ]])
- end
- rcall("XADD", KEYS[2], "*", "event", "cleaned", "count", result[2])
- return result[1]
- `;
- exports.cleanJobsInSet = {
- name: 'cleanJobsInSet',
- content,
- keys: 3,
- };
- //# sourceMappingURL=cleanJobsInSet-3.js.map
|