1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

Client Side Caching (#2947)

* CSC POC ontop of Parser

* add csc file that weren't merged after patch

* address review comments

* nits to try and fix github

* last change from review

* Update client-side cache and improve documentation

* Add client side caching RESP3 validation

* Add documentation for RESP and unstableResp3 options

* Add comprehensive cache statistics

The `CacheStats` class provides detailed metrics like hit/miss counts,
load success/failure counts, total load time, and eviction counts.
It also offers derived metrics such as hit/miss rates, load failure rate,
and average load penalty. The design is inspired by Caffeine.

`BasicClientSideCache` now uses a `StatsCounter` to accumulate these
statistics, exposed via a new `stats()` method. The previous
`cacheHits()` and `cacheMisses()` methods have been removed.

A `recordStats` option (default: true) in `ClientSideCacheConfig`
allows disabling statistics collection.

---------

Co-authored-by: Shaya Potter <shaya@redislabs.com>
This commit is contained in:
Bobby I.
2025-05-19 15:11:47 +03:00
committed by GitHub
parent 6f961bd715
commit f01f1014cb
25 changed files with 2330 additions and 101 deletions

View File

@@ -6,6 +6,7 @@ import { ChannelListeners, PUBSUB_TYPE, PubSubTypeListeners } from '../client/pu
import { RedisArgument, RedisFunctions, RedisModules, RedisScripts, RespVersions, TypeMapping } from '../RESP/types';
import calculateSlot from 'cluster-key-slot';
import { RedisSocketOptions } from '../client/socket';
import { BasicPooledClientSideCache, PooledClientSideCacheProvider } from '../client/cache';
interface NodeAddress {
host: string;
@@ -111,6 +112,7 @@ export default class RedisClusterSlots<
replicas = new Array<ShardNode<M, F, S, RESP, TYPE_MAPPING>>();
readonly nodeByAddress = new Map<string, MasterNode<M, F, S, RESP, TYPE_MAPPING> | ShardNode<M, F, S, RESP, TYPE_MAPPING>>();
pubSubNode?: PubSubNode<M, F, S, RESP, TYPE_MAPPING>;
clientSideCache?: PooledClientSideCacheProvider;
#isOpen = false;
@@ -118,12 +120,28 @@ export default class RedisClusterSlots<
return this.#isOpen;
}
#validateOptions(options?: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING>) {
if (options?.clientSideCache && options?.RESP !== 3) {
throw new Error('Client Side Caching is only supported with RESP3');
}
}
constructor(
options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING>,
emit: EventEmitter['emit']
) {
this.#validateOptions(options);
this.#options = options;
this.#clientFactory = RedisClient.factory(options);
if (options?.clientSideCache) {
if (options.clientSideCache instanceof PooledClientSideCacheProvider) {
this.clientSideCache = options.clientSideCache;
} else {
this.clientSideCache = new BasicPooledClientSideCache(options.clientSideCache)
}
}
this.#clientFactory = RedisClient.factory(this.#options);
this.#emit = emit;
}
@@ -164,6 +182,8 @@ export default class RedisClusterSlots<
}
async #discover(rootNode: RedisClusterClientOptions) {
this.clientSideCache?.clear();
this.clientSideCache?.disable();
try {
const addressesInUse = new Set<string>(),
promises: Array<Promise<unknown>> = [],
@@ -219,6 +239,7 @@ export default class RedisClusterSlots<
}
await Promise.all(promises);
this.clientSideCache?.enable();
return true;
} catch (err) {
@@ -314,6 +335,8 @@ export default class RedisClusterSlots<
#createClient(node: ShardNode<M, F, S, RESP, TYPE_MAPPING>, readonly = node.readonly) {
return this.#clientFactory(
this.#clientOptionsDefaults({
clientSideCache: this.clientSideCache,
RESP: this.#options.RESP,
socket: this.#getNodeAddress(node.address) ?? {
host: node.host,
port: node.port