From 8f41dfec2e27c35809f97399cee7e4ad6be21056 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 22 Mar 2021 12:41:33 +0900 Subject: [PATCH] perf(server): Reduce database query --- src/misc/populate-emojis.ts | 47 +++++++++++++++++++++++++ src/models/repositories/note.ts | 4 ++- src/models/repositories/notification.ts | 3 ++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/misc/populate-emojis.ts b/src/misc/populate-emojis.ts index f21e24b21..6b7443018 100644 --- a/src/misc/populate-emojis.ts +++ b/src/misc/populate-emojis.ts @@ -1,7 +1,10 @@ +import { In } from 'typeorm'; import { Emojis } from '../models'; import { Emoji } from '../models/entities/emoji'; +import { Note } from '../models/entities/note'; import { Cache } from './cache'; import { isSelfHost, toPunyNullable } from './convert-host'; +import { decodeReaction } from './reaction-lib'; const cache = new Cache(1000 * 60 * 60); @@ -70,3 +73,47 @@ export async function populateEmojis(emojiNames: string[], noteUserHost: string return emojis.filter((x): x is PopulatedEmoji => x != null); } +export function aggregateNoteEmojis(notes: Note[]) { + let emojis: { name: string | null; host: string | null; }[] = []; + for (const note of notes) { + emojis = emojis.concat(note.emojis + .map(e => parseEmojiStr(e, note.userHost))); + if (note.renote) { + emojis = emojis.concat(note.renote.emojis + .map(e => parseEmojiStr(e, note.renote!.userHost))); + if (note.renote.user) { + emojis = emojis.concat(note.renote.user.emojis + .map(e => parseEmojiStr(e, note.renote!.userHost))); + } + } + const customReactions = Object.keys(note.reactions).map(x => decodeReaction(x)).filter(x => x.name != null) as typeof emojis; + emojis = emojis.concat(customReactions); + if (note.user) { + emojis = emojis.concat(note.user.emojis + .map(e => parseEmojiStr(e, note.userHost))); + } + } + return emojis.filter(x => x.name != null) as { name: string; host: string | null; }[]; +} + +/** + * 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します + */ +export async function prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise { + const notCachedEmojis = emojis.filter(emoji => cache.get(`${emoji.name} ${emoji.host}`) == null); + const emojisQuery: any[] = []; + const hosts = new Set(notCachedEmojis.map(e => e.host)); + for (const host of hosts) { + emojisQuery.push({ + name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)), + host: host + }); + } + const _emojis = emojisQuery.length > 0 ? await Emojis.find({ + where: emojisQuery, + select: ['name', 'host', 'url'] + }) : []; + for (const emoji of _emojis) { + cache.set(`${emoji.name} ${emoji.host}`, emoji); + } +} diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index 354ea4dc7..73e18f6c5 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -8,7 +8,7 @@ import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '. import { toString } from '../../mfm/to-string'; import { parse } from '../../mfm/parse'; import { NoteReaction } from '../entities/note-reaction'; -import { populateEmojis } from '../../misc/populate-emojis'; +import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '../../misc/populate-emojis'; export type PackedNote = SchemaType; @@ -259,6 +259,8 @@ export class NoteRepository extends Repository { } } + await prefetchEmojis(aggregateNoteEmojis(notes)); + return await Promise.all(notes.map(n => this.pack(n, me, { ...options, _hint_: { diff --git a/src/models/repositories/notification.ts b/src/models/repositories/notification.ts index 4f6e797ef..83fe11d5f 100644 --- a/src/models/repositories/notification.ts +++ b/src/models/repositories/notification.ts @@ -6,6 +6,7 @@ import { SchemaType } from '../../misc/schema'; import { Note } from '../entities/note'; import { NoteReaction } from '../entities/note-reaction'; import { User } from '../entities/user'; +import { aggregateNoteEmojis, prefetchEmojis } from '../../misc/populate-emojis'; export type PackedNotification = SchemaType; @@ -98,6 +99,8 @@ export class NotificationRepository extends Repository { myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) || null); } + await prefetchEmojis(aggregateNoteEmojis(notes)); + return await Promise.all(notifications.map(x => this.pack(x, { _hintForEachNotes_: { myReactions: myReactionsMap