Skip to content

Commit 57dc361

Browse files
committed
feat: add cache factory & drop redis fallback & unify client int (#4558)
Signed-off-by: Mariusz Jasuwienas <[email protected]>
1 parent e1a74b9 commit 57dc361

25 files changed

+214
-300
lines changed

packages/relay/src/lib/clients/cache/ICacheClient.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ export interface ICacheClient {
44
keys(pattern: string, callingMethod: string): Promise<string[]>;
55
get(key: string, callingMethod: string): Promise<any>;
66
set(key: string, value: any, callingMethod: string, ttl?: number): Promise<void>;
7-
multiSet(keyValuePairs: Record<string, any>, callingMethod: string): Promise<void>;
7+
multiSet(keyValuePairs: Record<string, any>, callingMethod: string, ttl?: number | undefined): Promise<void>;
88
pipelineSet(keyValuePairs: Record<string, any>, callingMethod: string, ttl?: number | undefined): Promise<void>;
99
delete(key: string, callingMethod: string): Promise<void>;
1010
clear(): Promise<void>;
11+
incrBy(key: string, amount: number, callingMethod: string): Promise<number>;
12+
rPush(key: string, value: any, callingMethod: string): Promise<number>;
13+
lRange<T = any>(key: string, start: number, end: number, callingMethod: string): Promise<T[]>;
1114
}

packages/relay/src/lib/clients/cache/IRedisCacheClient.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/relay/src/lib/clients/cache/localLRUCache.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export class LocalLRUCache implements ICacheClient {
7272
* @constructor
7373
* @param {Logger} logger - The logger instance to be used for logging.
7474
* @param {Registry} register - The registry instance used for metrics tracking.
75+
* @param {Set<string>} reservedKeys - These are the cache keys delegated to the reserved cache.
7576
*/
7677
public constructor(logger: Logger, register: Registry, reservedKeys: Set<string> = new Set()) {
7778
this.cache = new LRUCache(this.options);
@@ -137,8 +138,9 @@ export class LocalLRUCache implements ICacheClient {
137138
* @param key - The key to check the remaining TTL for.
138139
* @param callingMethod - The name of the method calling the cache.
139140
* @returns The remaining TTL in milliseconds.
141+
* @private
140142
*/
141-
public async getRemainingTtl(key: string, callingMethod: string): Promise<number> {
143+
private async getRemainingTtl(key: string, callingMethod: string): Promise<number> {
142144
const prefixedKey = this.prefixKey(key);
143145
const cache = this.getCacheInstance(key);
144146
const remainingTtl = cache.getRemainingTTL(prefixedKey); // in milliseconds
@@ -280,6 +282,70 @@ export class LocalLRUCache implements ICacheClient {
280282
return matchingKeys.map((key) => key.substring(LocalLRUCache.CACHE_KEY_PREFIX.length));
281283
}
282284

285+
/**
286+
* Increments a value in the cache.
287+
*
288+
* @param key The key to increment
289+
* @param amount The amount to increment by
290+
* @param callingMethod The name of the calling method
291+
* @returns The value of the key after incrementing
292+
*/
293+
public async incrBy(key: string, amount: number, callingMethod: string): Promise<number> {
294+
const value = await this.get(key, callingMethod);
295+
const newValue = value + amount;
296+
const remainingTtl = await this.getRemainingTtl(key, callingMethod);
297+
await this.set(key, newValue, callingMethod, remainingTtl);
298+
return newValue;
299+
}
300+
301+
/**
302+
* Retrieves a range of elements from a list in the cache.
303+
*
304+
* @param key The key of the list
305+
* @param start The start index
306+
* @param end The end index
307+
* @param callingMethod The name of the calling method
308+
* @returns The list of elements in the range
309+
*/
310+
public async lRange(key: string, start: number, end: number, callingMethod: string): Promise<any[]> {
311+
const values = (await this.get(key, callingMethod)) ?? [];
312+
if (!Array.isArray(values)) {
313+
throw new Error(`Value at key ${key} is not an array`);
314+
}
315+
if (end < 0) {
316+
end = values.length + end;
317+
}
318+
return values.slice(start, end + 1);
319+
}
320+
321+
/**
322+
* Pushes a value to the end of a list in the cache.
323+
*
324+
* @param key The key of the list
325+
* @param value The value to push
326+
* @param callingMethod The name of the calling method
327+
* @returns The length of the list after pushing
328+
*/
329+
public async rPush(key: string, value: any, callingMethod: string): Promise<number> {
330+
const values = (await this.get(key, callingMethod)) ?? [];
331+
if (!Array.isArray(values)) {
332+
throw new Error(`Value at key ${key} is not an array`);
333+
}
334+
values.push(value);
335+
const remainingTtl = await this.getRemainingTtl(key, callingMethod);
336+
await this.set(key, values, callingMethod, remainingTtl);
337+
return values.length;
338+
}
339+
340+
/**
341+
* Returns the appropriate cache instance for the given key.
342+
* If a reserved cache exists and the key is marked as reserved,
343+
* the reserved cache is returned; otherwise the default cache is used.
344+
*
345+
* @param key - The cache key being accessed.
346+
* @returns The selected cache instance
347+
* @private
348+
*/
283349
private getCacheInstance(key: string): LRUCache<string, any> {
284350
return this.reservedCache && this.reservedKeys.has(key) ? this.reservedCache : this.cache;
285351
}

packages/relay/src/lib/clients/cache/redisCache.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
44
import { Logger } from 'pino';
5-
import { Registry } from 'prom-client';
65
import { RedisClientType } from 'redis';
76

87
import { Utils } from '../../../utils';
9-
import { IRedisCacheClient } from './IRedisCacheClient';
8+
import { ICacheClient } from './ICacheClient';
109

1110
/**
1211
* A class that provides caching functionality using Redis.
1312
*/
14-
export class RedisCache implements IRedisCacheClient {
13+
export class RedisCache implements ICacheClient {
1514
/**
1615
* Prefix used to namespace all keys managed by this cache.
1716
*
@@ -29,6 +28,7 @@ export class RedisCache implements IRedisCacheClient {
2928
private readonly options = {
3029
// Max time to live in ms, for items before they are considered stale.
3130
ttl: ConfigService.get('CACHE_TTL'),
31+
multiSetEnabled: ConfigService.get('MULTI_SET'),
3232
};
3333

3434
/**
@@ -37,12 +37,6 @@ export class RedisCache implements IRedisCacheClient {
3737
*/
3838
private readonly logger: Logger;
3939

40-
/**
41-
* The metrics register used for metrics tracking.
42-
* @private
43-
*/
44-
private readonly register: Registry;
45-
4640
/**
4741
* The Redis client.
4842
* @private
@@ -53,11 +47,10 @@ export class RedisCache implements IRedisCacheClient {
5347
* Creates an instance of `RedisCache`.
5448
*
5549
* @param {Logger} logger - The logger instance.
56-
* @param {Registry} register - The metrics registry.
50+
* @param {RedisClientType} client
5751
*/
58-
public constructor(logger: Logger, register: Registry, client: RedisClientType) {
52+
public constructor(logger: Logger, client: RedisClientType) {
5953
this.logger = logger;
60-
this.register = register;
6154
this.client = client;
6255
}
6356

@@ -129,9 +122,11 @@ export class RedisCache implements IRedisCacheClient {
129122
*
130123
* @param keyValuePairs - An object where each property is a key and its value is the value to be cached.
131124
* @param callingMethod - The name of the calling method.
125+
* @param [ttl] - The time-to-live (expiration) of the cache item in milliseconds. Used in fallback to pipelineSet.
132126
* @returns A Promise that resolves when the values are cached.
133127
*/
134-
async multiSet(keyValuePairs: Record<string, any>, callingMethod: string): Promise<void> {
128+
async multiSet(keyValuePairs: Record<string, any>, callingMethod: string, ttl?: number): Promise<void> {
129+
if (!this.options.multiSetEnabled) return this.pipelineSet(keyValuePairs, callingMethod, ttl);
135130
// Serialize values and add prefix
136131
const serializedKeyValuePairs: Record<string, string> = {};
137132
for (const [key, value] of Object.entries(keyValuePairs)) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
4+
import type { Logger } from 'pino';
5+
import { Registry } from 'prom-client';
6+
import { RedisClientType } from 'redis';
7+
8+
import { LocalLRUCache, RedisCache } from '../clients';
9+
import { ICacheClient } from '../clients/cache/ICacheClient';
10+
11+
export class CacheClientFactory {
12+
static create(
13+
logger: Logger,
14+
register: Registry = new Registry(),
15+
reservedKeys: Set<string> = new Set(),
16+
redisClient?: RedisClientType,
17+
): ICacheClient {
18+
return !ConfigService.get('TEST') && redisClient !== undefined
19+
? new RedisCache(logger.child({ name: 'redisCache' }), redisClient)
20+
: new LocalLRUCache(logger.child({ name: 'localLRUCache' }), register, reservedKeys);
21+
}
22+
}

packages/relay/src/lib/relay.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { IPAddressHbarSpendingPlanRepository } from './db/repositories/hbarLimit
1919
import { DebugImpl } from './debug';
2020
import { RpcMethodDispatcher } from './dispatcher';
2121
import { EthImpl } from './eth';
22+
import { CacheClientFactory } from './factories/cacheClientFactory';
2223
import { NetImpl } from './net';
2324
import { CacheService } from './services/cacheService/cacheService';
2425
import HAPIService from './services/hapiService/hapiService';
@@ -284,10 +285,13 @@ export class Relay {
284285

285286
// Create CacheService with the connected Redis client (or undefined for LRU-only)
286287
this.cacheService = new CacheService(
287-
this.logger.child({ name: 'cache-service' }),
288+
CacheClientFactory.create(
289+
this.logger.child({ name: 'cache-service' }),
290+
this.register,
291+
reservedKeys,
292+
this.redisClient,
293+
),
288294
this.register,
289-
reservedKeys,
290-
this.redisClient,
291295
);
292296

293297
// Create spending plan repositories

0 commit comments

Comments
 (0)