From 54e1c7f60ed69d32b3358841ee049d3f143ec3bf Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 26 Mar 2022 19:09:57 +0900
Subject: [PATCH] perf(server): use cached user info in getUserFromApId

---
 packages/backend/src/misc/cache.ts            | 27 ++++++++++++++++++-
 .../src/remote/activitypub/db-resolver.ts     | 14 +++++-----
 .../remote/activitypub/kernel/block/index.ts  |  2 +-
 .../backend/src/services/blocking/delete.ts   |  4 +--
 .../src/services/following/requests/accept.ts |  4 +--
 5 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index 9ce5c3e8b..01bbe98a8 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -48,7 +48,32 @@ export class Cache<T> {
 
 		// Cache MISS
 		const value = await fetcher();
-		this.set(key, value);
+		return value;
+	}
+
+	/**
+	 * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
+	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
+	 */
+	public async fetchMaybe(key: string | null, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
+		const cachedValue = this.get(key);
+		if (cachedValue !== undefined) {
+			if (validator) {
+				if (validator(cachedValue)) {
+					// Cache HIT
+					return cachedValue;
+				}
+			} else {
+				// Cache HIT
+				return cachedValue;
+			}
+		}
+
+		// Cache MISS
+		const value = await fetcher();
+		if (value !== undefined) {
+			this.set(key, value);
+		}
 		return value;
 	}
 }
diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts
index 3d61296bf..ef07966e4 100644
--- a/packages/backend/src/remote/activitypub/db-resolver.ts
+++ b/packages/backend/src/remote/activitypub/db-resolver.ts
@@ -1,14 +1,14 @@
 import escapeRegexp from 'escape-regexp';
 import config from '@/config/index.js';
 import { Note } from '@/models/entities/note.js';
-import { User, IRemoteUser, CacheableRemoteUser } from '@/models/entities/user.js';
+import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js';
 import { UserPublickey } from '@/models/entities/user-publickey.js';
 import { MessagingMessage } from '@/models/entities/messaging-message.js';
 import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js';
 import { IObject, getApId } from './type.js';
 import { resolvePerson } from './models/person.js';
 import { Cache } from '@/misc/cache.js';
-import { userByIdCache } from '@/services/user-cache.js';
+import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';
 
 const publicKeyCache = new Cache<UserPublickey | null>(Infinity);
 const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
@@ -59,19 +59,19 @@ export default class DbResolver {
 	/**
 	 * AP Person => Misskey User in DB
 	 */
-	public async getUserFromApId(value: string | IObject): Promise<User | null> {
+	public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> {
 		const parsed = this.parseUri(value);
 
 		if (parsed.id) {
-			return await Users.findOneBy({
+			return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({
 				id: parsed.id,
-			});
+			}).then(x => x ?? undefined)) ?? null;
 		}
 
 		if (parsed.uri) {
-			return await Users.findOneBy({
+			return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({
 				uri: parsed.uri,
-			});
+			}));
 		}
 
 		return null;
diff --git a/packages/backend/src/remote/activitypub/kernel/block/index.ts b/packages/backend/src/remote/activitypub/kernel/block/index.ts
index 23cee81d5..5e230ad7b 100644
--- a/packages/backend/src/remote/activitypub/kernel/block/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/block/index.ts
@@ -18,6 +18,6 @@ export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<str
 		return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`;
 	}
 
-	await block(await Users.findOneByOrFail({ id: actor.id }), blockee);
+	await block(await Users.findOneByOrFail({ id: actor.id }), await Users.findOneByOrFail({ id: blockee.id }));
 	return `ok`;
 };
diff --git a/packages/backend/src/services/blocking/delete.ts b/packages/backend/src/services/blocking/delete.ts
index f3a509bb0..d7b5ddd5f 100644
--- a/packages/backend/src/services/blocking/delete.ts
+++ b/packages/backend/src/services/blocking/delete.ts
@@ -3,12 +3,12 @@ import renderBlock from '@/remote/activitypub/renderer/block.js';
 import renderUndo from '@/remote/activitypub/renderer/undo.js';
 import { deliver } from '@/queue/index.js';
 import Logger from '../logger.js';
-import { User } from '@/models/entities/user.js';
+import { CacheableUser, User } from '@/models/entities/user.js';
 import { Blockings, Users } from '@/models/index.js';
 
 const logger = new Logger('blocking/delete');
 
-export default async function(blocker: User, blockee: User) {
+export default async function(blocker: CacheableUser, blockee: CacheableUser) {
 	const blocking = await Blockings.findOneBy({
 		blockerId: blocker.id,
 		blockeeId: blockee.id,
diff --git a/packages/backend/src/services/following/requests/accept.ts b/packages/backend/src/services/following/requests/accept.ts
index fc9d4062e..20829f70c 100644
--- a/packages/backend/src/services/following/requests/accept.ts
+++ b/packages/backend/src/services/following/requests/accept.ts
@@ -4,11 +4,11 @@ import renderAccept from '@/remote/activitypub/renderer/accept.js';
 import { deliver } from '@/queue/index.js';
 import { publishMainStream } from '@/services/stream.js';
 import { insertFollowingDoc } from '../create.js';
-import { User, ILocalUser } from '@/models/entities/user.js';
+import { User, ILocalUser, CacheableUser } from '@/models/entities/user.js';
 import { FollowRequests, Users } from '@/models/index.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
 
-export default async function(followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, follower: User) {
+export default async function(followee: { id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; }, follower: CacheableUser) {
 	const request = await FollowRequests.findOneBy({
 		followeeId: followee.id,
 		followerId: follower.id,