-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
1,051 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Client Side Caching Support | ||
|
||
Client Side Caching enables Redis Servers and Clients to work together to enable a client to cache results from command sent to a server and be informed by the server when the cached result is no longer valid. | ||
|
||
## Usage | ||
|
||
node-redis supports two ways of instantiating client side caching support | ||
|
||
Note: Client Side Caching is only supported with RESP3. | ||
|
||
### Anonymous Cache | ||
|
||
```javascript | ||
const client = createClient({RESP: 3, clientSideCache: {ttl: 0, maxEntries: 0, lru: false}}) | ||
``` | ||
|
||
In this instance, the cache is opaque to the user, and they have no control over it. | ||
|
||
### Controllable Cache | ||
|
||
```javascript | ||
const ttl = 0, maxEntries = 0, lru = false; | ||
const cache = new BasicClientSideCache(ttl, maxEntries, lru); | ||
const client = createClient({RESP: 3, clientSideCache: cache}); | ||
``` | ||
|
||
In this instance, the user has full control over the cache, as they have access to the cache object. | ||
|
||
They can manually invalidate keys | ||
|
||
```javascript | ||
cache.invalidate(key); | ||
``` | ||
|
||
they can clear the entire cache | ||
g | ||
```javascript | ||
cache.clear(); | ||
``` | ||
|
||
as well as get cache metrics | ||
|
||
```typescript | ||
const hits: number = cache.cacheHits(); | ||
const misses: number = cache.cacheMisses(); | ||
``` | ||
|
||
## Pooled Caching | ||
|
||
Similar to individual clients, node-redis also supports caching for its pooled client object, with the cache being able to be instantiated in an anonymous manner or a controllable manner. | ||
|
||
### Anonymous Cache | ||
|
||
```javascript | ||
const client = createClientPool({RESP: 3}, {clientSideCache: {ttl: 0, maxEntries: 0, lru: false}, minimum: 8}); | ||
``` | ||
|
||
### Controllable Cache | ||
|
||
```javascript | ||
const ttl = 0, maxEntries = 0, lru = false; | ||
const cache = new BasicPooledClientSideCache(ttl, maxEntries, lru); | ||
const client = createClientPool({RESP: 3}, {clientSideCache: cache, minimum: 8}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import assert from "assert"; | ||
import testUtils, { GLOBAL } from "../test-utils" | ||
import { BasicClientSideCache, BasicPooledClientSideCache } from "./cache" | ||
import { REDIS_FLUSH_MODES } from "../commands/FLUSHALL"; | ||
|
||
describe("Client Side Cache", () => { | ||
describe('Basic Cache', () => { | ||
const csc = new BasicClientSideCache(); | ||
|
||
testUtils.testWithClient('Basic Cache Miss', async client => { | ||
csc.clear(); | ||
|
||
await client.set("x", 1); | ||
await client.get("x"); | ||
|
||
assert.equal(1, csc.cacheMisses(), "Cache Misses"); | ||
assert.equal(0, csc.cacheHits(), "Cache Hits"); | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
clientOptions: { | ||
RESP: 3, | ||
clientSideCache: csc | ||
} | ||
}); | ||
|
||
testUtils.testWithClient('Basic Cache Hit', async client => { | ||
csc.clear(); | ||
|
||
await client.set("x", 1); | ||
await client.get("x"); | ||
await client.get("x"); | ||
|
||
assert.equal(1, csc.cacheMisses(), "Cache Misses"); | ||
assert.equal(1, csc.cacheHits(), "Cache Hits"); | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
clientOptions: { | ||
RESP: 3, | ||
clientSideCache: csc | ||
} | ||
}); | ||
|
||
testUtils.testWithClient('Basic Cache Clear', async client => { | ||
csc.clear(); | ||
|
||
await client.set("x", 1); | ||
await client.get("x"); | ||
csc.clear(); | ||
await client.get("x"); | ||
|
||
assert.equal(1, csc.cacheMisses(), "Cache Misses"); | ||
assert.equal(0, csc.cacheHits(), "Cache Hits"); | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
clientOptions: { | ||
RESP: 3, | ||
clientSideCache: csc | ||
} | ||
}); | ||
|
||
testUtils.testWithClient('Null Invalidate acts as clear', async client => { | ||
csc.clear(); | ||
|
||
await client.set("x", 1); | ||
await client.get("x"); | ||
csc.invalidate(null); | ||
await client.get("x"); | ||
|
||
assert.equal(2, csc.cacheMisses(), "Cache Misses"); | ||
assert.equal(0, csc.cacheHits(), "Cache Hits"); | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
clientOptions: { | ||
RESP: 3, | ||
clientSideCache: csc | ||
} | ||
}); | ||
|
||
testUtils.testWithClient('flushdb causes an invalidate null', async client => { | ||
csc.clear(); | ||
|
||
await client.set("x", 1); | ||
await client.get("x"); | ||
await client.flushDb(REDIS_FLUSH_MODES.SYNC); | ||
await client.get("x"); | ||
|
||
assert.equal(2, csc.cacheMisses(), "Cache Misses"); | ||
assert.equal(0, csc.cacheHits(), "Cache Hits"); | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
clientOptions: { | ||
RESP: 3, | ||
clientSideCache: csc | ||
} | ||
}); | ||
|
||
testUtils.testWithClient('Basic Cache Invalidate', async client => { | ||
csc.clear(); | ||
|
||
await client.set("x", 1); | ||
await client.get("x"); | ||
await client.set("x", 2); | ||
await client.get("x"); | ||
|
||
assert.equal(2, csc.cacheMisses(), "Cache Misses"); | ||
assert.equal(0, csc.cacheHits(), "Cache Hits"); | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
clientOptions: { | ||
RESP: 3, | ||
clientSideCache: csc | ||
} | ||
}); | ||
}); | ||
|
||
describe("Pooled Cache", () => { | ||
const csc = new BasicPooledClientSideCache(); | ||
|
||
testUtils.testWithClientPool('Basic Cache Miss and Clear', async client => { | ||
csc.clear(); | ||
|
||
await client.set("x", 1); | ||
await client.get("x"); | ||
|
||
assert.equal(1, csc.cacheMisses(), "Cache Misses"); | ||
assert.equal(0, csc.cacheHits(), "Cache Hits"); | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
poolOptions: { | ||
minimum: 5, | ||
maximum: 5, | ||
acquireTimeout: 0, | ||
cleanupDelay: 1, | ||
clientSideCache: csc | ||
} | ||
}) | ||
|
||
testUtils.testWithClientPool('Basic Cache Hit', async client => { | ||
csc.clear(); | ||
|
||
await client.set("x", 1); | ||
await client.get("x"); | ||
await client.get("x"); | ||
await client.get("x"); | ||
|
||
assert.equal(1, csc.cacheMisses(), "Cache Misses"); | ||
assert.equal(2, csc.cacheHits(), "Cache Hits"); | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
poolOptions: { | ||
minimum: 5, | ||
maximum: 5, | ||
acquireTimeout: 0, | ||
cleanupDelay: 1, | ||
clientSideCache: csc | ||
} | ||
}) | ||
|
||
testUtils.testWithClientPool('Basic Cache Invalidate', async client => { | ||
csc.clear(); | ||
|
||
await client.set("x", 1); | ||
await client.get("x"); | ||
csc.invalidate("x"); | ||
await client.get("x"); | ||
csc.invalidate("x"); | ||
await client.get("x"); | ||
csc.invalidate("x"); | ||
await client.get("x"); | ||
|
||
assert.equal(4, csc.cacheMisses(), "Cache Misses"); | ||
assert.equal(0, csc.cacheHits(), "Cache Hits"); | ||
}, { | ||
...GLOBAL.SERVERS.OPEN, | ||
poolOptions: { | ||
minimum: 5, | ||
maximum: 5, | ||
acquireTimeout: 0, | ||
cleanupDelay: 1, | ||
clientSideCache: csc | ||
} | ||
}) | ||
}); | ||
}); |
Oops, something went wrong.