Files
indiekit-endpoint-activitypub/lib/redis-cache.js
Ricardo abf1b94bd6 feat: migrate Fedify KV store and plugin cache from MongoDB to Redis
Replace unbounded ap_kv MongoDB collection (169K docs, 49MB) with Redis:
- Fedify KV store uses @fedify/redis RedisKvStore (native TTL support)
- Plugin cache (fedidb, batch-refollow state, migration flags) uses new
  redis-cache.js utility with indiekit: key prefix
- All controllers updated to remove kvCollection parameter passing
- Addresses OOM kills caused by ap_kv growing ~14K entries/day
2026-03-01 16:26:17 +01:00

99 lines
2.2 KiB
JavaScript

/**
* Redis-backed cache for plugin-level key-value storage.
*
* Replaces direct MongoDB ap_kv reads/writes for fedidb cache,
* batch-refollow state, and migration flags. Uses the same Redis
* connection as the Fedify message queue and KV store.
*
* All keys are prefixed with "indiekit:" to avoid collisions with
* Fedify's "fedify::" prefix.
*/
import Redis from "ioredis";
const KEY_PREFIX = "indiekit:";
let _redis = null;
/**
* Initialize the Redis cache with a connection URL.
* Safe to call multiple times — reuses existing connection.
* @param {string} redisUrl - Redis connection URL
*/
export function initRedisCache(redisUrl) {
if (_redis) return;
if (!redisUrl) return;
_redis = new Redis(redisUrl);
}
/**
* Get the Redis client instance (for direct use if needed).
* @returns {import("ioredis").Redis|null}
*/
export function getRedisClient() {
return _redis;
}
/**
* Get a value from Redis cache.
* @param {string} key
* @returns {Promise<unknown|null>}
*/
export async function cacheGet(key) {
if (!_redis) return null;
try {
const raw = await _redis.get(KEY_PREFIX + key);
if (raw === null) return null;
return JSON.parse(raw);
} catch {
return null;
}
}
/**
* Set a value in Redis cache with optional TTL.
* @param {string} key
* @param {unknown} value - Must be JSON-serializable
* @param {number} [ttlSeconds] - Optional TTL in seconds (0 = no expiry)
*/
export async function cacheSet(key, value, ttlSeconds = 0) {
if (!_redis) return;
try {
const raw = JSON.stringify(value);
if (ttlSeconds > 0) {
await _redis.set(KEY_PREFIX + key, raw, "EX", ttlSeconds);
} else {
await _redis.set(KEY_PREFIX + key, raw);
}
} catch {
// Cache write failure is non-critical
}
}
/**
* Delete a key from Redis cache.
* @param {string} key
*/
export async function cacheDelete(key) {
if (!_redis) return;
try {
await _redis.del(KEY_PREFIX + key);
} catch {
// Ignore
}
}
/**
* Check if a key exists in Redis cache.
* @param {string} key
* @returns {Promise<boolean>}
*/
export async function cacheExists(key) {
if (!_redis) return false;
try {
return (await _redis.exists(KEY_PREFIX + key)) === 1;
} catch {
return false;
}
}