diff --git a/.changeset/small-avocados-live.md b/.changeset/small-avocados-live.md new file mode 100644 index 0000000000..ddb01a9dfa --- /dev/null +++ b/.changeset/small-avocados-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Adds the optional flag for useRedisSets for the Redis cache to the config. diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 3ba3f4ba0b..f975baf1e1 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -323,8 +323,11 @@ backend: cache: store: redis connection: redis://user:pass@cache.example.com:6379 + useRedisSets: true ``` +The useRedisSets flag is explained [here](https://github.com/jaredwray/keyv/tree/main/packages/redis#useredissets). + Contributions supporting other cache stores are welcome! ## Containerization diff --git a/packages/backend-common/src/cache/CacheManager.test.ts b/packages/backend-common/src/cache/CacheManager.test.ts index 4578d4fed9..13a4a0d599 100644 --- a/packages/backend-common/src/cache/CacheManager.test.ts +++ b/packages/backend-common/src/cache/CacheManager.test.ts @@ -48,8 +48,10 @@ describe('CacheManager', () => { describe('CacheManager.fromConfig', () => { it('accesses the backend.cache key', () => { const getOptionalString = jest.fn(); + const getOptionalBoolean = jest.fn(); const config = defaultConfig(); config.getOptionalString = getOptionalString; + config.getOptionalBoolean = getOptionalBoolean; CacheManager.fromConfig(config); @@ -57,6 +59,9 @@ describe('CacheManager', () => { expect(getOptionalString.mock.calls[1][0]).toEqual( 'backend.cache.connection', ); + expect(getOptionalBoolean.mock.calls[0][0]).toEqual( + 'backend.cache.useRedisSets', + ); }); it('does not require the backend.cache key', () => { @@ -195,32 +200,61 @@ describe('CacheManager', () => { const mockMemcacheCalls = memcache.mock.calls.splice(-1); expect(mockMemcacheCalls[0][0]).toEqual(expectedHost); }); - }); - it('returns a Redis client when configured', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, + it('returns a Redis client when configured', () => { + const redisConnection = 'redis://127.0.0.1:6379'; + const manager = CacheManager.fromConfig( + new ConfigReader({ + backend: { + cache: { + store: 'redis', + connection: redisConnection, + }, }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); + }), + ); + const expectedTtl = 3600; + manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, + const cache = Keyv as unknown as jest.Mock; + const mockCacheCalls = cache.mock.calls.splice(-1); + expect(mockCacheCalls[0][0]).toMatchObject({ + ttl: expectedTtl, + }); + expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); + const redis = KeyvRedis as unknown as jest.Mock; + const mockRedisCalls = redis.mock.calls.splice(-1); + expect(mockRedisCalls[0][0]).toEqual(redisConnection); + }); + + it('returns a Redis client when configured with useRedisSets flag', () => { + const redisConnection = 'redis://127.0.0.1:6379'; + const useRedisSets = false; + const manager = CacheManager.fromConfig( + new ConfigReader({ + backend: { + cache: { + store: 'redis', + connection: redisConnection, + useRedisSets: useRedisSets, + }, + }, + }), + ); + const expectedTtl = 3600; + manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); + + const cache = Keyv as unknown as jest.Mock; + const mockCacheCalls = cache.mock.calls.splice(-1); + expect(mockCacheCalls[0][0]).toMatchObject({ + ttl: expectedTtl, + useRedisSets: useRedisSets, + }); + expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); + const redis = KeyvRedis as unknown as jest.Mock; + const mockRedisCalls = redis.mock.calls.splice(-1); + expect(mockRedisCalls[0][0]).toEqual(redisConnection); }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); }); describe('connection errors', () => { diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-common/src/cache/CacheManager.ts index 9101445b08..efaaf04e3a 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-common/src/cache/CacheManager.ts @@ -55,6 +55,7 @@ export class CacheManager { private readonly logger: LoggerService; private readonly store: keyof CacheManager['storeFactories']; private readonly connection: string; + private readonly useRedisSets: boolean; private readonly errorHandler: CacheManagerOptions['onError']; /** @@ -72,15 +73,24 @@ export class CacheManager { const store = config.getOptionalString('backend.cache.store') || 'memory'; const connectionString = config.getOptionalString('backend.cache.connection') || ''; + const useRedisSets = + config.getOptionalBoolean('backend.cache.useRedisSets') ?? true; const logger = (options.logger || getRootLogger()).child({ type: 'cacheManager', }); - return new CacheManager(store, connectionString, logger, options.onError); + return new CacheManager( + store, + connectionString, + useRedisSets, + logger, + options.onError, + ); } private constructor( store: string, connectionString: string, + useRedisSets: boolean, logger: LoggerService, errorHandler: CacheManagerOptions['onError'], ) { @@ -90,6 +100,7 @@ export class CacheManager { this.logger = logger; this.store = store as keyof CacheManager['storeFactories']; this.connection = connectionString; + this.useRedisSets = useRedisSets; this.errorHandler = errorHandler; } @@ -143,6 +154,7 @@ export class CacheManager { namespace: pluginId, ttl: defaultTtl, store: new KeyvRedis(this.connection), + useRedisSets: this.useRedisSets, }); }