From 7c3086e9d9508d5df03d7859932c766a26b9664e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 22 Mar 2021 15:14:54 +0900
Subject: [PATCH] perf(server): Cache user keypair

---
 src/misc/keypair-store.ts                 | 10 ++++++++++
 src/remote/activitypub/renderer/index.ts  |  6 ++----
 src/remote/activitypub/renderer/person.ts |  5 +++--
 src/remote/activitypub/request.ts         | 10 +++-------
 src/server/activitypub.ts                 |  5 +++--
 5 files changed, 21 insertions(+), 15 deletions(-)
 create mode 100644 src/misc/keypair-store.ts

diff --git a/src/misc/keypair-store.ts b/src/misc/keypair-store.ts
new file mode 100644
index 0000000000..43f451110c
--- /dev/null
+++ b/src/misc/keypair-store.ts
@@ -0,0 +1,10 @@
+import { UserKeypairs } from '../models';
+import { User } from '../models/entities/user';
+import { UserKeypair } from '../models/entities/user-keypair';
+import { Cache } from './cache';
+
+const cache = new Cache<UserKeypair>(Infinity);
+
+export async function getUserKeypair(userId: User['id']): Promise<UserKeypair> {
+	return await cache.fetch(userId, async () => await UserKeypairs.findOneOrFail(userId));
+}
diff --git a/src/remote/activitypub/renderer/index.ts b/src/remote/activitypub/renderer/index.ts
index e74affdadf..4c33fdafb1 100644
--- a/src/remote/activitypub/renderer/index.ts
+++ b/src/remote/activitypub/renderer/index.ts
@@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid';
 import { IActivity } from '../type';
 import { LdSignature } from '../misc/ld-signature';
 import { ILocalUser } from '../../../models/entities/user';
-import { UserKeypairs } from '../../../models';
+import { getUserKeypair } from '../../../misc/keypair-store';
 
 export const renderActivity = (x: any): IActivity | null => {
 	if (x == null) return null;
@@ -23,9 +23,7 @@ export const renderActivity = (x: any): IActivity | null => {
 export const attachLdSignature = async (activity: any, user: ILocalUser): Promise<IActivity | null> => {
 	if (activity == null) return null;
 
-	const keypair = await UserKeypairs.findOneOrFail({
-		userId: user.id
-	});
+	const keypair = await getUserKeypair(user.id);
 
 	const obj = {
 		// as non-standards
diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts
index 4907e3bc6f..479e6d76bf 100644
--- a/src/remote/activitypub/renderer/person.ts
+++ b/src/remote/activitypub/renderer/person.ts
@@ -8,7 +8,8 @@ import { getEmojis } from './note';
 import renderEmoji from './emoji';
 import { IIdentifier } from '../models/identifier';
 import renderHashtag from './hashtag';
-import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models';
+import { DriveFiles, UserProfiles } from '../../../models';
+import { getUserKeypair } from '../../../misc/keypair-store';
 
 export async function renderPerson(user: ILocalUser) {
 	const id = `${config.url}/users/${user.id}`;
@@ -49,7 +50,7 @@ export async function renderPerson(user: ILocalUser) {
 		...hashtagTags,
 	];
 
-	const keypair = await UserKeypairs.findOneOrFail(user.id);
+	const keypair = await getUserKeypair(user.id);
 
 	const person = {
 		type: isSystem ? 'Application' : user.isBot ? 'Service' : 'Person',
diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts
index 2f07351635..5f15d5480c 100644
--- a/src/remote/activitypub/request.ts
+++ b/src/remote/activitypub/request.ts
@@ -5,11 +5,11 @@ import * as crypto from 'crypto';
 
 import config from '../../config';
 import { ILocalUser } from '../../models/entities/user';
-import { UserKeypairs } from '../../models';
 import { getAgentByUrl } from '../../misc/fetch';
 import { URL } from 'url';
 import got from 'got';
 import * as Got from 'got';
+import { getUserKeypair } from '../../misc/keypair-store';
 
 export default async (user: ILocalUser, url: string, object: any) => {
 	const timeout = 10 * 1000;
@@ -22,9 +22,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
 	sha256.update(data);
 	const hash = sha256.digest('base64');
 
-	const keypair = await UserKeypairs.findOneOrFail({
-		userId: user.id
-	});
+	const keypair = await getUserKeypair(user.id);
 
 	await new Promise((resolve, reject) => {
 		const req = https.request({
@@ -74,9 +72,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
 export async function signedGet(url: string, user: ILocalUser) {
 	const timeout = 10 * 1000;
 
-	const keypair = await UserKeypairs.findOneOrFail({
-		userId: user.id
-	});
+	const keypair = await getUserKeypair(user.id);
 
 	const req = got.get<any>(url, {
 		headers: {
diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts
index bf71258625..694807239b 100644
--- a/src/server/activitypub.ts
+++ b/src/server/activitypub.ts
@@ -13,10 +13,11 @@ import Following from './activitypub/following';
 import Featured from './activitypub/featured';
 import { inbox as processInbox } from '../queue';
 import { isSelfHost } from '../misc/convert-host';
-import { Notes, Users, Emojis, UserKeypairs, NoteReactions } from '../models';
+import { Notes, Users, Emojis, NoteReactions } from '../models';
 import { ILocalUser, User } from '../models/entities/user';
 import { In } from 'typeorm';
 import { renderLike } from '../remote/activitypub/renderer/like';
+import { getUserKeypair } from '../misc/keypair-store';
 
 // Init router
 const router = new Router();
@@ -135,7 +136,7 @@ router.get('/users/:user/publickey', async ctx => {
 		return;
 	}
 
-	const keypair = await UserKeypairs.findOneOrFail(user.id);
+	const keypair = await getUserKeypair(user.id);
 
 	if (Users.isLocalUser(user)) {
 		ctx.body = renderActivity(renderKey(user, keypair));