summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-10-19 09:20:19 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-10-19 09:20:19 +0900
commit1671575d5d3d081c83f172f3439884010aafeb59 (patch)
tree662c64008c53d3290269ed5b69ceb8e773d60837 /packages/backend/src
parentperf(backend): improve my reaction population performance (diff)
downloadsharkey-1671575d5d3d081c83f172f3439884010aafeb59.tar.gz
sharkey-1671575d5d3d081c83f172f3439884010aafeb59.tar.bz2
sharkey-1671575d5d3d081c83f172f3439884010aafeb59.zip
perf(backend): ノートのリアクション情報をキャッシュすることでDBへのクエリを削減
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/core/NoteCreateService.ts2
-rw-r--r--packages/backend/src/core/ReactionService.ts4
-rw-r--r--packages/backend/src/core/entities/NoteEntityService.ts75
-rw-r--r--packages/backend/src/models/Note.ts5
-rw-r--r--packages/backend/src/models/json-schema/note.ts8
-rw-r--r--packages/backend/src/server/api/stream/channels/channel.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/global-timeline.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/hashtag.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/home-timeline.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/hybrid-timeline.ts3
-rw-r--r--packages/backend/src/server/api/stream/channels/local-timeline.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/user-list.ts2
12 files changed, 86 insertions, 23 deletions
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 30d7f3e76a..2c00418c47 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -584,7 +584,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}
// Pack the note
- const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true });
+ const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
this.globalEventService.publishNotesStream(noteObj);
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 1458e2b173..edf433025d 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -187,6 +187,9 @@ export class ReactionService {
await this.notesRepository.createQueryBuilder().update()
.set({
reactions: () => sql,
+ ...(note.reactionAndUserPairCache.length < 10 ? {
+ reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}:${reaction}')`,
+ } : {}),
})
.where('id = :id', { id: note.id })
.execute();
@@ -293,6 +296,7 @@ export class ReactionService {
await this.notesRepository.createQueryBuilder().update()
.set({
reactions: () => sql,
+ reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}:${exist.reaction}')`,
})
.where('id = :id', { id: note.id })
.execute();
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 66dbb66167..b46b5528a5 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -170,26 +170,37 @@ export class NoteEntityService implements OnModuleInit {
}
@bindThis
- public async populateMyReaction(noteId: MiNote['id'], meId: MiUser['id'], _hint_?: {
- myReactions: Map<MiNote['id'], MiNoteReaction | null>;
+ public async populateMyReaction(note: { id: MiNote['id']; reactions: MiNote['reactions']; reactionAndUserPairCache?: MiNote['reactionAndUserPairCache']; }, meId: MiUser['id'], _hint_?: {
+ myReactions: Map<MiNote['id'], string | null>;
}) {
if (_hint_?.myReactions) {
- const reaction = _hint_.myReactions.get(noteId);
+ const reaction = _hint_.myReactions.get(note.id);
if (reaction) {
- return this.reactionService.convertLegacyReaction(reaction.reaction);
+ return this.reactionService.convertLegacyReaction(reaction);
+ } else {
+ return undefined;
+ }
+ }
+
+ const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
+ if (reactionsCount === 0) return undefined;
+ if (note.reactionAndUserPairCache && reactionsCount <= note.reactionAndUserPairCache.length) {
+ const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
+ if (pair) {
+ return this.reactionService.convertLegacyReaction(pair.split(':')[1]);
} else {
return undefined;
}
}
// パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
- if (this.idService.parse(noteId).date.getTime() + 2000 > Date.now()) {
+ if (this.idService.parse(note.id).date.getTime() + 2000 > Date.now()) {
return undefined;
}
const reaction = await this.noteReactionsRepository.findOneBy({
userId: meId,
- noteId: noteId,
+ noteId: note.id,
});
if (reaction) {
@@ -275,8 +286,9 @@ export class NoteEntityService implements OnModuleInit {
options?: {
detail?: boolean;
skipHide?: boolean;
+ withReactionAndUserPairCache?: boolean;
_hint_?: {
- myReactions: Map<MiNote['id'], MiNoteReaction | null>;
+ myReactions: Map<MiNote['id'], string | null>;
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
};
},
@@ -284,6 +296,7 @@ export class NoteEntityService implements OnModuleInit {
const opts = Object.assign({
detail: true,
skipHide: false,
+ withReactionAndUserPairCache: false,
}, options);
const meId = me ? me.id : null;
@@ -324,6 +337,7 @@ export class NoteEntityService implements OnModuleInit {
repliesCount: note.repliesCount,
reactions: this.reactionService.convertLegacyReactions(note.reactions),
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
+ reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,
emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
tags: note.tags.length > 0 ? note.tags : undefined,
fileIds: note.fileIds,
@@ -346,18 +360,20 @@ export class NoteEntityService implements OnModuleInit {
reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, {
detail: false,
+ withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
}) : undefined,
renote: note.renoteId ? this.pack(note.renote ?? note.renoteId, me, {
detail: true,
+ withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
_hint_: options?._hint_,
}) : undefined,
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
...(meId && Object.keys(note.reactions).length > 0 ? {
- myReaction: this.populateMyReaction(note.id, meId, options?._hint_),
+ myReaction: this.populateMyReaction(note, meId, options?._hint_),
} : {}),
} : {}),
});
@@ -381,19 +397,48 @@ export class NoteEntityService implements OnModuleInit {
if (notes.length === 0) return [];
const meId = me ? me.id : null;
- const myReactionsMap = new Map<MiNote['id'], MiNoteReaction | null>();
+ const myReactionsMap = new Map<MiNote['id'], string | null>();
if (meId) {
- const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
+ const idsNeedFetchMyReaction = new Set<MiNote['id']>();
+
// パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
const oldId = this.idService.gen(Date.now() - 2000);
- const targets = [...notes.filter(n => (n.id < oldId) && (Object.keys(n.reactions).length > 0)).map(n => n.id), ...renoteIds];
- const myReactions = targets.length > 0 ? await this.noteReactionsRepository.findBy({
+
+ for (const note of notes) {
+ if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote
+ const reactionsCount = Object.values(note.renote.reactions).reduce((a, b) => a + b, 0);
+ if (reactionsCount === 0) {
+ myReactionsMap.set(note.renote.id, null);
+ } else if (reactionsCount <= note.renote.reactionAndUserPairCache.length) {
+ const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId));
+ myReactionsMap.set(note.renote.id, pair ? pair.split(':')[1] : null);
+ } else {
+ idsNeedFetchMyReaction.add(note.renote.id);
+ }
+ } else {
+ if (note.id < oldId) {
+ const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
+ if (reactionsCount === 0) {
+ myReactionsMap.set(note.id, null);
+ } else if (reactionsCount <= note.reactionAndUserPairCache.length) {
+ const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
+ myReactionsMap.set(note.id, pair ? pair.split(':')[1] : null);
+ } else {
+ idsNeedFetchMyReaction.add(note.id);
+ }
+ } else {
+ myReactionsMap.set(note.id, null);
+ }
+ }
+ }
+
+ const myReactions = idsNeedFetchMyReaction.size > 0 ? await this.noteReactionsRepository.findBy({
userId: meId,
- noteId: In(targets),
+ noteId: In(Array.from(idsNeedFetchMyReaction)),
}) : [];
- for (const target of targets) {
- myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) ?? null);
+ for (const id of idsNeedFetchMyReaction) {
+ myReactionsMap.set(id, myReactions.find(reaction => reaction.noteId === id)?.reaction ?? null);
}
}
diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts
index ac7f57d5d6..a4358b9ba6 100644
--- a/packages/backend/src/models/Note.ts
+++ b/packages/backend/src/models/Note.ts
@@ -165,6 +165,11 @@ export class MiNote {
public mentionedRemoteUsers: string;
@Column('varchar', {
+ length: 1024, array: true, default: '{}',
+ })
+ public reactionAndUserPairCache: string[];
+
+ @Column('varchar', {
length: 128, array: true, default: '{}',
})
public emojis: string[];
diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts
index 2caf0d0c3d..38c0054b55 100644
--- a/packages/backend/src/models/json-schema/note.ts
+++ b/packages/backend/src/models/json-schema/note.ts
@@ -174,6 +174,14 @@ export const packedNoteSchema = {
type: 'string',
optional: true, nullable: false,
},
+ reactionAndUserPairCache: {
+ type: 'array',
+ optional: true, nullable: false,
+ items: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ },
myReaction: {
type: 'object',
diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts
index 4d85e1ceec..57034231a3 100644
--- a/packages/backend/src/server/api/stream/channels/channel.ts
+++ b/packages/backend/src/server/api/stream/channels/channel.ts
@@ -47,7 +47,7 @@ class ChannelChannel extends Channel {
if (this.user && note.renoteId && !note.text) {
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
- const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
+ const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
note.renote.myReaction = myRenoteReaction;
}
}
diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts
index 18b8e8245b..553c44071f 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -73,7 +73,7 @@ class GlobalTimelineChannel extends Channel {
if (this.user && note.renoteId && !note.text) {
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
- const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
+ const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
note.renote.myReaction = myRenoteReaction;
}
}
diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts
index fb9226413b..f30b29cfd6 100644
--- a/packages/backend/src/server/api/stream/channels/hashtag.ts
+++ b/packages/backend/src/server/api/stream/channels/hashtag.ts
@@ -52,7 +52,7 @@ class HashtagChannel extends Channel {
if (this.user && note.renoteId && !note.text) {
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
- const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
+ const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
note.renote.myReaction = myRenoteReaction;
}
}
diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts
index 6914841534..2b235b9822 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -75,7 +75,7 @@ class HomeTimelineChannel extends Channel {
if (this.user && note.renoteId && !note.text) {
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
- const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
+ const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
note.renote.myReaction = myRenoteReaction;
}
}
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index c353aaac0f..4e90912084 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -89,7 +89,8 @@ class HybridTimelineChannel extends Channel {
if (this.user && note.renoteId && !note.text) {
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
- const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
+ console.log(note.renote.reactionAndUserPairCache);
+ const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
note.renote.myReaction = myRenoteReaction;
}
}
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index 0823799d2c..9dd05b9b08 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -72,7 +72,7 @@ class LocalTimelineChannel extends Channel {
if (this.user && note.renoteId && !note.text) {
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
- const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
+ const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
note.renote.myReaction = myRenoteReaction;
}
}
diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts
index a5a9d189b4..6d83d4cb5b 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -104,7 +104,7 @@ class UserListChannel extends Channel {
if (this.user && note.renoteId && !note.text) {
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
- const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
+ const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
note.renote.myReaction = myRenoteReaction;
}
}