Skip to content

Commit

Permalink
Merge pull request #22 from arusanovt/master
Browse files Browse the repository at this point in the history
More robust `deleteAll`
  • Loading branch information
pasupulaphani committed Jan 12, 2019
2 parents 278b4c5 + 00f7d66 commit f8464b6
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 21 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -14,6 +14,7 @@ Simplistic node redis cache ready can scale with generic-pool support
## Prerequisites

```node >= 4``` This module requires nodejs v4 or above as it has dependencies on es6 components such as Map, Set, Promise etc.
```redis >= 4``` This module requires redis v4 or above as it has dependencies on `UNLINK` and `redis.replicate_commands()` for pattern deletion.

## Featuring
- Out of the box default configuration (but fully configurable)
Expand Down
4 changes: 2 additions & 2 deletions lib/redis_cache.js
Expand Up @@ -82,14 +82,14 @@ RedisCache.prototype.keys = function (pattern) {
* Deletes all keys matching pattern
*
* @param {string} pattern - glob-style patterns/default '*'
* @returns {array} The number of keys that were removed.
* @returns {number} The number of keys that were removed.
*/
RedisCache.prototype.deleteAll = function (pattern) {
if (!pattern || pattern === "") {
pattern = "*";
}

return this.store.deleteAll();
return this.store.deleteAll(pattern);
};

/**
Expand Down
69 changes: 55 additions & 14 deletions lib/redis_store.js
Expand Up @@ -147,7 +147,7 @@ RedisStore.prototype.get = function (key) {
RedisStore.prototype.set = function (key, value, ttlInSeconds) {
value = Array.isArray(value) || isJSON(value, true) ? JSON.stringify(value) : value;

if (! ttlInSeconds) {
if (!ttlInSeconds) {
ttlInSeconds = this.ttlInSeconds;
}
if (ttlInSeconds) {
Expand Down Expand Up @@ -206,25 +206,14 @@ RedisStore.prototype.keys = function (pattern) {
* Deletes all keys matching pattern
*
* @param {string} pattern - glob-style patterns/default '*'
* @returns {array} The number of keys that were removed.
* @returns {number} The number of keys that were removed.
*/
RedisStore.prototype.deleteAll = function (pattern) {
if (!pattern || pattern === "") {
pattern = "*";
}
debug("clearing redis keys: ", pattern);

return this.keys(pattern)
.then(keys => {

if (keys.length > 0) {
debug("deleting keys ", keys);
return this.del(keys);
} else {
debug("no keys exists with pattern: ", pattern);
return Promise.resolve(true);
}
});
return this._executeDeleteAll(pattern);
};

/**
Expand All @@ -235,3 +224,55 @@ RedisStore.prototype.deleteAll = function (pattern) {
RedisStore.prototype.status = function () {
return this.pool.status();
};

/**
* Preloads delete all scripts into redis script cache
* (this script requires redis >= 4.0.0)
* @async
* @returns {Promise<string>} sha1 hash of preloaded function
* @private
*/
RedisStore.prototype._loadDeleteAllScript = function () {
if (!this.__deleteScriptPromise) {
const deleteKeysScript = `
local keys = {};
local done = false;
local cursor = "0";
local deleted = 0;
redis.replicate_commands();
repeat
local result = redis.call("SCAN", cursor, "match", ARGV[1], "count", ARGV[2])
cursor = result[1];
keys = result[2];
for i, key in ipairs(keys) do
deleted = deleted + redis.call("UNLINK", key);
end
if cursor == "0" then
done = true;
end
until done
return deleted;`;
this.__deleteScriptPromise = this.sendCommand("SCRIPT", ["LOAD", deleteKeysScript]);
}
return this.__deleteScriptPromise;
};

/**
* Preloads and execute delete all script
* @async
* @param {string} pattern - glob-style patterns/default '*'
* @returns {Promise<number>} The number of keys that were removed.
* @private
*/
RedisStore.prototype._executeDeleteAll = function (pattern) {
return this._loadDeleteAllScript()
.then(sha1 => this.sendCommand("EVALSHA", [sha1, 0, pattern, 1000]))
.catch(err => {
if (err.code === "NOSCRIPT") {
// We can get here only if server is restarted somehow and cache is deleted
this.__deleteScriptPromise = null;
return this._executeDeleteAll(pattern);
}
throw err;
});
};
9 changes: 8 additions & 1 deletion test/redis_cache.js
Expand Up @@ -175,14 +175,21 @@ describe("redisCache", () => {
const keyValues = {key1: "value1", key2: "value2"};

beforeEach(() => Promise.all(Object.keys(keyValues)
.map(key => cache.set(key, keyValues[key]))));
.map(key => cache.set(key, keyValues[key]))));

it("should delete all the keys", () => {

return cache.deleteAll()
.then(() => cache.keys())
.should.eventually.be.empty();
});

it("should delete all the keys with pattern", () => {

return cache.deleteAll("key1*")
.then(() => cache.keys())
.should.eventually.be.eql(["key2"]);
});
});

describe("getName", () => {
Expand Down
19 changes: 15 additions & 4 deletions test/redis_store.js
Expand Up @@ -366,7 +366,7 @@ describe("redisStore", () => {
it("should delete all the keys", () => {

return store.deleteAll()
.then(v => v.should.be.ok())
.then(v => v.should.be.eql(2))
.then(() => store.keys())
.should.eventually.be.empty();
});
Expand All @@ -375,7 +375,7 @@ describe("redisStore", () => {

return store.deleteAll("key[2]")
.then(v => {
v.should.be.ok();
v.should.be.eql(1);
})
.then(() => store.keys())
.should.eventually.be.not.empty()
Expand All @@ -386,10 +386,21 @@ describe("redisStore", () => {

return store.deleteAll()
.then(v => {
v.should.be.ok();
v.should.be.eql(2);
})
.then(() => store.deleteAll("nonExistingKey"))
.should.eventually.be.ok();
.should.eventually.be.eql(0);
});

it("should delete if function is deleted from cache", () => {

return store.deleteAll()
.then(v => {
v.should.be.eql(2);
})
.then(() => store.sendCommand("script",["flush"]))
.then(() => store.deleteAll())
.should.eventually.be.eql(0);
});
});
});

0 comments on commit f8464b6

Please sign in to comment.