From e58b4acc19e0bc53c0c27727c9033ecdbe8b9a36 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Thu, 12 Dec 2024 16:36:31 +0300 Subject: [PATCH] enhance(redis): use `SCAN` instead of `KEYS` --- .changeset/shaggy-poets-bow.md | 6 ++++++ packages/cache/redis/src/index.ts | 15 ++++++++++++++- packages/cache/redis/test/cache.spec.ts | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .changeset/shaggy-poets-bow.md diff --git a/.changeset/shaggy-poets-bow.md b/.changeset/shaggy-poets-bow.md new file mode 100644 index 0000000000000..4ba77366bb4f2 --- /dev/null +++ b/.changeset/shaggy-poets-bow.md @@ -0,0 +1,6 @@ +--- +'@graphql-mesh/cache-redis': patch +--- + +Use `SCAN` instead of `KEYS` to avoid the error `ERR KEYS command is disabled because total +number of keys is too large, please use SCAN` diff --git a/packages/cache/redis/src/index.ts b/packages/cache/redis/src/index.ts index 2a2a0e15c63b2..f66f2d41e4ce7 100644 --- a/packages/cache/redis/src/index.ts +++ b/packages/cache/redis/src/index.ts @@ -96,7 +96,7 @@ export default class RedisCache implements KeyValueCache, Disposa } getKeysByPrefix(prefix: string): Promise { - return this.client.keys(`${prefix}*`); + return scanPatterns(this.client, `${prefix}*`); } delete(key: string): PromiseLike | boolean { @@ -111,3 +111,16 @@ export default class RedisCache implements KeyValueCache, Disposa } } } + +function scanPatterns(redis: Redis, pattern: string, cursor: string = '0', keys: string[] = []) { + return mapMaybePromise( + redis.scan(cursor, 'MATCH', pattern, 'COUNT', '10'), + ([nextCursor, nextKeys]) => { + keys.push(...nextKeys); + if (nextCursor === '0') { + return keys; + } + return scanPatterns(redis, pattern, nextCursor, keys); + }, + ); +} diff --git a/packages/cache/redis/test/cache.spec.ts b/packages/cache/redis/test/cache.spec.ts index f61ee925e66f5..a091e2339e767 100644 --- a/packages/cache/redis/test/cache.spec.ts +++ b/packages/cache/redis/test/cache.spec.ts @@ -165,4 +165,22 @@ describe('redis', () => { }); }); }); + + describe('methods', () => { + it('get/set/delete', async () => { + using redis = new RedisCache({ logger }); + const key = 'key'; + const value = 'value'; + await redis.set(key, value); + await expect(redis.get(key)).resolves.toBe(value); + await redis.delete(key); + await expect(redis.get(key)).resolves.toBeUndefined(); + }); + it('getKeysByPrefix', async () => { + using redis = new RedisCache({ logger }); + const keys = ['foo1', 'foo2', 'foo3']; + await Promise.all(keys.map((key, i) => redis.set(key, `value${i}`))); + await expect(redis.getKeysByPrefix('foo')).resolves.toEqual(keys); + }); + }); });