fix(backend): キャッシュが溜まり続けないように

Related #10984
This commit is contained in:
syuilo 2023-06-10 13:45:11 +09:00
parent 6182a1cb2c
commit e8420ad90b
10 changed files with 117 additions and 13 deletions

View file

@ -21,6 +21,7 @@
- エラー時や項目が存在しないときなどのアイコン画像をサーバー管理者が設定できるようになりました - エラー時や項目が存在しないときなどのアイコン画像をサーバー管理者が設定できるようになりました
### Server ### Server
- Fix: キャッシュが溜まり続けないように
- Fix: api/metaで`TypeError: JSON5.parse is not a function`エラーが発生する問題を修正 - Fix: api/metaで`TypeError: JSON5.parse is not a function`エラーが発生する問題を修正
## 13.13.0 ## 13.13.0

View file

@ -168,6 +168,17 @@ export class CacheService implements OnApplicationShutdown {
@bindThis @bindThis
public dispose(): void { public dispose(): void {
this.redisForSub.off('message', this.onMessage); this.redisForSub.off('message', this.onMessage);
this.userByIdCache.dispose();
this.localUserByNativeTokenCache.dispose();
this.localUserByIdCache.dispose();
this.uriPersonCache.dispose();
this.userProfileCache.dispose();
this.userMutingsCache.dispose();
this.userBlockingCache.dispose();
this.userBlockedCache.dispose();
this.renoteMutingsCache.dispose();
this.userFollowingsCache.dispose();
this.userFollowingChannelsCache.dispose();
} }
@bindThis @bindThis

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { DataSource, In, IsNull } from 'typeorm'; import { DataSource, In, IsNull } from 'typeorm';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -18,7 +18,7 @@ import type { Serialized } from '@/server/api/stream/types.js';
const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/; const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
@Injectable() @Injectable()
export class CustomEmojiService { export class CustomEmojiService implements OnApplicationShutdown {
private cache: MemoryKVCache<Emoji | null>; private cache: MemoryKVCache<Emoji | null>;
public localEmojisCache: RedisSingleCache<Map<string, Emoji>>; public localEmojisCache: RedisSingleCache<Map<string, Emoji>>;
@ -349,4 +349,14 @@ export class CustomEmojiService {
this.cache.set(`${emoji.name} ${emoji.host}`, emoji); this.cache.set(`${emoji.name} ${emoji.host}`, emoji);
} }
} }
@bindThis
public dispose(): void {
this.cache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
} }

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import type { InstancesRepository } from '@/models/index.js'; import type { InstancesRepository } from '@/models/index.js';
import type { Instance } from '@/models/entities/Instance.js'; import type { Instance } from '@/models/entities/Instance.js';
@ -9,7 +9,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class FederatedInstanceService { export class FederatedInstanceService implements OnApplicationShutdown {
public federatedInstanceCache: RedisKVCache<Instance | null>; public federatedInstanceCache: RedisKVCache<Instance | null>;
constructor( constructor(
@ -77,4 +77,14 @@ export class FederatedInstanceService {
this.federatedInstanceCache.set(result.host, result); this.federatedInstanceCache.set(result.host, result);
} }
@bindThis
public dispose(): void {
this.federatedInstanceCache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
} }

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import push from 'web-push'; import push from 'web-push';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -42,7 +42,7 @@ function truncateBody<T extends keyof PushNotificationsTypes>(type: T, body: Pus
} }
@Injectable() @Injectable()
export class PushNotificationService { export class PushNotificationService implements OnApplicationShutdown {
private subscriptionsCache: RedisKVCache<SwSubscription[]>; private subscriptionsCache: RedisKVCache<SwSubscription[]>;
constructor( constructor(
@ -115,4 +115,14 @@ export class PushNotificationService {
}); });
} }
} }
@bindThis
public dispose(): void {
this.subscriptionsCache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
} }

View file

