Skip to content
This repository was archived by the owner on Sep 16, 2026. It is now read-only.

Commit 8a13340

Browse files
fix(redis): unread hash TTL + prune dev memory rate-limit store
- Refresh 90d TTL on unread hash when incrementing, clearing, or reading counts - Drop expired entries from in-memory sliding-window ratelimit map each check Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
1 parent 8e2015e commit 8a13340

2 files changed

Lines changed: 18 additions & 3 deletions

File tree

‎apps/web/src/lib/redis.ts‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const STREAM_TTL_SECONDS = 3600;
44
const STREAM_ERROR_TTL_SECONDS = 600;
55
const TYPING_TTL_SECONDS = 3;
66
const PRESENCE_TTL_SECONDS = 60;
7+
/** Per-user unread hash: refresh on write/read so inactive users do not retain keys forever. */
8+
const UNREAD_HASH_TTL_SECONDS = 60 * 60 * 24 * 90;
79
const ONLINE_WINDOW_MS = 60_000;
810

911
const keys = {
@@ -290,24 +292,32 @@ export async function isUserOnline(userId: string): Promise<boolean> {
290292
export async function incrementUnread(userId: string, chatId: string): Promise<void> {
291293
const client = await getConnectedClient();
292294
if (!client) return;
293-
await client.hincrby(keys.unread(userId), chatId, 1);
295+
const key = keys.unread(userId);
296+
await client.hincrby(key, chatId, 1);
297+
await client.expire(key, UNREAD_HASH_TTL_SECONDS);
294298
}
295299

296300
export async function clearUnread(userId: string, chatId: string): Promise<void> {
297301
const client = await getConnectedClient();
298302
if (!client) return;
299-
await client.hdel(keys.unread(userId), chatId);
303+
const key = keys.unread(userId);
304+
await client.hdel(key, chatId);
305+
await client.expire(key, UNREAD_HASH_TTL_SECONDS);
300306
}
301307

302308
export async function getUnreadCounts(userId: string): Promise<Record<string, number>> {
303309
const client = await getConnectedClient();
304310
if (!client) return {};
305311

306-
const counts = await client.hgetall<Record<string, string>>(keys.unread(userId));
312+
const key = keys.unread(userId);
313+
const counts = await client.hgetall<Record<string, string>>(key);
307314
const result: Record<string, number> = {};
308315
for (const [chatId, count] of Object.entries(counts ?? {})) {
309316
result[chatId] = Number.parseInt(count, 10);
310317
}
318+
if (Object.keys(result).length > 0) {
319+
await client.expire(key, UNREAD_HASH_TTL_SECONDS);
320+
}
311321
return result;
312322
}
313323

‎apps/web/src/lib/upstash.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@ function createSlidingWindowRatelimit(
268268
return {
269269
limit: async (identifier: string) => {
270270
const now = Date.now();
271+
for (const [key, entry] of memoryRatelimitStore) {
272+
if (entry.resetAt <= now) {
273+
memoryRatelimitStore.delete(key);
274+
}
275+
}
271276
const key = `${prefix}:${identifier}`;
272277
const existing = memoryRatelimitStore.get(key);
273278
if (!existing || existing.resetAt <= now) {

0 commit comments

Comments
 (0)