Skip to content

Commit a11780b

Browse files
committed
fix(client): use smaller cache key
Use smaller cache key when creating clients. `attachConfig` only cares abour certain properties, so we only need to cache them: `unstableResp3`, `RESP`, `modules`, `functions` and `scripts` If users provide different socket options etc, the cache should ignore them because they are irrelevant from the perspective of `attachConfig`
1 parent c3d8fe8 commit a11780b

File tree

4 files changed

+51
-25
lines changed

4 files changed

+51
-25
lines changed

packages/client/lib/client/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,8 @@ export default class RedisClient<
319319
RESP extends RespVersions = 2
320320
>(config?: CommanderConfig<M, F, S, RESP>) {
321321

322-
323-
let Client = RedisClient.#SingleEntryCache.get(config);
322+
const cacheKey = SingleEntryCache.getCacheKey(config);
323+
let Client = RedisClient.#SingleEntryCache.get(cacheKey);
324324
if (!Client) {
325325
Client = attachConfig({
326326
BaseClass: RedisClient,
@@ -334,7 +334,7 @@ export default class RedisClient<
334334

335335
Client.prototype.Multi = RedisClientMultiCommand.extend(config);
336336

337-
RedisClient.#SingleEntryCache.set(config, Client);
337+
RedisClient.#SingleEntryCache.set(cacheKey, Client);
338338
}
339339

340340
return <TYPE_MAPPING extends TypeMapping = {}>(

packages/client/lib/client/pool.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ export interface RedisPoolOptions {
2626
acquireTimeout: number;
2727
/**
2828
* The delay in milliseconds before a cleanup operation is performed on idle clients.
29-
*
30-
* After this delay, the pool will check if there are too many idle clients and destroy
29+
*
30+
* After this delay, the pool will check if there are too many idle clients and destroy
3131
* excess ones to maintain optimal pool size.
3232
*/
3333
cleanupDelay: number;
3434
/**
3535
* Client Side Caching configuration for the pool.
36-
*
37-
* Enables Redis Servers and Clients to work together to cache results from commands
36+
*
37+
* Enables Redis Servers and Clients to work together to cache results from commands
3838
* sent to a server. The server will notify the client when cached results are no longer valid.
3939
* In pooled mode, the cache is shared across all clients in the pool.
40-
*
40+
*
4141
* Note: Client Side Caching is only supported with RESP3.
42-
*
42+
*
4343
* @example Anonymous cache configuration
4444
* ```
4545
* const client = createClientPool({RESP: 3}, {
@@ -51,7 +51,7 @@ export interface RedisPoolOptions {
5151
* minimum: 5
5252
* });
5353
* ```
54-
*
54+
*
5555
* @example Using a controllable cache
5656
* ```
5757
* const cache = new BasicPooledClientSideCache({
@@ -68,11 +68,11 @@ export interface RedisPoolOptions {
6868
clientSideCache?: PooledClientSideCacheProvider | ClientSideCacheConfig;
6969
/**
7070
* Enable experimental support for RESP3 module commands.
71-
*
72-
* When enabled, allows the use of module commands that have been adapted
73-
* for the RESP3 protocol. This is an unstable feature and may change in
71+
*
72+
* When enabled, allows the use of module commands that have been adapted
73+
* for the RESP3 protocol. This is an unstable feature and may change in
7474
* future versions.
75-
*
75+
*
7676
* @default false
7777
*/
7878
unstableResp3Modules?: boolean;
@@ -169,7 +169,8 @@ export class RedisClientPool<
169169
options?: Partial<RedisPoolOptions>
170170
) {
171171

172-
let Pool = RedisClientPool.#SingleEntryCache.get(clientOptions);
172+
const cacheKey = SingleEntryCache.getCacheKey(clientOptions);
173+
let Pool = RedisClientPool.#SingleEntryCache.get(cacheKey);
173174
if(!Pool) {
174175
Pool = attachConfig({
175176
BaseClass: RedisClientPool,
@@ -181,7 +182,7 @@ export class RedisClientPool<
181182
config: clientOptions
182183
});
183184
Pool.prototype.Multi = RedisClientMultiCommand.extend(clientOptions);
184-
RedisClientPool.#SingleEntryCache.set(clientOptions, Pool);
185+
RedisClientPool.#SingleEntryCache.set(cacheKey, Pool);
185186
}
186187

187188
// returning a "proxy" to prevent the namespaces._self to leak between "proxies"
@@ -392,7 +393,7 @@ export class RedisClientPool<
392393

393394
this._self.#returnClient(node);
394395
}
395-
396+
396397
execute<T>(fn: PoolTask<M, F, S, RESP, TYPE_MAPPING, T>) {
397398
return new Promise<Awaited<T>>((resolve, reject) => {
398399
const client = this._self.#idleClients.shift(),
@@ -502,29 +503,29 @@ export class RedisClientPool<
502503
if (!this._self.#isOpen) return; // TODO: throw err?
503504

504505
this._self.#isClosing = true;
505-
506+
506507
try {
507508
const promises = [];
508509

509510
for (const client of this._self.#idleClients) {
510511
promises.push(client.close());
511512
}
512-
513+
513514
for (const client of this._self.#clientsInUse) {
514515
promises.push(client.close());
515516
}
516517

517518
await Promise.all(promises);
518519

519520
this.#clientSideCache?.onPoolClose();
520-
521+
521522
this._self.#idleClients.reset();
522523
this._self.#clientsInUse.reset();
523524
} catch (err) {
524-
525+
525526
} finally {
526527
this._self.#isClosing = false;
527-
}
528+
}
528529
}
529530

530531
destroy() {

packages/client/lib/cluster/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ export default class RedisCluster<
260260
// POLICIES extends CommandPolicies = {}
261261
>(config?: ClusterCommander<M, F, S, RESP, TYPE_MAPPING/*, POLICIES*/>) {
262262

263-
let Cluster = RedisCluster.#SingleEntryCache.get(config);
263+
const cacheKey = SingleEntryCache.getCacheKey(config);
264+
let Cluster = RedisCluster.#SingleEntryCache.get(cacheKey);
264265
if (!Cluster) {
265266
Cluster = attachConfig({
266267
BaseClass: RedisCluster,
@@ -273,7 +274,7 @@ export default class RedisCluster<
273274
});
274275

275276
Cluster.prototype.Multi = RedisClusterMultiCommand.extend(config);
276-
RedisCluster.#SingleEntryCache.set(config, Cluster);
277+
RedisCluster.#SingleEntryCache.set(cacheKey, Cluster);
277278
}
278279

279280
return (options?: Omit<RedisClusterOptions, keyof Exclude<typeof config, undefined>>) => {

packages/client/lib/single-entry-cache.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CommanderConfig } from "./RESP/types";
2+
13
export default class SingleEntryCache<K, V> {
24
#cached?: V;
35
#serializedKey?: string;
@@ -20,6 +22,28 @@ export default class SingleEntryCache<K, V> {
2022
this.#cached = obj;
2123
this.#serializedKey = JSON.stringify(keyObj, makeCircularReplacer());
2224
}
25+
26+
/**
27+
* Generates a cache key based on the provided CommanderConfig.
28+
*
29+
* @param config - The CommanderConfig object to derive the cache key from.
30+
* @returns An object representing the cache key.
31+
*
32+
* @remarks
33+
* The cache key includes properties like unstableResp3, RESP, modules, functions, and scripts
34+
* from the CommanderConfig.
35+
*/
36+
static getCacheKey (config?: CommanderConfig<any, any, any, any>): object {
37+
const key: Partial<CommanderConfig<any, any, any, any>> = {};
38+
if(config) {
39+
key.unstableResp3 = config.unstableResp3;
40+
key.RESP = config.RESP;
41+
key.modules = config.modules;
42+
key.functions = config.functions;
43+
key.scripts = config.scripts;
44+
}
45+
return key;
46+
}
2347
}
2448

2549
function makeCircularReplacer() {
@@ -34,4 +58,4 @@ function makeCircularReplacer() {
3458
}
3559
return value;
3660
}
37-
}
61+
}

0 commit comments

Comments
 (0)