Skip to content

Commit b802e4d

Browse files
committed
feat(getOrFetchUser): redis cache; use debug counters
1 parent 5d864e8 commit b802e4d

File tree

1 file changed

+43
-1
lines changed

1 file changed

+43
-1
lines changed

backend/src/utils/getOrFetchUser.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,65 @@
11
import { Client, User } from "discord.js";
2+
import { redis } from "../data/redis.js";
3+
import { incrementDebugCounter } from "../debugCounters.js";
24

35
const getOrFetchUserPromises: Map<string, Promise<User | undefined>> = new Map();
46

7+
const UNKNOWN_KEY = "__UNKNOWN__";
8+
9+
const baseCacheTimeSeconds = 60 * 60; // 1 hour
10+
const cacheTimeJitterSeconds = 5 * 60; // 5 minutes
11+
12+
// Use jitter on cache time to avoid tons of keys expiring at the same time
13+
const generateCacheTime = () => {
14+
const jitter = Math.floor(Math.random() * cacheTimeJitterSeconds);
15+
return baseCacheTimeSeconds + jitter;
16+
};
17+
518
/**
619
* Gets a user from cache or fetches it from the API if not cached.
720
* Concurrent requests are merged.
821
*/
922
export async function getOrFetchUser(bot: Client, userId: string): Promise<User | undefined> {
23+
// 1. Check Discord.js cache
1024
const cachedUser = bot.users.cache.get(userId);
1125
if (cachedUser) {
26+
incrementDebugCounter("getOrFetchUser:djsCache");
1227
return cachedUser;
1328
}
1429

30+
// 2. Check Redis
31+
const redisCacheKey = `cache:user:${userId}`;
32+
const userData = await redis.get(redisCacheKey);
33+
if (userData) {
34+
incrementDebugCounter("getOrFetchUser:redisCache");
35+
if (userData === UNKNOWN_KEY) {
36+
console.log("Found unknown user in Redis cache");
37+
return undefined;
38+
}
39+
console.log("Found user in Redis cache");
40+
// @ts-expect-error Replace with a proper solution once that exists
41+
return new User(bot, JSON.parse(userData));
42+
}
43+
1544
if (!getOrFetchUserPromises.has(userId)) {
45+
incrementDebugCounter("getOrFetchUser:fresh");
1646
getOrFetchUserPromises.set(
1747
userId,
1848
bot.users
1949
.fetch(userId)
20-
.catch(() => undefined)
50+
.catch(async () => {
51+
return undefined;
52+
})
53+
.then(async (user) => {
54+
const cacheValue = user ? JSON.stringify(user.toJSON()) : UNKNOWN_KEY;
55+
await redis.set(redisCacheKey, cacheValue, {
56+
expiration: {
57+
type: "EX",
58+
value: generateCacheTime(),
59+
},
60+
});
61+
return user;
62+
})
2163
.finally(() => {
2264
getOrFetchUserPromises.delete(userId);
2365
}),

0 commit comments

Comments
 (0)