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

@@ -16,6 +16,7 @@ import { RedisVariadicArgument } from '../commands/generic-transformers';
import { WaitQueue } from './wait-queue';
import { TcpNetConnectOpts } from 'node:net';
import { RedisTcpSocketOptions } from '../client/socket';
import { BasicPooledClientSideCache, PooledClientSideCacheProvider } from '../client/cache';
interface ClientInfo {
id: number;
@@ -301,6 +302,10 @@ export default class RedisSentinel<
#masterClientCount = 0;
#masterClientInfo?: ClientInfo;
get clientSideCache() {
return this._self.#internal.clientSideCache;
}
constructor(options: RedisSentinelOptions<M, F, S, RESP, TYPE_MAPPING>) {
super();
@@ -617,7 +622,7 @@ class RedisSentinelInternal<
readonly #name: string;
readonly #nodeClientOptions: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>;
readonly #sentinelClientOptions: RedisClientOptions<typeof RedisSentinelModule, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>;
readonly #sentinelClientOptions: RedisClientOptions<typeof RedisSentinelModule, RedisFunctions, RedisScripts, RespVersions, TypeMapping, RedisTcpSocketOptions>;
readonly #scanInterval: number;
readonly #passthroughClientErrorEvents: boolean;
@@ -650,9 +655,22 @@ class RedisSentinelInternal<
#trace: (msg: string) => unknown = () => { };
#clientSideCache?: PooledClientSideCacheProvider;
get clientSideCache() {
return this.#clientSideCache;
}
#validateOptions(options?: RedisSentinelOptions<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: RedisSentinelOptions<M, F, S, RESP, TYPE_MAPPING>) {
super();
this.#validateOptions(options);
this.#name = options.name;
this.#sentinelRootNodes = Array.from(options.sentinelRootNodes);
@@ -662,11 +680,21 @@ class RedisSentinelInternal<
this.#scanInterval = options.scanInterval ?? 0;
this.#passthroughClientErrorEvents = options.passthroughClientErrorEvents ?? false;
this.#nodeClientOptions = options.nodeClientOptions ? Object.assign({} as RedisClientOptions<M, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>, options.nodeClientOptions) : {};
this.#nodeClientOptions = (options.nodeClientOptions ? {...options.nodeClientOptions} : {}) as RedisClientOptions<M, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>;
if (this.#nodeClientOptions.url !== undefined) {
throw new Error("invalid nodeClientOptions for Sentinel");
}
if (options.clientSideCache) {
if (options.clientSideCache instanceof PooledClientSideCacheProvider) {
this.#clientSideCache = this.#nodeClientOptions.clientSideCache = options.clientSideCache;
} else {
const cscConfig = options.clientSideCache;
this.#clientSideCache = this.#nodeClientOptions.clientSideCache = new BasicPooledClientSideCache(cscConfig);
// this.#clientSideCache = this.#nodeClientOptions.clientSideCache = new PooledNoRedirectClientSideCache(cscConfig);
}
}
this.#sentinelClientOptions = options.sentinelClientOptions ? Object.assign({} as RedisClientOptions<typeof RedisSentinelModule, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>, options.sentinelClientOptions) : {};
this.#sentinelClientOptions.modules = RedisSentinelModule;
@@ -904,6 +932,8 @@ class RedisSentinelInternal<
this.#isReady = false;
this.#clientSideCache?.onPoolClose();
if (this.#scanTimer) {
clearInterval(this.#scanTimer);
this.#scanTimer = undefined;
@@ -952,6 +982,8 @@ class RedisSentinelInternal<
this.#isReady = false;
this.#clientSideCache?.onPoolClose();
if (this.#scanTimer) {
clearInterval(this.#scanTimer);
this.#scanTimer = undefined;