Skip to content

Commit 68fa0e5

Browse files
committed
feat: enforce maximum TTL in RedisCacheService to prevent overflow errors
1 parent 509518d commit 68fa0e5

File tree

2 files changed

+67
-25
lines changed

2 files changed

+67
-25
lines changed

src/datasources/cache/redis.cache.service.spec.ts

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -137,21 +137,6 @@ describe('RedisCacheService', () => {
137137
expect(ttl).toBeLessThanOrEqual(maxDeviation);
138138
});
139139

140-
it('Setting key throws on expire', async () => {
141-
const cacheDir = new CacheDir(
142-
faker.string.alphanumeric(),
143-
faker.string.sample(),
144-
);
145-
146-
// Expiration time out of range to force an error
147-
await expect(
148-
redisCacheService.hSet(cacheDir, '', Number.MAX_VALUE + 1),
149-
).rejects.toThrow();
150-
151-
const storedValue = await redisClient.hGet(cacheDir.key, cacheDir.field);
152-
expect(storedValue).toBeNull();
153-
});
154-
155140
it('Getting key gets the stored value', async () => {
156141
const cacheDir = new CacheDir(
157142
faker.string.alphanumeric(),
@@ -298,4 +283,44 @@ describe('RedisCacheService', () => {
298283
expect(ttl).toBeGreaterThan(0);
299284
expect(ttl).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
300285
});
286+
287+
it('Setting key with TTL larger than MAX_TTL enforces MAX_TTL limit', async () => {
288+
const cacheDir = new CacheDir(
289+
faker.string.alphanumeric(),
290+
faker.string.sample(),
291+
);
292+
const value = fakeJson();
293+
const expireTime = MAX_TTL + faker.number.int({ min: 1000, max: 10000 });
294+
295+
await redisCacheService.hSet(cacheDir, value, expireTime, 0);
296+
297+
const storedValue = await redisClient.hGet(cacheDir.key, cacheDir.field);
298+
const ttl = await redisClient.ttl(cacheDir.key);
299+
expect(storedValue).toEqual(value);
300+
expect(ttl).toBeGreaterThan(0);
301+
expect(ttl).toBeLessThanOrEqual(MAX_TTL);
302+
});
303+
304+
it('Increment with TTL larger than MAX_TTL enforces MAX_TTL limit', async () => {
305+
const key = faker.string.alphanumeric();
306+
const expireTime = MAX_TTL + faker.number.int({ min: 1000, max: 10000 });
307+
308+
await redisCacheService.increment(key, expireTime, 0);
309+
310+
const ttl = await redisClient.ttl(key);
311+
expect(ttl).toBeGreaterThan(0);
312+
expect(ttl).toBeLessThanOrEqual(MAX_TTL);
313+
});
314+
315+
it('SetCounter with TTL larger than MAX_TTL enforces MAX_TTL limit', async () => {
316+
const key = faker.string.alphanumeric();
317+
const value = faker.number.int({ min: 1, max: 100 });
318+
const expireTime = MAX_TTL + faker.number.int({ min: 1000, max: 10000 });
319+
320+
await redisCacheService.setCounter(key, value, expireTime, 0);
321+
322+
const ttl = await redisClient.ttl(key);
323+
expect(ttl).toBeGreaterThan(0);
324+
expect(ttl).toBeLessThanOrEqual(MAX_TTL);
325+
});
301326
});

src/datasources/cache/redis.cache.service.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { CacheDir } from '@/datasources/cache/entities/cache-dir.entity';
55
import { ICacheReadiness } from '@/domain/interfaces/cache-readiness.interface';
66
import { ILoggingService, LoggingService } from '@/logging/logging.interface';
77
import { IConfigurationService } from '@/config/configuration.service.interface';
8-
import { CacheKeyPrefix } from '@/datasources/cache/constants';
8+
import { CacheKeyPrefix, MAX_TTL } from '@/datasources/cache/constants';
99
import { LogType } from '@/domain/common/entities/log-type.entity';
1010
import { deviateRandomlyByPercentage } from '@/domain/common/utils/number';
1111

@@ -59,9 +59,11 @@ export class RedisCacheService
5959
}
6060

6161
const key = this._prefixKey(cacheDir.key);
62-
const expirationTime = deviateRandomlyByPercentage(
63-
expireTimeSeconds,
64-
expireDeviatePercent ?? this.defaultExpirationDeviatePercent,
62+
const expirationTime = this.enforceMaxRedisTTL(
63+
deviateRandomlyByPercentage(
64+
expireTimeSeconds,
65+
expireDeviatePercent ?? this.defaultExpirationDeviatePercent,
66+
),
6567
);
6668

6769
try {
@@ -106,9 +108,11 @@ export class RedisCacheService
106108
): Promise<number> {
107109
const transaction = this.client.multi().incr(cacheKey);
108110
if (expireTimeSeconds !== undefined && expireTimeSeconds > 0) {
109-
const expirationTime = deviateRandomlyByPercentage(
110-
expireTimeSeconds,
111-
expireDeviatePercent ?? this.defaultExpirationDeviatePercent,
111+
const expirationTime = this.enforceMaxRedisTTL(
112+
deviateRandomlyByPercentage(
113+
expireTimeSeconds,
114+
expireDeviatePercent ?? this.defaultExpirationDeviatePercent,
115+
),
112116
);
113117

114118
transaction.expire(cacheKey, expirationTime, 'NX');
@@ -123,9 +127,11 @@ export class RedisCacheService
123127
expireTimeSeconds: number,
124128
expireDeviatePercent?: number,
125129
): Promise<void> {
126-
const expirationTime = deviateRandomlyByPercentage(
127-
expireTimeSeconds,
128-
expireDeviatePercent ?? this.defaultExpirationDeviatePercent,
130+
const expirationTime = this.enforceMaxRedisTTL(
131+
deviateRandomlyByPercentage(
132+
expireTimeSeconds,
133+
expireDeviatePercent ?? this.defaultExpirationDeviatePercent,
134+
),
129135
);
130136

131137
await this.client.set(key, value, {
@@ -182,4 +188,15 @@ export class RedisCacheService
182188
});
183189
await this.client.disconnect();
184190
}
191+
192+
/**
193+
* Enforces the maximum TTL for Redis to prevent overflow errors.
194+
*
195+
* @param {number} ttl - The TTL to enforce.
196+
*
197+
* @returns {number} The TTL if it is less than or equal to MAX_TTL, otherwise MAX_TTL.
198+
*/
199+
private enforceMaxRedisTTL(ttl: number): number {
200+
return ttl > MAX_TTL ? MAX_TTL : ttl;
201+
}
185202
}

0 commit comments

Comments
 (0)