Skip to content

Commit

Permalink
enhance(backend): EntityServiceなどでDB照会の結果を短時間キャッシュするように
Browse files Browse the repository at this point in the history
  • Loading branch information
u1-liquid committed Feb 25, 2024
1 parent ce98a86 commit c1ce9af
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 103 deletions.
5 changes: 3 additions & 2 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
"got": "14.2.0",
"happy-dom": "10.0.3",
"hpagent": "1.2.0",
"htmlescape": "^1.1.1",
"htmlescape": "1.1.1",
"http-link-header": "1.1.1",
"ioredis": "5.3.2",
"ip-cidr": "3.1.0",
Expand All @@ -129,6 +129,7 @@
"json5": "2.2.3",
"jsonld": "8.3.2",
"jsrsasign": "11.1.0",
"lru-cache": "10.2.0",
"meilisearch": "0.37.0",
"mfm-js": "0.24.0",
"microformats-parser": "2.0.2",
Expand Down Expand Up @@ -197,7 +198,7 @@
"@types/color-convert": "2.0.3",
"@types/content-disposition": "0.5.8",
"@types/fluent-ffmpeg": "2.1.24",
"@types/htmlescape": "^1.1.3",
"@types/htmlescape": "1.1.3",
"@types/http-link-header": "1.0.5",
"@types/jest": "29.5.12",
"@types/js-yaml": "4.0.9",
Expand Down
45 changes: 45 additions & 0 deletions packages/backend/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export type RedisOptionsSource = Partial<RedisOptions> & {
prefix?: string;
};

export type MemoryCacheOptions = {
max?: number;
ttl?: number;
};

/**
* 設定ファイルの型
*/
Expand Down Expand Up @@ -66,6 +71,20 @@ type Source = {
scope?: 'local' | 'global' | string[];
};

memoryCache?: {
database?: MemoryCacheOptions;
user?: MemoryCacheOptions;
userProfile?: MemoryCacheOptions;
notification?: MemoryCacheOptions;
relation?: MemoryCacheOptions;
role?: MemoryCacheOptions;
channel?: MemoryCacheOptions;
driveFile?: MemoryCacheOptions;
note?: MemoryCacheOptions;
poll?: MemoryCacheOptions;
page?: MemoryCacheOptions;
};

