Add redis cache for cron triggers (#19306)

- WorkflowCronTriggerCronJob was querying every active workspace (~700)
sequentially every minute to find cron triggers, causing regular CPU
spikes to 100% on worker pods

- Added a Redis hashset cache that stores cron triggers. On cache miss
(TTL expired, cold start, or explicit invalidation), a full scan
rebuilds the cache

- Creating/deleting a new cron trigger updates the cache, only if exists
This commit is contained in:
Thomas Trompette
2026-04-03 17:08:31 +02:00
committed by GitHub
parent b55765a991
commit 90597e47ca
10 changed files with 694 additions and 44 deletions
@@ -286,6 +286,78 @@ export class CacheStorageService {
return newValue;
}
async hashGetValues(key: string): Promise<string[]> {
if (!this.isRedisCache()) {
throw new Error('hashGetValues is only supported with Redis cache');
}
const redisClient = (this.cache as RedisCache).store.client;
return redisClient.hVals(this.getKey(key));
}
async hashSet({
key,
field,
value,
}: {
key: string;
field: string;
value: string;
}): Promise<number> {
if (!this.isRedisCache()) {
throw new Error('hashSet is only supported with Redis cache');
}
const redisClient = (this.cache as RedisCache).store.client;
return redisClient.hSet(this.getKey(key), field, value);
}
async hashSetIfExists({
key,
field,
value,
}: {
key: string;
field: string;
value: string;
}): Promise<number> {
if (!this.isRedisCache()) {
throw new Error('hashSetIfExists is only supported with Redis cache');
}
const redisClient = (this.cache as RedisCache).store.client;
const script = `
if redis.call('EXISTS', KEYS[1]) == 1 then
return redis.call('HSET', KEYS[1], ARGV[1], ARGV[2])
else
return 0
end`;
return redisClient.eval(script, {
keys: [this.getKey(key)],
arguments: [field, value],
}) as Promise<number>;
}
async hashDelete({
key,
field,
}: {
key: string;
field: string;
}): Promise<number> {
if (!this.isRedisCache()) {
throw new Error('hashDelete is only supported with Redis cache');
}
const redisClient = (this.cache as RedisCache).store.client;
return redisClient.hDel(this.getKey(key), field);
}
async expire(key: string, ttlMs: Milliseconds): Promise<boolean> {
if (this.isRedisCache()) {
return (this.cache as RedisCache).store.client.expire(