redis pt 2: read cache wiring & event based invalidations
This commit is contained in:
61
src/common/cache/cache-keys.ts
vendored
Normal file
61
src/common/cache/cache-keys.ts
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
const EMPTY_VALUE_TOKEN = '_';
|
||||
|
||||
export const CACHE_NAMESPACE = {
|
||||
FRIENDS_LIST: 'friends-list',
|
||||
DOLLS_LIST: 'dolls-list',
|
||||
USERS_SEARCH: 'users-search',
|
||||
} as const;
|
||||
|
||||
function normalizeKeyPart(value: string | undefined): string {
|
||||
if (!value) {
|
||||
return EMPTY_VALUE_TOKEN;
|
||||
}
|
||||
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
|
||||
export const CACHE_TTL_SECONDS = {
|
||||
FRIENDS_LIST: 30,
|
||||
DOLLS_LIST: 30,
|
||||
USERS_SEARCH: 20,
|
||||
} as const;
|
||||
|
||||
export function friendsListCacheKey(userId: string): string {
|
||||
return normalizeKeyPart(userId);
|
||||
}
|
||||
|
||||
export function friendsListOwnerTag(userId: string): string {
|
||||
return `owner:${normalizeKeyPart(userId)}`;
|
||||
}
|
||||
|
||||
export function friendsListDependsOnUserTag(userId: string): string {
|
||||
return `depends-on:${normalizeKeyPart(userId)}`;
|
||||
}
|
||||
|
||||
export function dollsListCacheKey(
|
||||
ownerId: string,
|
||||
requesterId: string,
|
||||
): string {
|
||||
return `${normalizeKeyPart(ownerId)}:${normalizeKeyPart(requesterId)}`;
|
||||
}
|
||||
|
||||
export function dollsListOwnerTag(ownerId: string): string {
|
||||
return `owner:${normalizeKeyPart(ownerId)}`;
|
||||
}
|
||||
|
||||
export function dollsListViewerTag(viewerId: string): string {
|
||||
return `viewer:${normalizeKeyPart(viewerId)}`;
|
||||
}
|
||||
|
||||
export function usersSearchCacheKey(
|
||||
username: string | undefined,
|
||||
excludeUserId: string | undefined,
|
||||
): string {
|
||||
return `${normalizeKeyPart(username?.trim().toLowerCase())}:${normalizeKeyPart(excludeUserId)}`;
|
||||
}
|
||||
|
||||
export const USERS_SEARCH_GLOBAL_TAG = 'global';
|
||||
|
||||
export function usersSearchUserTag(userId: string): string {
|
||||
return `user:${normalizeKeyPart(userId)}`;
|
||||
}
|
||||
66
src/common/cache/cache-tags.service.ts
vendored
Normal file
66
src/common/cache/cache-tags.service.ts
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CacheService } from './cache.service';
|
||||
|
||||
const CACHE_TAG_SET_TTL_SECONDS = 86_400;
|
||||
|
||||
@Injectable()
|
||||
export class CacheTagsService {
|
||||
constructor(private readonly cacheService: CacheService) {}
|
||||
|
||||
async rememberKeyForTag(
|
||||
namespace: string,
|
||||
tag: string,
|
||||
cacheKey: string,
|
||||
): Promise<void> {
|
||||
const redisClient = this.cacheService.getRedisClient();
|
||||
if (!redisClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tagSetKey = this.getTagSetKey(namespace, tag);
|
||||
const keyWithNamespace = this.cacheService.getNamespacedKey(
|
||||
namespace,
|
||||
cacheKey,
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
redisClient.sadd(tagSetKey, keyWithNamespace),
|
||||
redisClient.expire(tagSetKey, CACHE_TAG_SET_TTL_SECONDS),
|
||||
]);
|
||||
} catch (error) {
|
||||
this.cacheService.recordError('tag remember', tagSetKey, error);
|
||||
}
|
||||
}
|
||||
|
||||
async invalidateTag(namespace: string, tag: string): Promise<void> {
|
||||
const redisClient = this.cacheService.getRedisClient();
|
||||
if (!redisClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tagSetKey = this.getTagSetKey(namespace, tag);
|
||||
|
||||
try {
|
||||
const keys = await redisClient.smembers(tagSetKey);
|
||||
if (keys.length === 0) {
|
||||
await redisClient.del(tagSetKey);
|
||||
return;
|
||||
}
|
||||
|
||||
const pipeline = redisClient.pipeline();
|
||||
keys.forEach((key) => pipeline.del(key));
|
||||
pipeline.del(tagSetKey);
|
||||
await pipeline.exec();
|
||||
} catch (error) {
|
||||
this.cacheService.recordError('tag invalidate', tagSetKey, error);
|
||||
}
|
||||
}
|
||||
|
||||
private getTagSetKey(namespace: string, tag: string): string {
|
||||
return this.cacheService.getNamespacedKey(
|
||||
'cache-tag',
|
||||
`${namespace}:${tag}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
5
src/common/cache/cache.module.ts
vendored
5
src/common/cache/cache.module.ts
vendored
@@ -1,12 +1,13 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { RedisModule } from '../../database/redis.module';
|
||||
import { CacheTagsService } from './cache-tags.service';
|
||||
import { CacheService } from './cache.service';
|
||||
import { RedisThrottlerStorage } from './redis-throttler.storage';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [RedisModule],
|
||||
providers: [CacheService, RedisThrottlerStorage],
|
||||
exports: [CacheService, RedisThrottlerStorage],
|
||||
providers: [CacheService, CacheTagsService, RedisThrottlerStorage],
|
||||
exports: [CacheService, CacheTagsService, RedisThrottlerStorage],
|
||||
})
|
||||
export class CacheModule {}
|
||||
|
||||
Reference in New Issue
Block a user