Skip to content

Commit ef7f6bd

Browse files
feat: Мove creation of redis client outside of cache service (#4409)
Signed-off-by: nikolay <n.atanasow94@gmail.com> Signed-off-by: Konstantina Blazhukova <konstantina.blajukova@gmail.com> Co-authored-by: Nikolay Atanasow <n.atanasow94@gmail.com>
1 parent b75d8a3 commit ef7f6bd

20 files changed

+417
-419
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
// SPDX-License-Identifier: Apache-2.0
22

3-
import type { ICacheClient } from './ICacheClient';
43
import { RequestDetails } from '../../types';
4+
import type { ICacheClient } from './ICacheClient';
55

66
export interface IRedisCacheClient extends ICacheClient {
7-
disconnect: () => Promise<void>;
87
incrBy(key: string, amount: number, callingMethod: string, requestDetails: RequestDetails): Promise<number>;
98
rPush(key: string, value: any, callingMethod: string, requestDetails: RequestDetails): Promise<number>;
109
lRange(

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

Lines changed: 22 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services';
44
import { Logger } from 'pino';
55
import { Registry } from 'prom-client';
6-
import { createClient, RedisClientType } from 'redis';
6+
import { RedisClientType } from 'redis';
77

88
import { Utils } from '../../../utils';
9-
import { RedisCacheError } from '../../errors/RedisCacheError';
109
import { IRedisCacheClient } from './IRedisCacheClient';
1110

1211
/**
@@ -41,66 +40,16 @@ export class RedisCache implements IRedisCacheClient {
4140
*/
4241
private readonly client: RedisClientType;
4342

44-
/**
45-
* Boolean showing if the connection to the Redis client has finished.
46-
* @private
47-
*/
48-
private connected: Promise<boolean>;
49-
5043
/**
5144
* Creates an instance of `RedisCache`.
5245
*
5346
* @param {Logger} logger - The logger instance.
5447
* @param {Registry} register - The metrics registry.
5548
*/
56-
public constructor(logger: Logger, register: Registry) {
49+
public constructor(logger: Logger, register: Registry, client: RedisClientType) {
5750
this.logger = logger;
5851
this.register = register;
59-
60-
const redisUrl = ConfigService.get('REDIS_URL')!;
61-
const reconnectDelay = ConfigService.get('REDIS_RECONNECT_DELAY_MS');
62-
this.client = createClient({
63-
url: redisUrl,
64-
socket: {
65-
reconnectStrategy: (retries: number) => {
66-
const delay = retries * reconnectDelay;
67-
logger.warn(`Trying to reconnect with Redis, retry #${retries}. Delay is ${delay} ms...`);
68-
return delay;
69-
},
70-
},
71-
});
72-
this.connected = this.client
73-
.connect()
74-
.then(() => true)
75-
.catch((error) => {
76-
this.logger.error(error, 'Redis connection could not be established!');
77-
return false;
78-
});
79-
this.client.on('ready', async () => {
80-
this.connected = Promise.resolve(true);
81-
const connections = await this.getNumberOfConnections().catch((error) => {
82-
this.logger.error(error);
83-
return 0;
84-
});
85-
logger.info(`Connected to Redis server (${redisUrl}) successfully! Number of connections: ${connections}`);
86-
});
87-
this.client.on('end', () => {
88-
this.connected = Promise.resolve(false);
89-
logger.info('Disconnected from Redis server!');
90-
});
91-
this.client.on('error', (error) => {
92-
this.connected = Promise.resolve(false);
93-
const redisError = new RedisCacheError(error);
94-
if (redisError.isSocketClosed()) {
95-
logger.error(`Error occurred with Redis Connection when closing socket: ${redisError.message}`);
96-
} else {
97-
logger.error(`Error occurred with Redis Connection: ${redisError.fullError}`);
98-
}
99-
});
100-
}
101-
102-
async getConnectedClient(): Promise<RedisClientType> {
103-
return this.isConnected().then(() => this.client);
52+
this.client = client;
10453
}
10554

10655
/**
@@ -111,8 +60,7 @@ export class RedisCache implements IRedisCacheClient {
11160
* @returns The cached value or null if not found.
11261
*/
11362
async get(key: string, callingMethod: string): Promise<any> {
114-
const client = await this.getConnectedClient();
115-
const result = await client.get(key);
63+
const result = await this.client.get(key);
11664
if (result) {
11765
if (this.logger.isLevelEnabled('trace')) {
11866
const censoredKey = key.replace(Utils.IP_ADDRESS_REGEX, '<REDACTED>');
@@ -135,13 +83,12 @@ export class RedisCache implements IRedisCacheClient {
13583
* @returns A Promise that resolves when the value is cached.
13684
*/
13785
async set(key: string, value: any, callingMethod: string, ttl?: number): Promise<void> {
138-
const client = await this.getConnectedClient();
13986
const serializedValue = JSON.stringify(value);
14087
const resolvedTtl = ttl ?? this.options.ttl; // in milliseconds
14188
if (resolvedTtl > 0) {
142-
await client.set(key, serializedValue, { PX: resolvedTtl });
89+
await this.client.set(key, serializedValue, { PX: resolvedTtl });
14390
} else {
144-
await client.set(key, serializedValue);
91+
await this.client.set(key, serializedValue);
14592
}
14693

14794
const censoredKey = key.replace(Utils.IP_ADDRESS_REGEX, '<REDACTED>');
@@ -163,15 +110,14 @@ export class RedisCache implements IRedisCacheClient {
163110
* @returns A Promise that resolves when the values are cached.
164111
*/
165112
async multiSet(keyValuePairs: Record<string, any>, callingMethod: string): Promise<void> {
166-
const client = await this.getConnectedClient();
167113
// Serialize values
168114
const serializedKeyValuePairs: Record<string, string> = {};
169115
for (const [key, value] of Object.entries(keyValuePairs)) {
170116
serializedKeyValuePairs[key] = JSON.stringify(value);
171117
}
172118

173119
// Perform mSet operation
174-
await client.mSet(serializedKeyValuePairs);
120+
await this.client.mSet(serializedKeyValuePairs);
175121

176122
// Log the operation
177123
const entriesLength = Object.keys(keyValuePairs).length;
@@ -189,10 +135,9 @@ export class RedisCache implements IRedisCacheClient {
189135
* @returns A Promise that resolves when the values are cached.
190136
*/
191137
async pipelineSet(keyValuePairs: Record<string, any>, callingMethod: string, ttl?: number): Promise<void> {
192-
const client = await this.getConnectedClient();
193138
const resolvedTtl = ttl ?? this.options.ttl; // in milliseconds
194139

195-
const pipeline = client.multi();
140+
const pipeline = this.client.multi();
196141

197142
for (const [key, value] of Object.entries(keyValuePairs)) {
198143
const serializedValue = JSON.stringify(value);
@@ -217,66 +162,13 @@ export class RedisCache implements IRedisCacheClient {
217162
* @returns A Promise that resolves when the value is deleted from the cache.
218163
*/
219164
async delete(key: string, callingMethod: string): Promise<void> {
220-
const client = await this.getConnectedClient();
221-
await client.del(key);
165+
await this.client.del(key);
222166
if (this.logger.isLevelEnabled('trace')) {
223167
this.logger.trace(`delete cache for ${key} on ${callingMethod} call`);
224168
}
225169
// TODO: add metrics
226170
}
227171

228-
/**
229-
* Clears the entire cache.
230-
*
231-
* @returns {Promise<void>} A Promise that resolves when the cache is cleared.
232-
*/
233-
async clear(): Promise<void> {
234-
const client = await this.getConnectedClient();
235-
await client.flushAll();
236-
}
237-
238-
/**
239-
* Checks if the client is connected to the Redis server.
240-
*
241-
* @returns {Promise<boolean>} A Promise that resolves to true if the client is connected, false otherwise.
242-
*/
243-
async isConnected(): Promise<boolean> {
244-
return this.connected;
245-
}
246-
247-
/**
248-
* Retrieves the number of connections to the Redis server.
249-
*
250-
* @returns {Promise<number>} A Promise that resolves to the number of connections.
251-
* @throws {Error} If an error occurs while retrieving the number of connections.
252-
*/
253-
async getNumberOfConnections(): Promise<number> {
254-
const client = await this.getConnectedClient();
255-
const clientList = await client.clientList();
256-
return clientList.length;
257-
}
258-
259-
/**
260-
* Connects the client to the Redis server.
261-
*
262-
* @returns {Promise<void>} A Promise that resolves when the client is connected.
263-
* @throws {Error} If an error occurs while connecting to Redis.
264-
*/
265-
async connect(): Promise<void> {
266-
await this.client.connect();
267-
}
268-
269-
/**
270-
* Disconnects the client from the Redis server.
271-
*
272-
* @returns {Promise<void>} A Promise that resolves when the client is disconnected.
273-
* @throws {Error} If an error occurs while disconnecting from Redis.
274-
*/
275-
async disconnect(): Promise<void> {
276-
const client = await this.getConnectedClient();
277-
await client.quit();
278-
}
279-
280172
/**
281173
* Increments a value in the cache.
282174
*
@@ -286,8 +178,7 @@ export class RedisCache implements IRedisCacheClient {
286178
* @returns The value of the key after incrementing
287179
*/
288180
async incrBy(key: string, amount: number, callingMethod: string): Promise<number> {
289-
const client = await this.getConnectedClient();
290-
const result = await client.incrBy(key, amount);
181+
const result = await this.client.incrBy(key, amount);
291182
if (this.logger.isLevelEnabled('trace')) {
292183
this.logger.trace(`incrementing ${key} by ${amount} on ${callingMethod} call`);
293184
}
@@ -304,8 +195,7 @@ export class RedisCache implements IRedisCacheClient {
304195
* @returns The list of elements in the range
305196
*/
306197
async lRange(key: string, start: number, end: number, callingMethod: string): Promise<any[]> {
307-
const client = await this.getConnectedClient();
308-
const result = await client.lRange(key, start, end);
198+
const result = await this.client.lRange(key, start, end);
309199
if (this.logger.isLevelEnabled('trace')) {
310200
this.logger.trace(`retrieving range [${start}:${end}] from ${key} on ${callingMethod} call`);
311201
}
@@ -321,9 +211,8 @@ export class RedisCache implements IRedisCacheClient {
321211
* @returns The length of the list after pushing
322212
*/
323213
async rPush(key: string, value: any, callingMethod: string): Promise<number> {
324-
const client = await this.getConnectedClient();
325214
const serializedValue = JSON.stringify(value);
326-
const result = await client.rPush(key, serializedValue);
215+
const result = await this.client.rPush(key, serializedValue);
327216
if (this.logger.isLevelEnabled('trace')) {
328217
this.logger.trace(`pushing ${serializedValue} to ${key} on ${callingMethod} call`);
329218
}
@@ -337,11 +226,19 @@ export class RedisCache implements IRedisCacheClient {
337226
* @returns The list of keys matching the pattern
338227
*/
339228
async keys(pattern: string, callingMethod: string): Promise<string[]> {
340-
const client = await this.getConnectedClient();
341-
const result = await client.keys(pattern);
229+
const result = await this.client.keys(pattern);
342230
if (this.logger.isLevelEnabled('trace')) {
343231
this.logger.trace(`retrieving keys matching ${pattern} on ${callingMethod} call`);
344232
}
345233
return result;
346234
}
235+
236+
/**
237+
* Clears the entire cache.
238+
*
239+
* @returns {Promise<void>} A Promise that resolves when the cache is cleared.
240+
*/
241+
async clear(): Promise<void> {
242+
await this.client.flushAll();
243+
}
347244
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
import { Logger } from 'pino';
3+
import { createClient, RedisClientType } from 'redis';
4+
5+
import { RedisCacheError } from '../errors/RedisCacheError';
6+
7+
export class RedisClientManager {
8+
private client: RedisClientType;
9+
10+
private connected: boolean = false;
11+
12+
constructor(
13+
private readonly logger: Logger,
14+
url: string,
15+
reconnectMs: number,
16+
) {
17+
this.client = createClient({
18+
url,
19+
socket: { reconnectStrategy: (retries) => retries * reconnectMs },
20+
});
21+
this.client.on('ready', () => {
22+
this.logger.info(`Redis client connected to ${url}`);
23+
});
24+
this.client.on('end', () => {
25+
this.logger.info('Disconnected from Redis server!');
26+
});
27+
this.client.on('error', (error) => {
28+
const redisError = new RedisCacheError(error);
29+
if (redisError.isSocketClosed()) {
30+
this.logger.error(`Error occurred with Redis Connection when closing socket: ${redisError.message}`);
31+
} else {
32+
this.logger.error(`Error occurred with Redis Connection: ${redisError.fullError}`);
33+
}
34+
});
35+
}
36+
37+
async connect(): Promise<void> {
38+
await this.client.connect();
39+
this.connected = true;
40+
}
41+
42+
async disconnect(): Promise<void> {
43+
await this.client.quit();
44+
this.connected = false;
45+
}
46+
47+
isConnected(): boolean {
48+
return this.connected;
49+
}
50+
51+
async getNumberOfConnections(): Promise<number> {
52+
const list = await this.client.clientList();
53+
return list.length;
54+
}
55+
56+
getClient(): RedisClientType {
57+
return this.client;
58+
}
59+
}

0 commit comments

Comments
 (0)