feat: improve tl performance (#11946)
* wip * wip * wip * wip * wip * wip * Update NoteCreateService.ts * wip * wip * wip * wip * Update NoteCreateService.ts * wip * Update NoteCreateService.ts * wip * Update user-notes.ts * wip * wip * wip * Update NoteCreateService.ts * wip * Update timeline.ts * Update timeline.ts * Update timeline.ts * Update timeline.ts * Update timeline.ts * wip * Update timelines.ts * Update timelines.ts * Update timelines.ts * wip * wip * wip * Update timelines.ts * Update misskey-js.api.md * Update timelines.ts * Update timelines.ts * wip * wip * wip * Update timelines.ts * wip * Update timelines.ts * wip * test * Update activitypub.ts * refactor: UserListJoining -> UserListMembership * Update NoteCreateService.ts * wip
This commit is contained in:
		| @@ -18,7 +18,6 @@ class GlobalTimelineChannel extends Channel { | ||||
| 	public readonly chName = 'globalTimeline'; | ||||
| 	public static shouldShare = true; | ||||
| 	public static requireCredential = false; | ||||
| 	private withReplies: boolean; | ||||
| 	private withRenotes: boolean; | ||||
|  | ||||
| 	constructor( | ||||
| @@ -38,7 +37,6 @@ class GlobalTimelineChannel extends Channel { | ||||
| 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); | ||||
| 		if (!policies.gtlAvailable) return; | ||||
|  | ||||
| 		this.withReplies = params.withReplies ?? false; | ||||
| 		this.withRenotes = params.withRenotes ?? true; | ||||
|  | ||||
| 		// Subscribe events | ||||
| @@ -64,7 +62,7 @@ class GlobalTimelineChannel extends Channel { | ||||
| 		} | ||||
|  | ||||
| 		// 関係ない返信は除外 | ||||
| 		if (note.reply && !this.withReplies) { | ||||
| 		if (note.reply && !this.following[note.userId]?.withReplies) { | ||||
| 			const reply = note.reply; | ||||
| 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 | ||||
| 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; | ||||
| @@ -82,13 +80,6 @@ class GlobalTimelineChannel extends Channel { | ||||
|  | ||||
| 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; | ||||
|  | ||||
| 		// 流れてきたNoteがミュートすべきNoteだったら無視する | ||||
| 		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) | ||||
| 		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 | ||||
| 		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 | ||||
| 		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる | ||||
| 		if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return; | ||||
|  | ||||
| 		this.connection.cacheNote(note); | ||||
|  | ||||
| 		this.send('note', note); | ||||
|   | ||||
| @@ -16,7 +16,6 @@ class HomeTimelineChannel extends Channel { | ||||
| 	public readonly chName = 'homeTimeline'; | ||||
| 	public static shouldShare = true; | ||||
| 	public static requireCredential = true; | ||||
| 	private withReplies: boolean; | ||||
| 	private withRenotes: boolean; | ||||
|  | ||||
| 	constructor( | ||||
| @@ -31,7 +30,6 @@ class HomeTimelineChannel extends Channel { | ||||
|  | ||||
| 	@bindThis | ||||
| 	public async init(params: any) { | ||||
| 		this.withReplies = params.withReplies ?? false; | ||||
| 		this.withRenotes = params.withRenotes ?? true; | ||||
|  | ||||
| 		this.subscriber.on('notesStream', this.onNote); | ||||
| @@ -43,7 +41,7 @@ class HomeTimelineChannel extends Channel { | ||||
| 			if (!this.followingChannels.has(note.channelId)) return; | ||||
| 		} else { | ||||
| 			// その投稿のユーザーをフォローしていなかったら弾く | ||||
| 			if ((this.user!.id !== note.userId) && !this.following.has(note.userId)) return; | ||||
| 			if ((this.user!.id !== note.userId) && !Object.hasOwn(this.following, note.userId)) return; | ||||
| 		} | ||||
|  | ||||
| 		// Ignore notes from instances the user has muted | ||||
| @@ -73,7 +71,7 @@ class HomeTimelineChannel extends Channel { | ||||
| 		} | ||||
|  | ||||
| 		// 関係ない返信は除外 | ||||
| 		if (note.reply && !this.withReplies) { | ||||
| 		if (note.reply && !this.following[note.userId]?.withReplies) { | ||||
| 			const reply = note.reply; | ||||
| 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 | ||||
| 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; | ||||
| @@ -88,13 +86,6 @@ class HomeTimelineChannel extends Channel { | ||||
|  | ||||
| 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; | ||||
|  | ||||
| 		// 流れてきたNoteがミュートすべきNoteだったら無視する | ||||
| 		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) | ||||
| 		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 | ||||
| 		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 | ||||
| 		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる | ||||
| 		if (await checkWordMute(note, this.user, this.userProfile!.mutedWords)) return; | ||||
|  | ||||
| 		this.connection.cacheNote(note); | ||||
|  | ||||
| 		this.send('note', note); | ||||
|   | ||||
| @@ -18,7 +18,6 @@ class HybridTimelineChannel extends Channel { | ||||
| 	public readonly chName = 'hybridTimeline'; | ||||
| 	public static shouldShare = true; | ||||
| 	public static requireCredential = true; | ||||
| 	private withReplies: boolean; | ||||
| 	private withRenotes: boolean; | ||||
|  | ||||
| 	constructor( | ||||
| @@ -38,7 +37,6 @@ class HybridTimelineChannel extends Channel { | ||||
| 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); | ||||
| 		if (!policies.ltlAvailable) return; | ||||
|  | ||||
| 		this.withReplies = params.withReplies ?? false; | ||||
| 		this.withRenotes = params.withRenotes ?? true; | ||||
|  | ||||
| 		// Subscribe events | ||||
| @@ -53,7 +51,7 @@ class HybridTimelineChannel extends Channel { | ||||
| 		// フォローしているチャンネルの投稿 の場合だけ | ||||
| 		if (!( | ||||
| 			(note.channelId == null && this.user!.id === note.userId) || | ||||
| 			(note.channelId == null && this.following.has(note.userId)) || | ||||
| 			(note.channelId == null && Object.hasOwn(this.following, note.userId)) || | ||||
| 			(note.channelId == null && (note.user.host == null && note.visibility === 'public')) || | ||||
| 			(note.channelId != null && this.followingChannels.has(note.channelId)) | ||||
| 		)) return; | ||||
| @@ -85,7 +83,7 @@ class HybridTimelineChannel extends Channel { | ||||
| 		if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return; | ||||
|  | ||||
| 		// 関係ない返信は除外 | ||||
| 		if (note.reply && !this.withReplies) { | ||||
| 		if (note.reply && !this.following[note.userId]?.withReplies) { | ||||
| 			const reply = note.reply; | ||||
| 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 | ||||
| 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; | ||||
| @@ -100,13 +98,6 @@ class HybridTimelineChannel extends Channel { | ||||
|  | ||||
| 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; | ||||
|  | ||||
| 		// 流れてきたNoteがミュートすべきNoteだったら無視する | ||||
| 		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) | ||||
| 		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 | ||||
| 		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 | ||||
| 		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる | ||||
| 		if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return; | ||||
|  | ||||
| 		this.connection.cacheNote(note); | ||||
|  | ||||
| 		this.send('note', note); | ||||
|   | ||||
| @@ -17,7 +17,6 @@ class LocalTimelineChannel extends Channel { | ||||
| 	public readonly chName = 'localTimeline'; | ||||
| 	public static shouldShare = true; | ||||
| 	public static requireCredential = false; | ||||
| 	private withReplies: boolean; | ||||
| 	private withRenotes: boolean; | ||||
|  | ||||
| 	constructor( | ||||
| @@ -37,7 +36,6 @@ class LocalTimelineChannel extends Channel { | ||||
| 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null); | ||||
| 		if (!policies.ltlAvailable) return; | ||||
|  | ||||
| 		this.withReplies = params.withReplies ?? false; | ||||
| 		this.withRenotes = params.withRenotes ?? true; | ||||
|  | ||||
| 		// Subscribe events | ||||
| @@ -64,7 +62,7 @@ class LocalTimelineChannel extends Channel { | ||||
| 		} | ||||
|  | ||||
| 		// 関係ない返信は除外 | ||||
| 		if (note.reply && this.user && !this.withReplies) { | ||||
| 		if (note.reply && this.user && !this.following[note.userId]?.withReplies) { | ||||
| 			const reply = note.reply; | ||||
| 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 | ||||
| 			if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return; | ||||
| @@ -79,13 +77,6 @@ class LocalTimelineChannel extends Channel { | ||||
|  | ||||
| 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; | ||||
|  | ||||
| 		// 流れてきたNoteがミュートすべきNoteだったら無視する | ||||
| 		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある) | ||||
| 		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、 | ||||
| 		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 | ||||
| 		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる | ||||
| 		if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return; | ||||
|  | ||||
| 		this.connection.cacheNote(note); | ||||
|  | ||||
| 		this.send('note', note); | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|  */ | ||||
|  | ||||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; | ||||
| import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; | ||||
| import type { MiUser } from '@/models/User.js'; | ||||
| import { isUserRelated } from '@/misc/is-user-related.js'; | ||||
| import type { Packed } from '@/misc/json-schema.js'; | ||||
| @@ -18,12 +18,12 @@ class UserListChannel extends Channel { | ||||
| 	public static shouldShare = false; | ||||
| 	public static requireCredential = false; | ||||
| 	private listId: string; | ||||
| 	public listUsers: MiUser['id'][] = []; | ||||
| 	public membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {}; | ||||
| 	private listUsersClock: NodeJS.Timeout; | ||||
|  | ||||
| 	constructor( | ||||
| 		private userListsRepository: UserListsRepository, | ||||
| 		private userListJoiningsRepository: UserListJoiningsRepository, | ||||
| 		private userListMembershipsRepository: UserListMembershipsRepository, | ||||
| 		private noteEntityService: NoteEntityService, | ||||
|  | ||||
| 		id: string, | ||||
| @@ -58,19 +58,25 @@ class UserListChannel extends Channel { | ||||
|  | ||||
| 	@bindThis | ||||
| 	private async updateListUsers() { | ||||
| 		const users = await this.userListJoiningsRepository.find({ | ||||
| 		const memberships = await this.userListMembershipsRepository.find({ | ||||
| 			where: { | ||||
| 				userListId: this.listId, | ||||
| 			}, | ||||
| 			select: ['userId'], | ||||
| 		}); | ||||
|  | ||||
| 		this.listUsers = users.map(x => x.userId); | ||||
| 		const membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {}; | ||||
| 		for (const membership of memberships) { | ||||
| 			membershipsMap[membership.userId] = { | ||||
| 				withReplies: membership.withReplies, | ||||
| 			}; | ||||
| 		} | ||||
| 		this.membershipsMap = membershipsMap; | ||||
| 	} | ||||
|  | ||||
| 	@bindThis | ||||
| 	private async onNote(note: Packed<'Note'>) { | ||||
| 		if (!this.listUsers.includes(note.userId)) return; | ||||
| 		if (!Object.hasOwn(this.membershipsMap, note.userId)) return; | ||||
|  | ||||
| 		if (['followers', 'specified'].includes(note.visibility)) { | ||||
| 			note = await this.noteEntityService.pack(note.id, this.user, { | ||||
| @@ -95,6 +101,13 @@ class UserListChannel extends Channel { | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// 関係ない返信は除外 | ||||
| 		if (note.reply && !this.membershipsMap[note.userId]?.withReplies) { | ||||
| 			const reply = note.reply; | ||||
| 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 | ||||
| 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; | ||||
| 		} | ||||
|  | ||||
| 		// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する | ||||
| 		if (isUserRelated(note, this.userIdsWhoMeMuting)) return; | ||||
| 		// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する | ||||
| @@ -124,8 +137,8 @@ export class UserListChannelService { | ||||
| 		@Inject(DI.userListsRepository) | ||||
| 		private userListsRepository: UserListsRepository, | ||||
|  | ||||
| 		@Inject(DI.userListJoiningsRepository) | ||||
| 		private userListJoiningsRepository: UserListJoiningsRepository, | ||||
| 		@Inject(DI.userListMembershipsRepository) | ||||
| 		private userListMembershipsRepository: UserListMembershipsRepository, | ||||
|  | ||||
| 		private noteEntityService: NoteEntityService, | ||||
| 	) { | ||||
| @@ -135,7 +148,7 @@ export class UserListChannelService { | ||||
| 	public create(id: string, connection: Channel['connection']): UserListChannel { | ||||
| 		return new UserListChannel( | ||||
| 			this.userListsRepository, | ||||
| 			this.userListJoiningsRepository, | ||||
| 			this.userListMembershipsRepository, | ||||
| 			this.noteEntityService, | ||||
| 			id, | ||||
| 			connection, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo