forked from GigSalad/node-redis-scan
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathredis-scan.js
118 lines (108 loc) · 3.65 KB
/
redis-scan.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
* Given a Redis instance this class provides a couple methods for easily
* scanning the entire keyspace.
*
* @name RedisScan
* @class
* @param {Object} redis - An instance of Node Redis:
* https://github.com/NodeRedis/node_redis
*/
class RedisScan {
constructor(redis) {
this.redisClient = redis;
}
/**
* Scans the entire Redis keyspace to find matching keys. The matching
* keys are returned in sets via the `eachScanCallback` function which is
* called after each iteration of the Redis `SCAN` command. This method is
* useful if you want to operate on chunks and/or perform work with results
* at the same time as the keyspace is being scanned. That is, you want to
* be efficient while searching through or expecting to match on tens of
* thousands or even millions of keys.
*
* @name eachScan
*
* @method
*
* @param {String} pattern - A Redis glob-style string pattern of keys to
* match.
*
* @param {Function} eachScanCallback - A function called after each
* call to the Redis `SCAN` command. Invoked with (matchingKeys). If this
* function returns `true` (NOT truthy) the scan will be cancelled and
* `callback` will be called with the keys that were scanned.
*
* @param {Function} [callback] - A function called after the full scan has
* completed and all keys have been returned.
*
* @param {Number} [count] - The COUNT parameter. Defaults to 10 (the Redis default)
*/
eachScan(pattern, eachScanCallback, callback, count = 10) {
let matchingKeysCount = 0;
// Because we're using the `scan()` method of the node-redis library
// a recursive function seems easiest here.
const recursiveScan = (cursor = 0) => {
// Build a Redis `SCAN` command using the `MATCH` option.
// See: https://redis.io/commands/scan#the-match-option
this.redisClient.scan(cursor, 'MATCH', pattern, 'COUNT', count, (err, data) => {
if (err) {
callback(err);
} else {
// client.scan() returns an array with two elements. The
// first element is the next cursor and the second is an
// array of matching keys (which might be empty). We'll
// destructure this into two variables.
const [cursor, matchingKeys] = data;
matchingKeysCount += matchingKeys.length;
if (eachScanCallback(matchingKeys) === true) {
callback(null, matchingKeysCount);
return;
}
// We're done once Redis returns 0 for the next cursor.
if (cursor === '0') {
callback(null, matchingKeysCount);
} else {
// Otherwise, call this function again AKA recurse
// and pass the next cursor.
recursiveScan(cursor);
}
}
});
};
// Begin the scan.
recursiveScan();
}
/**
* Scans the entire Redis keyspace to find matching keys. The matching
* keys are returned as an array of strings via callback. Depending on
* the size of your keyspace this function might not be ideal for
* performance. It may take tens of seconds or more for Redis databases
* with huge keyspaces (i.e. millions of keys or more).
*
* @name scan
*
* @method
*
* @param {String} pattern - A Redis glob-style string pattern of keys to
* match.
*
* @param {Function} [callback] - A function called after the full scan
* of the Redis keyspace completes having searched for the given pattern.
* Invoked with (err, keys).
*/
scan(pattern, callback) {
let keys = [];
// Collect all our keys into a single array using the `eachScan()`
// method from above.
this.eachScan(pattern, (matchingKeys) => {
keys = keys.concat(matchingKeys);
}, (err, count) => {
if (err) {
callback(err);
} else {
callback(null, keys);
}
});
}
}
module.exports = RedisScan;