Skip to content

Commit 82f571f

Browse files
authored
fix(obliterate): safer implementation
1 parent 61d39d5 commit 82f571f

File tree

4 files changed

+103
-29
lines changed

4 files changed

+103
-29
lines changed

src/classes/queue.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -217,17 +217,18 @@ export class Queue<
217217
* Note: This operation requires to iterate on all the jobs stored in the queue
218218
* and can be slow for very large queues.
219219
*
220-
* @param { { force: boolean }} opts. Use force = true to force obliteration even
221-
* with active jobs in the queue.
220+
* @param { { force: boolean, count: number }} opts. Use force = true to force obliteration even
221+
* with active jobs in the queue. Use count with the maximun number of deleted keys per iteration,
222+
* 1000 is the default.
222223
*/
223-
async obliterate(opts?: { force: boolean }) {
224+
async obliterate(opts?: { force?: boolean; count?: number }) {
224225
await this.pause();
225226

226227
let cursor = 0;
227228
do {
228-
cursor = await Scripts.obliterate(this, cursor, {
229+
cursor = await Scripts.obliterate(this, {
229230
force: false,
230-
count: 5000,
231+
count: 1000,
231232
...opts,
232233
});
233234
} while (cursor);

src/classes/scripts.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -474,15 +474,12 @@ export class Scripts {
474474

475475
static async obliterate(
476476
queue: Queue,
477-
cursor: number,
478477
opts: { force: boolean; count: number },
479478
) {
480479
const client = await queue.client;
481480

482-
const pattern = `${queue.opts.prefix}:${queue.name}:*`;
483-
484-
const keys: (string | number)[] = [queue.keys.meta, queue.keys.active];
485-
const args = [cursor, pattern, opts.count, opts.force ? 'force' : null];
481+
const keys: (string | number)[] = [queue.keys.meta, queue.toKey('')];
482+
const args = [opts.count, opts.force ? 'force' : null];
486483

487484
const result = await (<any>client).obliterate(keys.concat(args));
488485
if (result < 0) {

src/commands/obliterate-2.lua

+91-15
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
--[[
2-
Completely obliterates a queue and all of its content
2+
Completely obliterates a queue and all of its contents
33
Input:
44
55
KEYS[1] meta
6-
KEYS[2] active
6+
KEYS[2] base
77
8-
ARGV[1] cursor
9-
ARGV[2] pattern
10-
ARGV[3] count
11-
ARGV[4] force
8+
ARGV[1] count
9+
ARGV[2] force
1210
]]
1311

1412
-- This command completely destroys a queue including all of its jobs, current or past
@@ -18,26 +16,104 @@
1816
-- The queue needs to be "paused" or it will return an error
1917
-- If the queue has currently active jobs then the script by default will return error,
2018
-- however this behaviour can be overrided using the `force` option.
19+
local maxCount = tonumber(ARGV[1])
20+
local count = 0
21+
local baseKey = KEYS[2]
2122

2223
local rcall = redis.call
24+
local function getListItems(keyName)
25+
return rcall('LRANGE', keyName, 0, -1)
26+
end
27+
28+
local function getZSetItems(keyName)
29+
return rcall('ZRANGE', keyName, 0, -1)
30+
end
31+
32+
local function getSetItems(keyName)
33+
return rcall('SMEMBERS', keyName, 0, -1)
34+
end
35+
36+
local function removeKeys(parentKey, keys)
37+
for i, key in ipairs(keys) do
38+
if(count > maxCount) then
39+
return true
40+
end
41+
rcall("DEL", baseKey .. key)
42+
count = count + 1
43+
end
44+
rcall("DEL", parentKey)
45+
return false
46+
end
47+
48+
local function removeLockKeys(keys)
49+
for i, key in ipairs(keys) do
50+
if(count > maxCount) then
51+
return true
52+
end
53+
rcall("DEL", baseKey .. key .. ':lock')
54+
count = count + 1
55+
end
56+
return false
57+
end
2358

2459
-- 1) Check if paused, if not return with error.
2560
if rcall("HEXISTS", KEYS[1], "paused") ~= 1 then
2661
return -1 -- Error, NotPaused
2762
end
2863

2964
-- 2) Check if there are active jobs, if there are and not "force" return error.
30-
local activeKey = KEYS[2]
31-
local active = rcall('LRANGE', activeKey, 0, -1)
32-
if (#active > 0) then
33-
if(ARGV[4] == "") then
65+
local activeKey = baseKey .. 'active'
66+
local activeKeys = getListItems(activeKey)
67+
if (#activeKeys > 0) then
68+
if(ARGV[2] == "") then
3469
return -2 -- Error, ExistsActiveJobs
3570
end
3671
end
3772

38-
local result = rcall("scan", ARGV[1], "MATCH", ARGV[2], "COUNT", ARGV[3])
39-
local cursor = result[1]
40-
local keys = result[2]
41-
for i, key in ipairs(keys) do
42-
rcall("DEL", key)
73+
if(removeLockKeys(activeKeys)) then
74+
return 1
4375
end
76+
77+
if(removeKeys(activeKey, activeKeys)) then
78+
return 1
79+
end
80+
81+
local waitKey = baseKey .. 'paused'
82+
if(removeKeys(waitKey, getListItems(waitKey))) then
83+
return 1
84+
end
85+
86+
local delayedKey = baseKey .. 'delayed'
87+
if(removeKeys(delayedKey, getZSetItems(delayedKey))) then
88+
return 1
89+
end
90+
91+
local completedKey = baseKey .. 'completed'
92+
if(removeKeys(completedKey, getZSetItems(completedKey))) then
93+
return 1
94+
end
95+
96+
local failedKey = baseKey .. 'failed'
97+
if(removeKeys(failedKey, getZSetItems(failedKey))) then
98+
return 1
99+
end
100+
101+
local waitKey = baseKey .. 'wait'
102+
if(removeKeys(waitKey, getListItems(waitKey))) then
103+
return 1
104+
end
105+
106+
local waitKey = baseKey .. 'wait'
107+
if(removeKeys(waitKey, getListItems(waitKey))) then
108+
return 1
109+
end
110+
111+
rcall("DEL", baseKey .. 'priority')
112+
rcall("DEL", baseKey .. 'events')
113+
rcall("DEL", baseKey .. 'delay')
114+
rcall("DEL", baseKey .. 'stalled-check')
115+
rcall("DEL", baseKey .. 'stalled')
116+
rcall("DEL", baseKey .. 'id')
117+
rcall("DEL", baseKey .. 'meta')
118+
119+
return 0

src/test/test_obliterate.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('Obliterate', () => {
2929
await queue.obliterate();
3030

3131
const client = await queue.client;
32-
const keys = await client.keys(`bull:${queue.name}*`);
32+
const keys = await client.keys(`bull:${queue.name}:*`);
3333

3434
expect(keys.length).to.be.eql(0);
3535
});
@@ -56,7 +56,7 @@ describe('Obliterate', () => {
5656

5757
await queue.obliterate();
5858
const client = await queue.client;
59-
const keys = await client.keys(`bull:${queue.name}*`);
59+
const keys = await client.keys(`bull:${queue.name}:*`);
6060
expect(keys.length).to.be.eql(0);
6161

6262
await worker.close();
@@ -87,7 +87,7 @@ describe('Obliterate', () => {
8787
await queue.obliterate();
8888
} catch (err) {
8989
const client = await queue.client;
90-
const keys = await client.keys(`bull:${queue.name}*`);
90+
const keys = await client.keys(`bull:${queue.name}:*`);
9191
expect(keys.length).to.be.not.eql(0);
9292

9393
await worker.close();
@@ -119,7 +119,7 @@ describe('Obliterate', () => {
119119
await job.waitUntilFinished(queueEvents);
120120
await queue.obliterate({ force: true });
121121
const client = await queue.client;
122-
const keys = await client.keys(`bull:${queue.name}*`);
122+
const keys = await client.keys(`bull:${queue.name}:*`);
123123
expect(keys.length).to.be.eql(0);
124124

125125
await worker.close();

0 commit comments

Comments
 (0)