@ -435,6 +435,7 @@ export class RoleService implements OnApplicationShutdown {
@bindThis @bindThis
public dispose(): void { public dispose(): void {
this.redisForSub.off('message', this.onMessage); this.redisForSub.off('message', this.onMessage);
this.roleAssignmentByUserIdCache.dispose();
} }
@bindThis @bindThis

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { UserKeypairsRepository } from '@/models/index.js'; import type { UserKeypairsRepository } from '@/models/index.js';
@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class UserKeypairService { export class UserKeypairService implements OnApplicationShutdown {
private cache: RedisKVCache<UserKeypair>; private cache: RedisKVCache<UserKeypair>;
constructor( constructor(
@ -31,4 +31,14 @@ export class UserKeypairService {
public async getUserKeypair(userId: User['id']): Promise<UserKeypair> { public async getUserKeypair(userId: User['id']): Promise<UserKeypair> {
return await this.cache.fetch(userId); return await this.cache.fetch(userId);
} }
@bindThis
public dispose(): void {
this.cache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
} }

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import escapeRegexp from 'escape-regexp'; import escapeRegexp from 'escape-regexp';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
@ -30,7 +30,7 @@ export type UriParseResult = {
}; };
@Injectable() @Injectable()
export class ApDbResolverService { export class ApDbResolverService implements OnApplicationShutdown {
private publicKeyCache: MemoryKVCache<UserPublickey | null>; private publicKeyCache: MemoryKVCache<UserPublickey | null>;
private publicKeyByUserIdCache: MemoryKVCache<UserPublickey | null>; private publicKeyByUserIdCache: MemoryKVCache<UserPublickey | null>;
@ -162,4 +162,15 @@ export class ApDbResolverService {
key, key,
}; };
} }
@bindThis
public dispose(): void {
this.publicKeyCache.dispose();
this.publicKeyByUserIdCache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
} }

View file

@ -83,6 +83,16 @@ export class RedisKVCache<T> {
// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする // TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
} }
@bindThis
public gc() {
this.memoryCache.gc();
}
@bindThis
public dispose() {
this.memoryCache.dispose();
}
} }
export class RedisSingleCache<T> { export class RedisSingleCache<T> {
@ -174,10 +184,15 @@ export class RedisSingleCache<T> {
export class MemoryKVCache<T> { export class MemoryKVCache<T> {
public cache: Map<string, { date: number; value: T; }>; public cache: Map<string, { date: number; value: T; }>;
private lifetime: number; private lifetime: number;
private gcIntervalHandle: NodeJS.Timer;
constructor(lifetime: MemoryKVCache<never>['lifetime']) { constructor(lifetime: MemoryKVCache<never>['lifetime']) {
this.cache = new Map(); this.cache = new Map();
this.lifetime = lifetime; this.lifetime = lifetime;
this.gcIntervalHandle = setInterval(() => {
this.gc();
}, 1000 * 60 * 3);
} }
@bindThis @bindThis
@ -200,7 +215,7 @@ export class MemoryKVCache<T> {
} }
@bindThis @bindThis
public delete(key: string) { public delete(key: string): void {
this.cache.delete(key); this.cache.delete(key);
} }
@ -255,6 +270,21 @@ export class MemoryKVCache<T> {
} }
return value; return value;
} }
@bindThis
public gc(): void {
const now = Date.now();
for (const [key, { date }] of this.cache.entries()) {
if ((now - date) > this.lifetime) {
this.cache.delete(key);
}
}
}
@bindThis
public dispose(): void {
clearInterval(this.gcIntervalHandle);
}
} }
export class MemorySingleCache<T> { export class MemorySingleCache<T> {

View file

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js'; import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js';
import type { LocalUser } from '@/models/entities/User.js'; import type { LocalUser } from '@/models/entities/User.js';
@ -17,7 +17,7 @@ export class AuthenticationError extends Error {
} }
@Injectable() @Injectable()
export class AuthenticateService { export class AuthenticateService implements OnApplicationShutdown {
private appCache: MemoryKVCache<App>; private appCache: MemoryKVCache<App>;
constructor( constructor(
@ -85,4 +85,14 @@ export class AuthenticateService {
} }
} }
} }
@bindThis
public dispose(): void {
this.appCache.dispose();
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
} }