Skip to content

Commit

Permalink
perf(backend): improve streaming api performance (#12033)
Browse files Browse the repository at this point in the history
* wip

* Update NoteEntityService.ts

* wip

* wip

* wip

* wip
  • Loading branch information
syuilo authored Oct 15, 2023
1 parent 329830e commit 3f4ee98
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 115 deletions.
2 changes: 1 addition & 1 deletion packages/backend/src/core/NoteCreateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}

// Pack the note
const noteObj = await this.noteEntityService.pack(note);
const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true });

this.globalEventService.publishNotesStream(noteObj);

Expand Down
13 changes: 8 additions & 5 deletions packages/backend/src/core/entities/NoteEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepos
import { bindThis } from '@/decorators.js';
import { isNotNull } from '@/misc/is-not-null.js';
import { DebounceLoader } from '@/misc/loader.js';
import { IdService } from '@/core/IdService.js';
import type { OnModuleInit } from '@nestjs/common';
import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { ReactionService } from '../ReactionService.js';
Expand All @@ -28,6 +29,7 @@ export class NoteEntityService implements OnModuleInit {
private driveFileEntityService: DriveFileEntityService;
private customEmojiService: CustomEmojiService;
private reactionService: ReactionService;
private idService: IdService;
private noteLoader = new DebounceLoader(this.findNoteOrFail);

constructor(
Expand Down Expand Up @@ -66,6 +68,7 @@ export class NoteEntityService implements OnModuleInit {
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
this.reactionService = this.moduleRef.get('ReactionService');
this.idService = this.moduleRef.get('IdService');
}

@bindThis
Expand Down Expand Up @@ -167,11 +170,11 @@ export class NoteEntityService implements OnModuleInit {
}

@bindThis
private async populateMyReaction(note: MiNote, meId: MiUser['id'], _hint_?: {
public async populateMyReaction(noteId: MiNote['id'], meId: MiUser['id'], _hint_?: {
myReactions: Map<MiNote['id'], MiNoteReaction | null>;
}) {
if (_hint_?.myReactions) {
const reaction = _hint_.myReactions.get(note.id);
const reaction = _hint_.myReactions.get(noteId);
if (reaction) {
return this.reactionService.convertLegacyReaction(reaction.reaction);
} else if (reaction === null) {
Expand All @@ -181,13 +184,13 @@ export class NoteEntityService implements OnModuleInit {
}

// パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
if (note.createdAt.getTime() + 2000 > Date.now()) {
if (this.idService.parse(noteId).date.getTime() + 2000 > Date.now()) {
return undefined;
}

const reaction = await this.noteReactionsRepository.findOneBy({
userId: meId,
noteId: note.id,
noteId: noteId,
});

if (reaction) {
Expand Down Expand Up @@ -355,7 +358,7 @@ export class NoteEntityService implements OnModuleInit {
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,

...(meId ? {
myReaction: this.populateMyReaction(note, meId, options?._hint_),
myReaction: this.populateMyReaction(note.id, meId, options?._hint_),
} : {}),
} : {}),
});
Expand Down
18 changes: 5 additions & 13 deletions packages/backend/src/server/api/stream/channels/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,18 @@ class ChannelChannel extends Channel {
private async onNote(note: Packed<'Note'>) {
if (note.channelId !== this.channelId) return;

// リプライなら再pack
if (note.replyId != null) {
note.reply = await this.noteEntityService.pack(note.replyId, this.user, {
detail: true,
});
}
// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await this.noteEntityService.pack(note.renoteId, this.user, {
detail: true,
});
}

// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;

if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;

if (this.user && note.renoteId && !note.text) {
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
note.renote!.myReaction = myRenoteReaction;
}

this.connection.cacheNote(note);

this.send('note', note);
Expand Down
18 changes: 5 additions & 13 deletions packages/backend/src/server/api/stream/channels/global-timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,6 @@ class GlobalTimelineChannel extends Channel {
if (note.visibility !== 'public') return;
if (note.channelId != null) return;

// リプライなら再pack
if (note.replyId != null) {
note.reply = await this.noteEntityService.pack(note.replyId, this.user, {
detail: true,
});
}
// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await this.noteEntityService.pack(note.renoteId, this.user, {
detail: true,
});
}

// 関係ない返信は除外
if (note.reply && !this.following[note.userId]?.withReplies) {
const reply = note.reply;
Expand All @@ -84,6 +71,11 @@ class GlobalTimelineChannel extends Channel {

if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;

if (this.user && note.renoteId && !note.text) {
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
note.renote!.myReaction = myRenoteReaction;
}

this.connection.cacheNote(note);

this.send('note', note);
Expand Down
12 changes: 5 additions & 7 deletions packages/backend/src/server/api/stream/channels/hashtag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,18 @@ class HashtagChannel extends Channel {
const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag))));
if (!matched) return;

// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await this.noteEntityService.pack(note.renoteId, this.user, {
detail: true,
});
}

// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;

if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;

if (this.user && note.renoteId && !note.text) {
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
note.renote!.myReaction = myRenoteReaction;
}

this.connection.cacheNote(note);

this.send('note', note);
Expand Down
30 changes: 9 additions & 21 deletions packages/backend/src/server/api/stream/channels/home-timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,10 @@ class HomeTimelineChannel extends Channel {
// Ignore notes from instances the user has muted
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;

if (['followers', 'specified'].includes(note.visibility)) {
note = await this.noteEntityService.pack(note.id, this.user!, {
detail: true,
});

if (note.isHidden) {
return;
}
} else {
// リプライなら再pack
if (note.replyId != null) {
note.reply = await this.noteEntityService.pack(note.replyId, this.user!, {
detail: true,
});
}
// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await this.noteEntityService.pack(note.renoteId, this.user!, {
detail: true,
});
}
if (note.visibility === 'followers') {
if (!Object.hasOwn(this.following, note.userId)) return;
} else if (note.visibility === 'specified') {
if (!note.visibleUserIds!.includes(this.user!.id)) return;
}

// 関係ない返信は除外
Expand All @@ -90,6 +73,11 @@ class HomeTimelineChannel extends Channel {

if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;

if (this.user && note.renoteId && !note.text) {
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
note.renote!.myReaction = myRenoteReaction;
}

this.connection.cacheNote(note);

this.send('note', note);
Expand Down
30 changes: 9 additions & 21 deletions packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,27 +62,10 @@ class HybridTimelineChannel extends Channel {
(note.channelId != null && this.followingChannels.has(note.channelId))
)) return;

if (['followers', 'specified'].includes(note.visibility)) {
note = await this.noteEntityService.pack(note.id, this.user!, {
detail: true,
});

if (note.isHidden) {
return;
}
} else {
// リプライなら再pack
if (note.replyId != null) {
note.reply = await this.noteEntityService.pack(note.replyId, this.user!, {
detail: true,
});
}
// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await this.noteEntityService.pack(note.renoteId, this.user!, {
detail: true,
});
}
if (note.visibility === 'followers') {
if (!Object.hasOwn(this.following, note.userId)) return;
} else if (note.visibility === 'specified') {
if (!note.visibleUserIds!.includes(this.user!.id)) return;
}

// Ignore notes from instances the user has muted
Expand All @@ -104,6 +87,11 @@ class HybridTimelineChannel extends Channel {

if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;

if (this.user && note.renoteId && !note.text) {
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
note.renote!.myReaction = myRenoteReaction;
}

this.connection.cacheNote(note);

this.send('note', note);
Expand Down
18 changes: 5 additions & 13 deletions packages/backend/src/server/api/stream/channels/local-timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,6 @@ class LocalTimelineChannel extends Channel {
if (note.visibility !== 'public') return;
if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;

// リプライなら再pack
if (note.replyId != null) {
note.reply = await this.noteEntityService.pack(note.replyId, this.user, {
detail: true,
});
}
// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await this.noteEntityService.pack(note.renoteId, this.user, {
detail: true,
});
}

// 関係ない返信は除外
if (note.reply && this.user && !this.following[note.userId]?.withReplies && !this.withReplies) {
const reply = note.reply;
Expand All @@ -83,6 +70,11 @@ class LocalTimelineChannel extends Channel {

if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;

if (this.user && note.renoteId && !note.text) {
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
note.renote!.myReaction = myRenoteReaction;
}

this.connection.cacheNote(note);

this.send('note', note);
Expand Down
32 changes: 11 additions & 21 deletions packages/backend/src/server/api/stream/channels/user-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,27 +82,10 @@ class UserListChannel extends Channel {

if (!Object.hasOwn(this.membershipsMap, note.userId)) return;

if (['followers', 'specified'].includes(note.visibility)) {
note = await this.noteEntityService.pack(note.id, this.user, {
detail: true,
});

if (note.isHidden) {
return;
}
} else {
// リプライなら再pack
if (note.replyId != null) {
note.reply = await this.noteEntityService.pack(note.replyId, this.user, {
detail: true,
});
}
// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await this.noteEntityService.pack(note.renoteId, this.user, {
detail: true,
});
}
if (note.visibility === 'followers') {
if (!Object.hasOwn(this.following, note.userId)) return;
} else if (note.visibility === 'specified') {
if (!note.visibleUserIds!.includes(this.user!.id)) return;
}

// 関係ない返信は除外
Expand All @@ -119,6 +102,13 @@ class UserListChannel extends Channel {

if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;

if (this.user && note.renoteId && !note.text) {
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
note.renote!.myReaction = myRenoteReaction;
}

this.connection.cacheNote(note);

this.send('note', note);
}

Expand Down

0 comments on commit 3f4ee98

Please sign in to comment.