├── .gitignore ├── .jshintignore ├── .jshintrc ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi": false, 3 | "expr": true, 4 | "loopfunc": true, 5 | "curly": false, 6 | "evil": true, 7 | "white": true, 8 | "undef": true, 9 | "node": true, 10 | "predef": [ 11 | "$", 12 | "FormBot", 13 | "socket", 14 | "confirm", 15 | "alert", 16 | "require", 17 | "__dirname", 18 | "process", 19 | "exports", 20 | "console", 21 | "Buffer", 22 | "module" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nathanael C. Fritz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedisScan 2 | 3 | Recursively scans the keyspace of a Redis 2.8+ instance using SCAN, HSCAN, ZSCAN, & SSCAN as well as Lists. 4 | 5 | Fairly safe in a production environment as it does **NOT** use KEYS * to iterate. 6 | 7 | Optionally pass a redis pattern to filter from. 8 | 9 | ## Install 10 | `npm install redisscan` 11 | 12 | ## Example 13 | 14 | ```javascript 15 | var redisScan = require('redisscan'); 16 | var redis = require('redis').createClient(); 17 | 18 | 19 | redisScan({ 20 | redis: redis, 21 | pattern: 'awesome:key:prefix:*', 22 | keys_only: false, 23 | each_callback: function (type, key, subkey, length, value, cb) { 24 | console.log(type, key, subkey, length, value); 25 | cb(); 26 | }, 27 | done_callback: function (err) { 28 | console.log("-=-=-=-=-=--=-=-=-"); 29 | redis.quit(); 30 | } 31 | }); 32 | ``` 33 | 34 | ### redisScan(parameters): 35 | 36 | * `redis`: **required** `node-redis` client instance 37 | * `pattern`: **optional** wildcard key pattern to match, e.g: `some:key:pattern:*` [docs](https://redis.io/commands/scan#the-match-option) 38 | * `keys_only`: **optional** boolean -- returns nothing but keys, no types,lengths,values etc. (defaults to `false`) 39 | * `count_amt`: **optional** positive/non-zero integer -- redis hint for work done per SCAN operation (defaults to 10) [docs](https://redis.io/commands/scan#the-count-option) 40 | * `each_callback`: **required** `function (type, key, subkey, length, value, next)` This is called for every string, and every subkey/value in a container when not using `keys_only`, so outer keys may show up multiple times. 41 | * `type` may be `"string"`, `"hash"`, `"set"`, `"zset"`, `"list"` 42 | * `key` is the redis key 43 | * `subkey` may be `null` or populated with a hash key 44 | * `length` is the length of a set or list 45 | * `value` is the value of the key or subkey when appropriate 46 | * `next()` should be called as a function with no arguments if successful or an `Error` object if not. 47 | * `done_callback`: **optional** function called when scanning completes with one argument, and `Error` object if an error ws raised 48 | 49 | ## Note/Warning 50 | 51 | If values are changing, there is no guarantee on value integrity. This is not atomic. 52 | I recommend using a lock pattern with this function. 53 | 54 | 55 | 56 | License MIT (c) 2014 Nathanael C. Fritz 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var redis, pattern, keys_only, count_amt, each_callback; 3 | 4 | module.exports = function (args) { 5 | redis = args.redis; 6 | pattern = args.pattern || pattern; 7 | keys_only = args.keys_only; 8 | count_amt = args.count_amt; 9 | each_callback = args.each_callback; 10 | 11 | genericScan(args.cmd, args.key, args.done_callback); 12 | }; 13 | 14 | var genericScan = function(cmd, key, callback) { 15 | 16 | cmd = cmd || 'SCAN'; 17 | key = key || null; 18 | callback = callback || function(){}; 19 | 20 | var iter = '0'; 21 | async.doWhilst( 22 | function (acb) { 23 | //scan with the current iterator, matching the given pattern 24 | var args = [iter]; 25 | if (cmd === 'SCAN') { 26 | if (pattern) { 27 | args = args.concat(['MATCH', pattern]); 28 | } 29 | if (count_amt){ 30 | args = args.concat(['COUNT', count_amt]); 31 | } 32 | } else { 33 | args = [key].concat(args); 34 | } 35 | redis.send_command(cmd, args, function (err, result) { 36 | 37 | var idx = 0; 38 | var keys; 39 | if (err) { 40 | acb(err); 41 | } else { 42 | //update the iterator 43 | iter = result[0]; 44 | //each key, limit to 5 pending callbacks at a time 45 | if (['SCAN', 'SSCAN'].indexOf(cmd) !== -1) { 46 | 47 | async.eachSeries(result[1], function (subkey, next) { 48 | if (keys_only){ 49 | each_callback(null, subkey, null, null, null, next); 50 | } else if (cmd === 'SCAN') { 51 | redis.type(subkey, function (err, sresult) { 52 | var value; 53 | if (err) { 54 | next(err); 55 | } else { 56 | if (sresult === 'string') { 57 | redis.get(subkey, function (err, value) { 58 | if (err) { 59 | next(err); 60 | } else { 61 | each_callback('string', subkey, null, null, value, next); 62 | } 63 | }); 64 | } else if (sresult === 'hash') { 65 | genericScan('HSCAN', subkey, next); 66 | } else if (sresult === 'set') { 67 | genericScan('SSCAN', subkey, next); 68 | } else if (sresult === 'zset') { 69 | genericScan('ZSCAN', subkey, next); 70 | } else if (sresult === 'list') { 71 | //each_callback('list', subkey, null, null, next); 72 | redis.llen(subkey, function (err, length) { 73 | var idx = 0; 74 | length = parseInt(length); 75 | if (err) { 76 | next(err); 77 | } else { 78 | async.doWhilst( 79 | function (wcb) { 80 | redis.lindex(subkey, idx, function (err, value) { 81 | each_callback('list', subkey, idx, length, value, wcb); 82 | }); 83 | }, 84 | function () { idx++; return idx < length; }, 85 | function (err) { 86 | next(err); 87 | } 88 | ); 89 | } 90 | }); 91 | } 92 | } 93 | }); 94 | } else if (cmd === 'SSCAN') { 95 | each_callback('set', key, idx, null, subkey, next); 96 | } 97 | idx++; 98 | }, 99 | function (err) { 100 | //done with this scan iterator; on to the next 101 | acb(err); 102 | }); 103 | } else { 104 | var idx2 = 0; 105 | async.doWhilst( 106 | function (ecb) { 107 | var subkey = result[1][idx2]; 108 | var value = result[1][idx2+1]; 109 | if (cmd === 'HSCAN') { 110 | each_callback('hash', key, subkey, null, value, ecb); 111 | } else if (cmd === 'ZSCAN') { 112 | each_callback('zset', key, value, null, subkey, ecb); 113 | } 114 | }, 115 | function () {idx2 += 2; return idx2 < result[1].length;}, 116 | function (err) { 117 | acb(err); 118 | } 119 | ); 120 | } 121 | } 122 | }); 123 | }, 124 | //test to see if iterator is done 125 | function () { return iter != '0'; }, 126 | //done 127 | function (err) { 128 | callback(err); 129 | // done_callback(err); 130 | } 131 | ); 132 | }; 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redisscan", 3 | "version": "2.0.0", 4 | "homepage": "https://github.com/fritzy/node-redisscan", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:fritzy/node-redisscan.git" 8 | }, 9 | "description": "Scan through all redis keys and containers, calling back each value individually.", 10 | "dependencies": { 11 | "redis": "^2.7.1", 12 | "async": "~0.2.10" 13 | }, 14 | "devDependencies": { 15 | "precommit-hook": "*" 16 | }, 17 | "scripts": { 18 | "lint": "jshint .", 19 | "validate": "npm ls" 20 | }, 21 | "main": "index.js", 22 | "pre-commit": [ 23 | "lint", 24 | "validate", 25 | "test" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var redisScan = require('./index.js'); 2 | var redis = require('redis').createClient(); 3 | 4 | redisScan({ 5 | redis: redis, 6 | each_callback: function (type, key, subkey, length, value, cb) { 7 | console.log(type, key, subkey, length, value); 8 | cb(); 9 | }, 10 | done_callback: function (err) { 11 | console.log("-=-=-=-=-=--=-=-=-"); 12 | redis.quit(); 13 | } 14 | }); 15 | --------------------------------------------------------------------------------