perf: optimize DB layer with caching and pool tuning
- Add Redis cache wrapper (cached-db.ts) — cachedQuery + invalidate helpers - Add cached login user lookup (auth.ts: getLoginUser) — short 15s TTL for brute-force protection, cache invalidation on password/rank changes - Switch auth.ts login flow from Prisma facade to raw SQL via db.execute (avoids abstraction overhead for this hot path) - Cache invalidation wired in: login password upgrade, updateUser, resetPassword - Connection pool tuning: enableKeepAlive, namedPlaceholders, prepared statement cache (Node 22+), multipleStatements off (SQLi hardening) - 0 tsc errors, 583 tests passing
This commit is contained in:
1 parent
c0bbcae5d6
commit
ef5e706ee1
4 files changed
+152
-4
No files matched your search
@@ -0,0 +1,56 @@
|
||||
import "server-only";
|
||||
|
||||
import { redis } from "@/lib/redis";
|
||||
import { logger } from "@/lib/logger";
|
||||
|
||||
const DEFAULT_CACHE_TTL = 60;
|
||||
|
||||
function isRedisAvailable(): boolean {
|
||||
return !!redis && redis.status !== "end";
|
||||
}
|
||||
|
||||
export async function cachedQuery<T>(
|
||||
cacheKey: string,
|
||||
queryFn: () => Promise<T>,
|
||||
ttl: number = DEFAULT_CACHE_TTL,
|
||||
): Promise<T> {
|
||||
if (!isRedisAvailable()) {
|
||||
return queryFn();
|
||||
}
|
||||
|
||||
try {
|
||||
const cached = await redis?.get(cacheKey);
|
||||
if (cached !== null && cached !== undefined) {
|
||||
return JSON.parse(cached) as T;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn("[cache] read failed, falling back to DB", { key: cacheKey, error: String(err) });
|
||||
}
|
||||
|
||||
const result = await queryFn();
|
||||
|
||||
try {
|
||||
await redis?.setex(cacheKey, ttl, JSON.stringify(result));
|
||||
} catch (err) {
|
||||
logger.warn("[cache] write failed, continuing without cache", {
|
||||
key: cacheKey,
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Invalidate cache keys matching a glob pattern. */
|
||||
export function invalidateCache(pattern: string): Promise<number> {
|
||||
if (!isRedisAvailable()) return Promise.resolve(0);
|
||||
return redis?.keys(pattern).then((keys) =>
|
||||
keys.length > 0 ? redis!.del(...keys) : 0,
|
||||
) ?? Promise.resolve(0);
|
||||
}
|
||||
|
||||
/** Invalidate a single cache key immediately. */
|
||||
export function invalidateKey(key: string): Promise<number> {
|
||||
if (!isRedisAvailable()) return Promise.resolve(0);
|
||||
return redis!.del(key);
|
||||
}
|
||||
Reference in new issue
Block a user