proxy?: string;
proxySmtp?: string;
proxyBypassHosts?: string[];
Expand Down Expand Up @@ -137,6 +156,19 @@ export type Config = {
index: string;
scope?: 'local' | 'global' | string[];
} | undefined;
memoryCache: {
database: MemoryCacheOptions;
user: MemoryCacheOptions;
userProfile: MemoryCacheOptions;
notification: MemoryCacheOptions;
relation: MemoryCacheOptions;
role: MemoryCacheOptions;
channel: MemoryCacheOptions;
driveFile: MemoryCacheOptions;
note: MemoryCacheOptions;
poll: MemoryCacheOptions;
page: MemoryCacheOptions;
};
proxy: string | undefined;
proxySmtp: string | undefined;
proxyBypassHosts: string[] | undefined;
Expand Down Expand Up @@ -261,6 +293,19 @@ export function loadConfig(): Config {
redisForObjectStorageQueue: config.redisForObjectStorageQueue ? convertRedisOptions(config.redisForObjectStorageQueue, host) : redisForJobQueue,
redisForWebhookDeliverQueue: config.redisForWebhookDeliverQueue ? convertRedisOptions(config.redisForWebhookDeliverQueue, host) : redisForJobQueue,
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
memoryCache: {
database: config.memoryCache?.database ?? { max: undefined, ttl: undefined },
user: config.memoryCache?.user ?? { max: undefined, ttl: undefined },
userProfile: config.memoryCache?.userProfile ?? { max: undefined, ttl: undefined },
notification: config.memoryCache?.notification ?? { max: undefined, ttl: undefined },
relation: config.memoryCache?.relation ?? { max: undefined, ttl: undefined },
role: config.memoryCache?.role ?? { max: undefined, ttl: undefined },
channel: config.memoryCache?.channel ?? { max: undefined, ttl: undefined },
driveFile: config.memoryCache?.driveFile ?? { max: undefined, ttl: undefined },
note: config.memoryCache?.note ?? { max: undefined, ttl: undefined },
poll: config.memoryCache?.poll ?? { max: undefined, ttl: undefined },
page: config.memoryCache?.page ?? { max: undefined, ttl: undefined },
},
id: config.id,
proxy: config.proxy,
proxySmtp: config.proxySmtp,
Expand Down
25 changes: 25 additions & 0 deletions packages/backend/src/core/CacheService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export class CacheService implements OnApplicationShutdown {
case 'userChangeDeletedState':
case 'remoteUserUpdated':
case 'localUserUpdated': {
this.userEntityService.purgeCache(body.id);
const user = await this.usersRepository.findOneBy({ id: body.id });
if (user == null) {
this.userByIdCache.delete(body.id);
Expand All @@ -155,6 +156,7 @@ export class CacheService implements OnApplicationShutdown {
break;
}
case 'userTokenRegenerated': {
this.userEntityService.purgeCache(body.id);
const user = await this.usersRepository.findOneByOrFail({ id: body.id }) as MiLocalUser;
this.localUserByNativeTokenCache.delete(body.oldToken);
this.localUserByNativeTokenCache.set(body.newToken, user);
Expand All @@ -171,6 +173,29 @@ export class CacheService implements OnApplicationShutdown {
default:
break;
}
} else if (obj.channel.startsWith('mainStream:')) {
const { type } = obj.message as GlobalEvents['main']['payload'];
switch (type) {
case 'meUpdated':
case 'readAllNotifications':
case 'unreadNotification':
case 'unreadMention':
case 'readAllUnreadMentions':
case 'unreadSpecifiedNote':
case 'readAllUnreadSpecifiedNotes':
case 'readAllAntennas':
case 'unreadAntenna':
case 'readAllAnnouncements':
case 'readAntenna':
case 'receiveFollowRequest':
case 'announcementCreated': {
const userId = obj.channel.slice(11);
this.userEntityService.purgeCache(userId);
break;
}
default:
break;
}
}
}

Expand Down
38 changes: 34 additions & 4 deletions packages/backend/src/core/entities/ChannelEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,30 @@
*/

import { Inject, Injectable } from '@nestjs/common';
import { LRUCache } from 'lru-cache';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NotesRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import type { Config } from '@/config.js';
import type { MiUser } from '@/models/User.js';
import type { MiChannel } from '@/models/Channel.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import { cacheFetch, cacheFetchOrFail } from '@/misc/cache-fetch.js';
import { DriveFileEntityService } from './DriveFileEntityService.js';
import { NoteEntityService } from './NoteEntityService.js';

@Injectable()
export class ChannelEntityService {
private readonly channelCache: LRUCache<string, MiChannel>;
private readonly bannerCache: LRUCache<string, MiDriveFile>;

constructor(
@Inject(DI.config)
private config: Config,

@Inject(DI.channelsRepository)
private channelsRepository: ChannelsRepository,

Expand All @@ -37,6 +47,27 @@ export class ChannelEntityService {
private driveFileEntityService: DriveFileEntityService,
private idService: IdService,
) {
this.channelCache = new LRUCache({
max: this.config.memoryCache.channel.max ?? 1,
ttl: this.config.memoryCache.channel.ttl ?? 100,

ignoreFetchAbort: true,
ttlResolution: 1000 * 30,
fetchMethod: async (id: string) => {
return await this.channelsRepository.findOneByOrFail({ id });
},
});

this.bannerCache = new LRUCache({
max: this.config.memoryCache.driveFile.max ?? 1,
ttl: this.config.memoryCache.driveFile.ttl ?? 100,

ignoreFetchAbort: true,
ttlResolution: 1000 * 30,
fetchMethod: async (id: string) => {
return await this.driveFilesRepository.findOneByOrFail({ id });
},
});
}

@bindThis
Expand All @@ -45,11 +76,10 @@ export class ChannelEntityService {
me: { id: MiUser['id'] } | null | undefined,
detailed?: boolean,
): Promise<Packed<'Channel'>> {
const channel = typeof src === 'object' ? src : await this.channelsRepository.findOneByOrFail({ id: src });
const meId = me ? me.id : null;

const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null;
const channel = typeof src === 'object' ? src : await cacheFetchOrFail(this.channelCache, src);
const banner = channel.bannerId ? await cacheFetch(this.bannerCache, channel.bannerId) : null;

const meId = me ? me.id : null;
const isFollowing = meId ? await this.channelFollowingsRepository.exists({
where: {
followerId: meId,
Expand Down
24 changes: 19 additions & 5 deletions packages/backend/src/core/entities/DriveFileEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { LRUCache } from 'lru-cache';
import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import type { Packed } from '@/misc/json-schema.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
import type { Config } from '@/config.js';
import type { MiUser } from '@/models/User.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import { appendQuery, query } from '@/misc/prelude/url.js';
Expand All @@ -18,6 +19,7 @@ import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
import { isNotNull } from '@/misc/is-not-null.js';
import { IdService } from '@/core/IdService.js';
import { cacheFetch, cacheFetchOrFail } from '@/misc/cache-fetch.js';
import { UtilityService } from '../UtilityService.js';
import { VideoProcessingService } from '../VideoProcessingService.js';
import { UserEntityService } from './UserEntityService.js';
Expand All @@ -31,6 +33,8 @@ type PackOptions = {

@Injectable()
export class DriveFileEntityService {
private readonly driveFileCache: LRUCache<string, MiDriveFile>;

constructor(
@Inject(DI.config)
private config: Config,
Expand All @@ -47,6 +51,16 @@ export class DriveFileEntityService {
private videoProcessingService: VideoProcessingService,
private idService: IdService,
) {
this.driveFileCache = new LRUCache({
max: this.config.memoryCache.driveFile.max ?? 1,
ttl: this.config.memoryCache.driveFile.ttl ?? 100,

ignoreFetchAbort: true,
ttlResolution: 1000 * 30,
fetchMethod: async (id: string) => {
return await this.driveFilesRepository.findOneByOrFail({ id });
},
});
}

@bindThis
Expand Down Expand Up @@ -195,7 +209,7 @@ export class DriveFileEntityService {
self: false,
}, options);

const file = typeof src === 'object' ? src : await this.driveFilesRepository.findOneByOrFail({ id: src });
const file = typeof src === 'object' ? src : await cacheFetchOrFail(this.driveFileCache, src);

return await awaitAll<Packed<'DriveFile'>>({
id: file.id,
Expand Down Expand Up @@ -230,7 +244,7 @@ export class DriveFileEntityService {
self: false,
}, options);

const file = typeof src === 'object' ? src : await this.driveFilesRepository.findOneBy({ id: src });
const file = typeof src === 'object' ? src : await cacheFetch(this.driveFileCache, src);
if (file == null) return null;

return await awaitAll<Packed<'DriveFile'>>({
Expand Down Expand Up @@ -273,7 +287,7 @@ export class DriveFileEntityService {
options?: PackOptions,
): Promise<Map<Packed<'DriveFile'>['id'], Packed<'DriveFile'> | null>> {
if (fileIds.length === 0) return new Map();
const files = await this.driveFilesRepository.findBy({ id: In(fileIds) });
const files = await this.driveFilesRepository.find({ where: { id: In(fileIds) }, cache: this.config.memoryCache.driveFile.ttl });
const packedFiles = await this.packMany(files, me, options);
const map = new Map<Packed<'DriveFile'>['id'], Packed<'DriveFile'> | null>(packedFiles.map(f => [f.id, f]));
for (const id of fileIds) {
Expand Down
Loading

0 comments on commit c1ce9af

Please sign in to comment.