Skip to content

Commit

Permalink
CSC POC ontop of Parser
Browse files Browse the repository at this point in the history
  • Loading branch information
sjpotter committed Oct 15, 2024
1 parent c39f377 commit c6238a2
Show file tree
Hide file tree
Showing 17 changed files with 1,051 additions and 47 deletions.
3 changes: 3 additions & 0 deletions packages/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import RedisSentinel from './lib/sentinel';
export { RedisSentinelOptions, RedisSentinelType } from './lib/sentinel/types';
export const createSentinel = RedisSentinel.create;

import { BasicClientSideCache, BasicPooledClientSideCache } from './lib/client/cache';
export { BasicClientSideCache, BasicPooledClientSideCache };

// export { GeoReplyWith } from './lib/commands/generic-transformers';

// export { SetOptions } from './lib/commands/SET';
Expand Down
64 changes: 64 additions & 0 deletions packages/client/lib/client/README-cache.md
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});
```
184 changes: 184 additions & 0 deletions packages/client/lib/client/cache.spec.ts
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
}
})
});
});
Loading

0 comments on commit c6238a2

Please sign in to comment.