Skip to content

Commit 8cf63f3

Browse files
anderdcanderdc
andauthored
fix(cache): back the response cache with Redis on cache-manager v7 (#108)
* fix(cache): back the response cache with Redis on cache-manager v7 The cache module was copied from das-gittensor (@nestjs/cache-manager v2 / cache-manager v5). das-github-mirror is on v3 / cache-manager v7 (Keyv-based), where the config shape changed, so the copied config was broken on arrival in b771271: - v3 reads the store list from `stores` (plural Keyv instances); the singular `store` key was ignored, so Redis was never used. - v3 reads the entry TTL from top-level `ttl`; the `ttl` nested inside `redisStore({ ttl })` never reached the cache. Production therefore ran an in-memory cache with no expiry — GET responses were pinned from process start and served stale until the next restart (~20h stale observed on /api/v1/miners/:id/issues). - Replace cache-manager-redis-yet (v5-era, incompatible with v7) with @keyv/redis. - Pass `stores: [Keyv]` + top-level `ttl`. - Namespace cache keys `apicache`, clear of BullMQ's `bull:*`. - Add a store error listener so a Redis blip degrades to an uncached request instead of crashing the process. - Type the factory return as CacheModuleOptions (was `any`). * fix(cache): regenerate package-lock.json with npm 10 The previous commit's lockfile was generated with npm 11 (local), which CI's npm 10 (Node 20) rejected as out of sync — `npm ci` reported the chokidar subtree missing from the lock. Regenerated with npm 10 so the lockfile format matches CI; `npm ci` now passes locally under npm 10. --------- Co-authored-by: anderdc <me@alexanderdc.com>
1 parent 1f9e3c3 commit 8cf63f3

3 files changed

Lines changed: 97 additions & 78 deletions

File tree

packages/das/package-lock.json

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

packages/das/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"lint:fix": "eslint \"src/**/*.ts\" --fix"
1818
},
1919
"dependencies": {
20+
"@keyv/redis": "^5.1.6",
2021
"@nestjs/bullmq": "^11.0.4",
2122
"@nestjs/cache-manager": "^3.1.0",
2223
"@nestjs/common": "^10.0.0",
@@ -26,8 +27,8 @@
2627
"@nestjs/swagger": "^7.4.2",
2728
"@nestjs/typeorm": "^10.0.2",
2829
"bullmq": "^5.74.1",
29-
"cache-manager-redis-yet": "^5.1.5",
3030
"jsonwebtoken": "^9.0.3",
31+
"keyv": "^5.6.0",
3132
"pg": "^8.16.3",
3233
"reflect-metadata": "^0.2.0",
3334
"rxjs": "^7.8.1",

packages/das/src/cache/cache.module.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { CacheModule } from "@nestjs/cache-manager";
2-
import { Global, Module } from "@nestjs/common";
1+
import { CacheModule, CacheModuleOptions } from "@nestjs/cache-manager";
2+
import { Global, Logger, Module } from "@nestjs/common";
33
import { APP_INTERCEPTOR } from "@nestjs/core";
4-
import { redisStore } from "cache-manager-redis-yet";
4+
import KeyvRedis from "@keyv/redis";
5+
import { Keyv } from "keyv";
56
import { CustomCacheInterceptor } from "./custom-cache.interceptor";
67

78
// Production TTL for cached API responses (scoring cycles run far less often)
@@ -11,26 +12,42 @@ const DEV_CACHE_TTL_MS = 1;
1112

1213
const isProduction = process.env.NODE_ENV === "production";
1314

15+
const logger = new Logger("CustomCacheModule");
16+
1417
@Global()
1518
@Module({
1619
imports: [
1720
CacheModule.registerAsync({
1821
isGlobal: true,
19-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20-
useFactory: async (): Promise<any> => {
22+
useFactory: (): CacheModuleOptions => {
2123
const ttl = isProduction ? CACHE_TTL_MS : DEV_CACHE_TTL_MS;
2224

25+
// @nestjs/cache-manager v3 (cache-manager v7 / Keyv) reads the store
26+
// list from `stores` (plural — Keyv instances) and the entry TTL from
27+
// the top-level `ttl`. A singular `store`, or a `ttl` nested inside a
28+
// store adapter, is silently ignored: the cache then falls back to an
29+
// in-memory store with no expiry. Keep both at the top level.
2330
if (process.env.REDIS_HOST) {
24-
return {
25-
store: await redisStore({
26-
socket: {
27-
host: process.env.REDIS_HOST,
28-
port: parseInt(process.env.REDIS_PORT ?? "6379"),
29-
},
30-
ttl,
31-
}),
32-
};
31+
const host = process.env.REDIS_HOST;
32+
const port = process.env.REDIS_PORT ?? "6379";
33+
34+
// `apicache` namespace keeps response-cache keys clear of the
35+
// BullMQ (`bull:*`) keyspace on the shared Redis instance.
36+
const store = new Keyv({
37+
store: new KeyvRedis(`redis://${host}:${port}`),
38+
namespace: "apicache",
39+
});
40+
// Surface — and swallow — Redis connection errors so a Redis blip
41+
// degrades to an uncached request rather than an unhandled
42+
// EventEmitter "error" that would crash the process.
43+
store.on("error", (err) =>
44+
logger.error(`Redis response cache error: ${String(err)}`),
45+
);
46+
47+
return { ttl, stores: [store] };
3348
}
49+
50+
// No Redis configured — fall back to the built-in in-memory store.
3451
return { ttl };
3552
},
3653
}),

0 commit comments

Comments
 (0)