From ff196401715bd25482bed0643005e1284bd3d18c Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 21 Mar 2022 20:43:43 +0900
Subject: [PATCH] perf(server): reduce db query

---
 locales/ja-JP.yml                             |  1 +
 packages/backend/src/services/note/create.ts  | 38 +++++++++++++------
 .../client/src/pages/settings/word-mute.vue   |  2 +-
 3 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index e2cf11b49d..befa92bd36 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -840,6 +840,7 @@ tenMinutes: "10分"
 oneHour: "1時間"
 oneDay: "1æ—¥"
 oneWeek: "1週間"
+reflectMayTakeTime: "反映されるまで時間がかかる場合があります。"
 
 _emailUnavailable:
   used: "既に使用されています"
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index 8c5f133628..2e8b2ffa0b 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -35,6 +35,12 @@ import { Channel } from '@/models/entities/channel.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
 import { getAntennas } from '@/misc/antenna-cache.js';
 import { endedPollNotificationQueue } from '@/queue/queues.js';
+import { Cache } from '@/misc/cache.js';
+import { UserProfile } from '@/models/entities/user-profile.js';
+
+const usersCache = new Cache<MinimumUser>(Infinity);
+
+const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -91,6 +97,13 @@ class NotificationManager {
 	}
 }
 
+type MinimumUser = {
+	id: User['id'];
+	host: User['host'];
+	username: User['username'];
+	uri: User['uri'];
+};
+
 type Option = {
 	createdAt?: Date | null;
 	name?: string | null;
@@ -102,9 +115,9 @@ type Option = {
 	localOnly?: boolean | null;
 	cw?: string | null;
 	visibility?: string;
-	visibleUsers?: User[] | null;
+	visibleUsers?: MinimumUser[] | null;
 	channel?: Channel | null;
-	apMentions?: User[] | null;
+	apMentions?: MinimumUser[] | null;
 	apHashtags?: string[] | null;
 	apEmojis?: string[] | null;
 	uri?: string | null;
@@ -199,7 +212,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
 	tags = tags.filter(tag => Array.from(tag || '').length <= 128).splice(0, 32);
 
 	if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {
-		mentionedUsers.push(await Users.findOneOrFail(data.reply.userId));
+		mentionedUsers.push(await usersCache.fetch(data.reply.userId, () => Users.findOneOrFail(data.reply!.userId)));
 	}
 
 	if (data.visibility === 'specified') {
@@ -212,7 +225,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
 		}
 
 		if (data.reply && !data.visibleUsers.some(x => x.id === data.reply!.userId)) {
-			data.visibleUsers.push(await Users.findOneOrFail(data.reply.userId));
+			data.visibleUsers.push(await usersCache.fetch(data.reply.userId, () => Users.findOneOrFail(data.reply!.userId)));
 		}
 	}
 
@@ -241,10 +254,12 @@ export default async (user: { id: User['id']; username: User['username']; host:
 	incNotesCountOfUser(user);
 
 	// Word mute
-	// TODO: cache
-	UserProfiles.find({
-		enableWordMute: true,
-	}).then(us => {
+	mutedWordsCache.fetch(null, () => UserProfiles.find({
+		where: {
+			enableWordMute: true,
+		},
+		select: ['userId', 'mutedWords'],
+	})).then(us => {
 		for (const u of us) {
 			checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => {
 				if (shouldMute) {
@@ -260,11 +275,12 @@ export default async (user: { id: User['id']; username: User['username']; host:
 	});
 
 	// Antenna
+	 // TODO: キャッシュしたい
 	Followings.createQueryBuilder('following')
 		.andWhere(`following.followeeId = :userId`, { userId: note.userId })
 		.getMany()
 		.then(async followings => {
-			const blockings = await Blockings.find({ blockerId: user.id }); // TODO: キャッシュしたい
+			const blockings = await Blockings.find({ blockerId: user.id });
 			const followers = followings.map(f => f.followerId);
 			for (const antenna of (await getAntennas())) {
 				if (blockings.some(blocking => blocking.blockeeId === antenna.userId)) continue; // この処理は checkHitAntenna 内でやるようにしてもいいかも
@@ -465,7 +481,7 @@ function incRenoteCount(renote: Note) {
 		.execute();
 }
 
-async function insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) {
+async function insertNote(user: { id: User['id']; host: User['host']; }, data: Option, tags: string[], emojis: string[], mentionedUsers: MinimumUser[]) {
 	const insert = new Note({
 		id: genId(data.createdAt!),
 		createdAt: data.createdAt!,
@@ -597,7 +613,7 @@ async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; },
 	}
 }
 
-async function createMentionedEvents(mentionedUsers: User[], note: Note, nm: NotificationManager) {
+async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) {
 	for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) {
 		const threadMuted = await NoteThreadMutings.findOne({
 			userId: u.id,
diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue
index 2767e6351a..c11707b6cf 100644
--- a/packages/client/src/pages/settings/word-mute.vue
+++ b/packages/client/src/pages/settings/word-mute.vue
@@ -13,7 +13,7 @@
 			</FormTextarea>
 		</div>
 		<div v-show="tab === 'hard'">
-			<MkInfo class="_formBlock">{{ $ts._wordMute.hardDescription }}</MkInfo>
+			<MkInfo class="_formBlock">{{ $ts._wordMute.hardDescription }} {{ $ts.reflectMayTakeTime }}</MkInfo>
 			<FormTextarea v-model="hardMutedWords" class="_formBlock">
 				<span>{{ $ts._wordMute.muteWords }}</span>
 				<template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template>
-- 
GitLab