From 0c0c8f51445bc9327a0607f58e0afdf8c4c17cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Wed, 22 Nov 2023 01:10:48 +0900 Subject: [PATCH] =?UTF-8?q?fix(backend/channel):=20=E3=83=81=E3=83=A3?= =?UTF-8?q?=E3=83=B3=E3=83=8D=E3=83=AB=E3=81=B8=E3=81=AE=E3=83=8E=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=8C=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AEFTT?= =?UTF-8?q?=E3=81=AB=E4=BF=9D=E5=AD=98=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=81=E5=80=8B?= =?UTF-8?q?=E4=BA=BA=E3=83=8F=E3=82=A4=E3=83=A9=E3=82=A4=E3=83=88=E3=81=AB?= =?UTF-8?q?=E5=8F=8D=E6=98=A0=E3=81=95=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20(MisskeyIO#245)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/src/core/NoteCreateService.ts | 95 ++++++++++--------- packages/backend/src/core/ReactionService.ts | 1 + .../api/endpoints/notes/user-list-timeline.ts | 1 - packages/backend/test/e2e/timelines.ts | 5 +- 4 files changed, 52 insertions(+), 50 deletions(-) diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index b6f7ee745b..03e69fa8ba 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -743,6 +743,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (renote.channelId != null) { if (renote.replyId == null) { this.featuredService.updateInChannelNotesRanking(renote.channelId, renote.id, 5); + this.featuredService.updatePerUserNotesRanking(renote.userId, renote.id, 5); } } else { if (renote.visibility === 'public' && renote.userHost == null && renote.replyId == null) { @@ -843,6 +844,48 @@ export class NoteCreateService implements OnApplicationShutdown { const r = this.redisForTimelines.pipeline(); + // TODO: キャッシュ? + // eslint-disable-next-line prefer-const + let [followings, userListMemberships] = await Promise.all([ + this.followingsRepository.find({ + where: { + followeeId: user.id, + followerHost: IsNull(), + isFollowerHibernated: false, + }, + select: ['followerId', 'withReplies'], + }), + this.userListMembershipsRepository.find({ + where: { + userId: user.id, + }, + select: ['userListId', 'userListUserId', 'withReplies'], + }), + ]); + + if (note.visibility === 'followers') { + // TODO: 重そうだから何とかしたい Set 使う? + userListMemberships = userListMemberships.filter(x => x.userListUserId === user.id || followings.some(f => f.followerId === x.userListUserId)); + } + + for (const userListMembership of userListMemberships) { + // ダイレクトのとき、そのリストが対象外のユーザーの場合 + if ( + note.visibility === 'specified' && + !note.visibleUserIds.some(v => v === userListMembership.userListUserId) + ) continue; + + // 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合 + if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) { + if (!userListMembership.withReplies) continue; + } + + this.funoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); + if (note.fileIds.length > 0) { + this.funoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); + } + } + if (note.channelId) { this.funoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); @@ -862,30 +905,6 @@ export class NoteCreateService implements OnApplicationShutdown { } } } else { - // TODO: キャッシュ? - // eslint-disable-next-line prefer-const - let [followings, userListMemberships] = await Promise.all([ - this.followingsRepository.find({ - where: { - followeeId: user.id, - followerHost: IsNull(), - isFollowerHibernated: false, - }, - select: ['followerId', 'withReplies'], - }), - this.userListMembershipsRepository.find({ - where: { - userId: user.id, - }, - select: ['userListId', 'userListUserId', 'withReplies'], - }), - ]); - - if (note.visibility === 'followers') { - // TODO: 重そうだから何とかしたい Set 使う? - userListMemberships = userListMemberships.filter(x => x.userListUserId === user.id || followings.some(f => f.followerId === x.userListUserId)); - } - // TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする for (const following of followings) { // 基本的にvisibleUserIdsには自身のidが含まれている前提であること @@ -902,24 +921,6 @@ export class NoteCreateService implements OnApplicationShutdown { } } - for (const userListMembership of userListMemberships) { - // ダイレクトのとき、そのリストが対象外のユーザーの場合 - if ( - note.visibility === 'specified' && - !note.visibleUserIds.some(v => v === userListMembership.userListUserId) - ) continue; - - // 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合 - if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) { - if (!userListMembership.withReplies) continue; - } - - this.funoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); - if (note.fileIds.length > 0) { - this.funoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); - } - } - if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL this.funoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); if (note.fileIds.length > 0) { @@ -947,12 +948,12 @@ export class NoteCreateService implements OnApplicationShutdown { } } } + } - if (Math.random() < 0.1) { - process.nextTick(() => { - this.checkHibernation(followings); - }); - } + if (Math.random() < 0.1) { + process.nextTick(() => { + this.checkHibernation(followings); + }); } r.exec(); diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index d1560d2fb0..6df8a88601 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -208,6 +208,7 @@ export class ReactionService { if (note.channelId != null) { if (note.replyId == null) { this.featuredService.updateInChannelNotesRanking(note.channelId, note.id, 1); + this.featuredService.updatePerUserNotesRanking(note.userId, note.id, 1); } } else { if (note.visibility === 'public' && note.userHost == null && note.replyId == null) { diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index dbc3875597..562a156514 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -200,7 +200,6 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser') .andWhere('userListMemberships.userListId = :userListId', { userListId: list.id }) - .andWhere('note.channelId IS NULL') // チャンネルノートではない .andWhere(new Brackets(qb => { qb .where('note.replyId IS NULL') // 返信ではない diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index 9287f17e3e..8b1438f516 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -978,7 +978,7 @@ describe('Timelines', () => { assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi'); }); - test.concurrent('リスインしているユーザーのチャンネルノートが含まれない', async () => { + test.concurrent('リスインしているユーザーのチャンネルノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body); @@ -991,7 +991,8 @@ describe('Timelines', () => { const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); - assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); + assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); }); test.concurrent('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => {