merge: Remove infinite caches to prevent memory leak (!587)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/587 Closes #600 and #601 Approved-by: Amelia Yukii <amelia.yukii@shourai.de> Approved-by: Marie <marie@kaifa.ch>
This commit is contained in:
		
						commit
						4e7df7a5f2
					
				
					 12 changed files with 94 additions and 87 deletions
				
			
		| 
						 | 
					@ -29,7 +29,7 @@ export class AvatarDecorationService implements OnApplicationShutdown {
 | 
				
			||||||
		private moderationLogService: ModerationLogService,
 | 
							private moderationLogService: ModerationLogService,
 | 
				
			||||||
		private globalEventService: GlobalEventService,
 | 
							private globalEventService: GlobalEventService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		this.cache = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30);
 | 
							this.cache = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30); // 30s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.redisForSub.on('message', this.onMessage);
 | 
							this.redisForSub.on('message', this.onMessage);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,10 +56,10 @@ export class CacheService implements OnApplicationShutdown {
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		//this.onMessage = this.onMessage.bind(this);
 | 
							//this.onMessage = this.onMessage.bind(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.userByIdCache = new MemoryKVCache<MiUser>(Infinity);
 | 
							this.userByIdCache = new MemoryKVCache<MiUser>(1000 * 60 * 5); // 5m
 | 
				
			||||||
		this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null>(Infinity);
 | 
							this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null>(1000 * 60 * 5); // 5m
 | 
				
			||||||
		this.localUserByIdCache = new MemoryKVCache<MiLocalUser>(Infinity);
 | 
							this.localUserByIdCache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 5); // 5m
 | 
				
			||||||
		this.uriPersonCache = new MemoryKVCache<MiUser | null>(Infinity);
 | 
							this.uriPersonCache = new MemoryKVCache<MiUser | null>(1000 * 60 * 5); // 5m
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.userProfileCache = new RedisKVCache<MiUserProfile>(this.redisClient, 'userProfile', {
 | 
							this.userProfileCache = new RedisKVCache<MiUserProfile>(this.redisClient, 'userProfile', {
 | 
				
			||||||
			lifetime: 1000 * 60 * 30, // 30m
 | 
								lifetime: 1000 * 60 * 30, // 30m
 | 
				
			||||||
| 
						 | 
					@ -135,14 +135,14 @@ export class CacheService implements OnApplicationShutdown {
 | 
				
			||||||
					if (user == null) {
 | 
										if (user == null) {
 | 
				
			||||||
						this.userByIdCache.delete(body.id);
 | 
											this.userByIdCache.delete(body.id);
 | 
				
			||||||
						this.localUserByIdCache.delete(body.id);
 | 
											this.localUserByIdCache.delete(body.id);
 | 
				
			||||||
						for (const [k, v] of this.uriPersonCache.cache.entries()) {
 | 
											for (const [k, v] of this.uriPersonCache.entries) {
 | 
				
			||||||
							if (v.value?.id === body.id) {
 | 
												if (v.value?.id === body.id) {
 | 
				
			||||||
								this.uriPersonCache.delete(k);
 | 
													this.uriPersonCache.delete(k);
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						this.userByIdCache.set(user.id, user);
 | 
											this.userByIdCache.set(user.id, user);
 | 
				
			||||||
						for (const [k, v] of this.uriPersonCache.cache.entries()) {
 | 
											for (const [k, v] of this.uriPersonCache.entries) {
 | 
				
			||||||
							if (v.value?.id === user.id) {
 | 
												if (v.value?.id === user.id) {
 | 
				
			||||||
								this.uriPersonCache.set(k, user);
 | 
													this.uriPersonCache.set(k, user);
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class CustomEmojiService implements OnApplicationShutdown {
 | 
					export class CustomEmojiService implements OnApplicationShutdown {
 | 
				
			||||||
	private cache: MemoryKVCache<MiEmoji | null>;
 | 
						private emojisCache: MemoryKVCache<MiEmoji | null>;
 | 
				
			||||||
	public localEmojisCache: RedisSingleCache<Map<string, MiEmoji>>;
 | 
						public localEmojisCache: RedisSingleCache<Map<string, MiEmoji>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 | 
				
			||||||
		private globalEventService: GlobalEventService,
 | 
							private globalEventService: GlobalEventService,
 | 
				
			||||||
		private driveService: DriveService,
 | 
							private driveService: DriveService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		this.cache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12);
 | 
							this.emojisCache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12); // 12h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.localEmojisCache = new RedisSingleCache<Map<string, MiEmoji>>(this.redisClient, 'localEmojis', {
 | 
							this.localEmojisCache = new RedisSingleCache<Map<string, MiEmoji>>(this.redisClient, 'localEmojis', {
 | 
				
			||||||
			lifetime: 1000 * 60 * 30, // 30m
 | 
								lifetime: 1000 * 60 * 30, // 30m
 | 
				
			||||||
| 
						 | 
					@ -364,7 +364,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 | 
				
			||||||
			host: newHost ?? IsNull(),
 | 
								host: newHost ?? IsNull(),
 | 
				
			||||||
		})) ?? null;
 | 
							})) ?? null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull);
 | 
							const emoji = await this.emojisCache.fetch(`${name} ${host}`, queryOrNull);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (emoji == null) return null;
 | 
							if (emoji == null) return null;
 | 
				
			||||||
		return emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 | 
							return emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 | 
				
			||||||
| 
						 | 
					@ -391,7 +391,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> {
 | 
						public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> {
 | 
				
			||||||
		const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null);
 | 
							const notCachedEmojis = emojis.filter(emoji => this.emojisCache.get(`${emoji.name} ${emoji.host}`) == null);
 | 
				
			||||||
		const emojisQuery: any[] = [];
 | 
							const emojisQuery: any[] = [];
 | 
				
			||||||
		const hosts = new Set(notCachedEmojis.map(e => e.host));
 | 
							const hosts = new Set(notCachedEmojis.map(e => e.host));
 | 
				
			||||||
		for (const host of hosts) {
 | 
							for (const host of hosts) {
 | 
				
			||||||
| 
						 | 
					@ -406,7 +406,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 | 
				
			||||||
			select: ['name', 'host', 'originalUrl', 'publicUrl'],
 | 
								select: ['name', 'host', 'originalUrl', 'publicUrl'],
 | 
				
			||||||
		}) : [];
 | 
							}) : [];
 | 
				
			||||||
		for (const emoji of _emojis) {
 | 
							for (const emoji of _emojis) {
 | 
				
			||||||
			this.cache.set(`${emoji.name} ${emoji.host}`, emoji);
 | 
								this.emojisCache.set(`${emoji.name} ${emoji.host}`, emoji);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -431,7 +431,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	public dispose(): void {
 | 
						public dispose(): void {
 | 
				
			||||||
		this.cache.dispose();
 | 
							this.emojisCache.dispose();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,7 @@ export class RelayService {
 | 
				
			||||||
		private createSystemUserService: CreateSystemUserService,
 | 
							private createSystemUserService: CreateSystemUserService,
 | 
				
			||||||
		private apRendererService: ApRendererService,
 | 
							private apRendererService: ApRendererService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10);
 | 
							this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10); // 10m
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -129,10 +129,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 | 
				
			||||||
		private moderationLogService: ModerationLogService,
 | 
							private moderationLogService: ModerationLogService,
 | 
				
			||||||
		private fanoutTimelineService: FanoutTimelineService,
 | 
							private fanoutTimelineService: FanoutTimelineService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		//this.onMessage = this.onMessage.bind(this);
 | 
							this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h
 | 
				
			||||||
 | 
							this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m
 | 
				
			||||||
		this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60 * 1);
 | 
					 | 
				
			||||||
		this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 60 * 1);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.redisForSub.on('message', this.onMessage);
 | 
							this.redisForSub.on('message', this.onMessage);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,7 +25,7 @@ export class UserKeypairService implements OnApplicationShutdown {
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		this.cache = new RedisKVCache<MiUserKeypair>(this.redisClient, 'userKeypair', {
 | 
							this.cache = new RedisKVCache<MiUserKeypair>(this.redisClient, 'userKeypair', {
 | 
				
			||||||
			lifetime: 1000 * 60 * 60 * 24, // 24h
 | 
								lifetime: 1000 * 60 * 60 * 24, // 24h
 | 
				
			||||||
			memoryCacheLifetime: Infinity,
 | 
								memoryCacheLifetime: 1000 * 60 * 60, // 1h
 | 
				
			||||||
			fetcher: (key) => this.userKeypairsRepository.findOneByOrFail({ userId: key }),
 | 
								fetcher: (key) => this.userKeypairsRepository.findOneByOrFail({ userId: key }),
 | 
				
			||||||
			toRedisConverter: (value) => JSON.stringify(value),
 | 
								toRedisConverter: (value) => JSON.stringify(value),
 | 
				
			||||||
			fromRedisConverter: (value) => JSON.parse(value),
 | 
								fromRedisConverter: (value) => JSON.parse(value),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,8 +54,8 @@ export class ApDbResolverService implements OnApplicationShutdown {
 | 
				
			||||||
		private cacheService: CacheService,
 | 
							private cacheService: CacheService,
 | 
				
			||||||
		private apPersonService: ApPersonService,
 | 
							private apPersonService: ApPersonService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		this.publicKeyCache = new MemoryKVCache<MiUserPublickey | null>(Infinity);
 | 
							this.publicKeyCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
 | 
				
			||||||
		this.publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(Infinity);
 | 
							this.publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,23 +7,23 @@ import * as Redis from 'ioredis';
 | 
				
			||||||
import { bindThis } from '@/decorators.js';
 | 
					import { bindThis } from '@/decorators.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class RedisKVCache<T> {
 | 
					export class RedisKVCache<T> {
 | 
				
			||||||
	private redisClient: Redis.Redis;
 | 
						private readonly lifetime: number;
 | 
				
			||||||
	private name: string;
 | 
						private readonly memoryCache: MemoryKVCache<T>;
 | 
				
			||||||
	private lifetime: number;
 | 
						private readonly fetcher: (key: string) => Promise<T>;
 | 
				
			||||||
	private memoryCache: MemoryKVCache<T>;
 | 
						private readonly toRedisConverter: (value: T) => string;
 | 
				
			||||||
	private fetcher: (key: string) => Promise<T>;
 | 
						private readonly fromRedisConverter: (value: string) => T | undefined;
 | 
				
			||||||
	private toRedisConverter: (value: T) => string;
 | 
					 | 
				
			||||||
	private fromRedisConverter: (value: string) => T | undefined;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(redisClient: RedisKVCache<T>['redisClient'], name: RedisKVCache<T>['name'], opts: {
 | 
						constructor(
 | 
				
			||||||
 | 
							private redisClient: Redis.Redis,
 | 
				
			||||||
 | 
							private name: string,
 | 
				
			||||||
 | 
							opts: {
 | 
				
			||||||
			lifetime: RedisKVCache<T>['lifetime'];
 | 
								lifetime: RedisKVCache<T>['lifetime'];
 | 
				
			||||||
			memoryCacheLifetime: number;
 | 
								memoryCacheLifetime: number;
 | 
				
			||||||
			fetcher: RedisKVCache<T>['fetcher'];
 | 
								fetcher: RedisKVCache<T>['fetcher'];
 | 
				
			||||||
			toRedisConverter: RedisKVCache<T>['toRedisConverter'];
 | 
								toRedisConverter: RedisKVCache<T>['toRedisConverter'];
 | 
				
			||||||
			fromRedisConverter: RedisKVCache<T>['fromRedisConverter'];
 | 
								fromRedisConverter: RedisKVCache<T>['fromRedisConverter'];
 | 
				
			||||||
	}) {
 | 
							},
 | 
				
			||||||
		this.redisClient = redisClient;
 | 
						) {
 | 
				
			||||||
		this.name = name;
 | 
					 | 
				
			||||||
		this.lifetime = opts.lifetime;
 | 
							this.lifetime = opts.lifetime;
 | 
				
			||||||
		this.memoryCache = new MemoryKVCache(opts.memoryCacheLifetime);
 | 
							this.memoryCache = new MemoryKVCache(opts.memoryCacheLifetime);
 | 
				
			||||||
		this.fetcher = opts.fetcher;
 | 
							this.fetcher = opts.fetcher;
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,13 @@ export class RedisKVCache<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`);
 | 
							const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`);
 | 
				
			||||||
		if (cached == null) return undefined;
 | 
							if (cached == null) return undefined;
 | 
				
			||||||
		return this.fromRedisConverter(cached);
 | 
					
 | 
				
			||||||
 | 
							const value = this.fromRedisConverter(cached);
 | 
				
			||||||
 | 
							if (value !== undefined) {
 | 
				
			||||||
 | 
								this.memoryCache.set(key, value);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return value;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
| 
						 | 
					@ -77,14 +83,14 @@ export class RedisKVCache<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Cache MISS
 | 
							// Cache MISS
 | 
				
			||||||
		const value = await this.fetcher(key);
 | 
							const value = await this.fetcher(key);
 | 
				
			||||||
		this.set(key, value);
 | 
							await this.set(key, value);
 | 
				
			||||||
		return value;
 | 
							return value;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	public async refresh(key: string) {
 | 
						public async refresh(key: string) {
 | 
				
			||||||
		const value = await this.fetcher(key);
 | 
							const value = await this.fetcher(key);
 | 
				
			||||||
		this.set(key, value);
 | 
							await this.set(key, value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
 | 
							// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -101,23 +107,23 @@ export class RedisKVCache<T> {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class RedisSingleCache<T> {
 | 
					export class RedisSingleCache<T> {
 | 
				
			||||||
	private redisClient: Redis.Redis;
 | 
						private readonly lifetime: number;
 | 
				
			||||||
	private name: string;
 | 
						private readonly memoryCache: MemorySingleCache<T>;
 | 
				
			||||||
	private lifetime: number;
 | 
						private readonly fetcher: () => Promise<T>;
 | 
				
			||||||
	private memoryCache: MemorySingleCache<T>;
 | 
						private readonly toRedisConverter: (value: T) => string;
 | 
				
			||||||
	private fetcher: () => Promise<T>;
 | 
						private readonly fromRedisConverter: (value: string) => T | undefined;
 | 
				
			||||||
	private toRedisConverter: (value: T) => string;
 | 
					 | 
				
			||||||
	private fromRedisConverter: (value: string) => T | undefined;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(redisClient: RedisSingleCache<T>['redisClient'], name: RedisSingleCache<T>['name'], opts: {
 | 
						constructor(
 | 
				
			||||||
		lifetime: RedisSingleCache<T>['lifetime'];
 | 
							private redisClient: Redis.Redis,
 | 
				
			||||||
 | 
							private name: string,
 | 
				
			||||||
 | 
							opts: {
 | 
				
			||||||
 | 
								lifetime: number;
 | 
				
			||||||
			memoryCacheLifetime: number;
 | 
								memoryCacheLifetime: number;
 | 
				
			||||||
			fetcher: RedisSingleCache<T>['fetcher'];
 | 
								fetcher: RedisSingleCache<T>['fetcher'];
 | 
				
			||||||
			toRedisConverter: RedisSingleCache<T>['toRedisConverter'];
 | 
								toRedisConverter: RedisSingleCache<T>['toRedisConverter'];
 | 
				
			||||||
			fromRedisConverter: RedisSingleCache<T>['fromRedisConverter'];
 | 
								fromRedisConverter: RedisSingleCache<T>['fromRedisConverter'];
 | 
				
			||||||
	}) {
 | 
							},
 | 
				
			||||||
		this.redisClient = redisClient;
 | 
						) {
 | 
				
			||||||
		this.name = name;
 | 
					 | 
				
			||||||
		this.lifetime = opts.lifetime;
 | 
							this.lifetime = opts.lifetime;
 | 
				
			||||||
		this.memoryCache = new MemorySingleCache(opts.memoryCacheLifetime);
 | 
							this.memoryCache = new MemorySingleCache(opts.memoryCacheLifetime);
 | 
				
			||||||
		this.fetcher = opts.fetcher;
 | 
							this.fetcher = opts.fetcher;
 | 
				
			||||||
| 
						 | 
					@ -149,7 +155,13 @@ export class RedisSingleCache<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const cached = await this.redisClient.get(`singlecache:${this.name}`);
 | 
							const cached = await this.redisClient.get(`singlecache:${this.name}`);
 | 
				
			||||||
		if (cached == null) return undefined;
 | 
							if (cached == null) return undefined;
 | 
				
			||||||
		return this.fromRedisConverter(cached);
 | 
					
 | 
				
			||||||
 | 
							const value = this.fromRedisConverter(cached);
 | 
				
			||||||
 | 
							if (value !== undefined) {
 | 
				
			||||||
 | 
								this.memoryCache.set(value);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return value;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
| 
						 | 
					@ -171,14 +183,14 @@ export class RedisSingleCache<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Cache MISS
 | 
							// Cache MISS
 | 
				
			||||||
		const value = await this.fetcher();
 | 
							const value = await this.fetcher();
 | 
				
			||||||
		this.set(value);
 | 
							await this.set(value);
 | 
				
			||||||
		return value;
 | 
							return value;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	public async refresh() {
 | 
						public async refresh() {
 | 
				
			||||||
		const value = await this.fetcher();
 | 
							const value = await this.fetcher();
 | 
				
			||||||
		this.set(value);
 | 
							await this.set(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
 | 
							// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -187,22 +199,12 @@ export class RedisSingleCache<T> {
 | 
				
			||||||
// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
 | 
					// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class MemoryKVCache<T> {
 | 
					export class MemoryKVCache<T> {
 | 
				
			||||||
	/**
 | 
						private readonly cache = new Map<string, { date: number; value: T; }>();
 | 
				
			||||||
	 * データを持つマップ
 | 
						private readonly gcIntervalHandle = setInterval(() => this.gc(), 1000 * 60 * 3); // 3m
 | 
				
			||||||
	 * @deprecated これを直接操作するべきではない
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	public cache: Map<string, { date: number; value: T; }>;
 | 
					 | 
				
			||||||
	private lifetime: number;
 | 
					 | 
				
			||||||
	private gcIntervalHandle: NodeJS.Timeout;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(lifetime: MemoryKVCache<never>['lifetime']) {
 | 
						constructor(
 | 
				
			||||||
		this.cache = new Map();
 | 
							private readonly lifetime: number,
 | 
				
			||||||
		this.lifetime = lifetime;
 | 
						) {}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.gcIntervalHandle = setInterval(() => {
 | 
					 | 
				
			||||||
			this.gc();
 | 
					 | 
				
			||||||
		}, 1000 * 60 * 3);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
| 
						 | 
					@ -287,27 +289,34 @@ export class MemoryKVCache<T> {
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	public gc(): void {
 | 
						public gc(): void {
 | 
				
			||||||
		const now = Date.now();
 | 
							const now = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (const [key, { date }] of this.cache.entries()) {
 | 
							for (const [key, { date }] of this.cache.entries()) {
 | 
				
			||||||
			if ((now - date) > this.lifetime) {
 | 
								// The map is ordered from oldest to youngest.
 | 
				
			||||||
 | 
								// We can stop once we find an entry that's still active, because all following entries must *also* be active.
 | 
				
			||||||
 | 
								const age = now - date;
 | 
				
			||||||
 | 
								if (age < this.lifetime) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.cache.delete(key);
 | 
								this.cache.delete(key);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	public dispose(): void {
 | 
						public dispose(): void {
 | 
				
			||||||
		clearInterval(this.gcIntervalHandle);
 | 
							clearInterval(this.gcIntervalHandle);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public get entries() {
 | 
				
			||||||
 | 
							return this.cache.entries();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class MemorySingleCache<T> {
 | 
					export class MemorySingleCache<T> {
 | 
				
			||||||
	private cachedAt: number | null = null;
 | 
						private cachedAt: number | null = null;
 | 
				
			||||||
	private value: T | undefined;
 | 
						private value: T | undefined;
 | 
				
			||||||
	private lifetime: number;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(lifetime: MemorySingleCache<never>['lifetime']) {
 | 
						constructor(
 | 
				
			||||||
		this.lifetime = lifetime;
 | 
							private lifetime: number,
 | 
				
			||||||
	}
 | 
						) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	public set(value: T): void {
 | 
						public set(value: T): void {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,7 @@ export class DeliverProcessorService {
 | 
				
			||||||
		private queueLoggerService: QueueLoggerService,
 | 
							private queueLoggerService: QueueLoggerService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
 | 
							this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
 | 
				
			||||||
		this.suspendedHostsCache = new MemorySingleCache<MiInstance[]>(1000 * 60 * 60);
 | 
							this.suspendedHostsCache = new MemorySingleCache<MiInstance[]>(1000 * 60 * 60); // 1h
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -135,7 +135,7 @@ export class NodeinfoServerService {
 | 
				
			||||||
			return document;
 | 
								return document;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
 | 
							const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10); // 10m
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fastify.get(nodeinfo2_1path, async (request, reply) => {
 | 
							fastify.get(nodeinfo2_1path, async (request, reply) => {
 | 
				
			||||||
			const base = await cache.fetch(() => nodeinfo2(21));
 | 
								const base = await cache.fetch(() => nodeinfo2(21));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,7 @@ export class AuthenticateService implements OnApplicationShutdown {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		private cacheService: CacheService,
 | 
							private cacheService: CacheService,
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		this.appCache = new MemoryKVCache<MiApp>(Infinity);
 | 
							this.appCache = new MemoryKVCache<MiApp>(1000 * 60 * 60 * 24 * 7); // 1w
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,8 +38,8 @@ export class UrlPreviewService {
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		this.logger = this.loggerService.getLogger('url-preview');
 | 
							this.logger = this.loggerService.getLogger('url-preview');
 | 
				
			||||||
		this.previewCache = new RedisKVCache<SummalyResult>(this.redisClient, 'summaly', {
 | 
							this.previewCache = new RedisKVCache<SummalyResult>(this.redisClient, 'summaly', {
 | 
				
			||||||
			lifetime: 1000 * 86400,
 | 
								lifetime: 1000 * 60 * 60 * 24, // 1d
 | 
				
			||||||
			memoryCacheLifetime: 1000 * 10 * 60,
 | 
								memoryCacheLifetime: 1000 * 60 * 10, // 10m
 | 
				
			||||||
			fetcher: (key: string) => { throw new Error('the UrlPreview cache should never fetch'); },
 | 
								fetcher: (key: string) => { throw new Error('the UrlPreview cache should never fetch'); },
 | 
				
			||||||
			toRedisConverter: (value) => JSON.stringify(value),
 | 
								toRedisConverter: (value) => JSON.stringify(value),
 | 
				
			||||||
			fromRedisConverter: (value) => JSON.parse(value),
 | 
								fromRedisConverter: (value) => JSON.parse(value),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue