perf: Reduce database query
This commit is contained in:
		
							parent
							
								
									5e61c60f85
								
							
						
					
					
						commit
						87c8f9ff95
					
				
					 8 changed files with 114 additions and 123 deletions
				
			
		|  | @ -350,7 +350,15 @@ export default defineComponent({ | ||||||
| 
 | 
 | ||||||
| 		capture(withHandler = false) { | 		capture(withHandler = false) { | ||||||
| 			if (this.$i) { | 			if (this.$i) { | ||||||
| 				this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id }); | 				this.connection.send('sn', { id: this.appearNote.id }); | ||||||
|  | 				if (this.appearNote.userId !== this.$i.id) { | ||||||
|  | 					if (this.appearNote.mentions && this.appearNote.mentions.includes(this.$i.id)) { | ||||||
|  | 						this.connection.send('readMention', { id: this.appearNote.id }); | ||||||
|  | 					} | ||||||
|  | 					if (this.appearNote.visibleUserIds && this.appearNote.visibleUserIds.includes(this.$i.id)) { | ||||||
|  | 						this.connection.send('readSpecifiedNote', { id: this.appearNote.id }); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 				if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); | 				if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -325,7 +325,15 @@ export default defineComponent({ | ||||||
| 
 | 
 | ||||||
| 		capture(withHandler = false) { | 		capture(withHandler = false) { | ||||||
| 			if (this.$i) { | 			if (this.$i) { | ||||||
| 				this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id }); | 				this.connection.send('sn', { id: this.appearNote.id }); | ||||||
|  | 				if (this.appearNote.userId !== this.$i.id) { | ||||||
|  | 					if (this.appearNote.mentions && this.appearNote.mentions.includes(this.$i.id)) { | ||||||
|  | 						this.connection.send('readMention', { id: this.appearNote.id }); | ||||||
|  | 					} | ||||||
|  | 					if (this.appearNote.visibleUserIds && this.appearNote.visibleUserIds.includes(this.$i.id)) { | ||||||
|  | 						this.connection.send('readSpecifiedNote', { id: this.appearNote.id }); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 				if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); | 				if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -325,7 +325,15 @@ export default defineComponent({ | ||||||
| 
 | 
 | ||||||
| 		capture(withHandler = false) { | 		capture(withHandler = false) { | ||||||
| 			if (this.$i) { | 			if (this.$i) { | ||||||
| 				this.connection.send(document.body.contains(this.$el) ? 'sn' : 's', { id: this.appearNote.id }); | 				this.connection.send('sn', { id: this.appearNote.id }); | ||||||
|  | 				if (this.appearNote.userId !== this.$i.id) { | ||||||
|  | 					if (this.appearNote.mentions && this.appearNote.mentions.includes(this.$i.id)) { | ||||||
|  | 						this.connection.send('readMention', { id: this.appearNote.id }); | ||||||
|  | 					} | ||||||
|  | 					if (this.appearNote.visibleUserIds && this.appearNote.visibleUserIds.includes(this.$i.id)) { | ||||||
|  | 						this.connection.send('readSpecifiedNote', { id: this.appearNote.id }); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 				if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); | 				if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import $ from 'cafy'; | import $ from 'cafy'; | ||||||
| import { ID } from '../../../../misc/cafy-id'; | import { ID } from '../../../../misc/cafy-id'; | ||||||
| import define from '../../define'; | import define from '../../define'; | ||||||
| import read from '../../../../services/note/read'; |  | ||||||
| import { Notes, Followings } from '../../../../models'; | import { Notes, Followings } from '../../../../models'; | ||||||
| import { generateVisibilityQuery } from '../../common/generate-visibility-query'; | import { generateVisibilityQuery } from '../../common/generate-visibility-query'; | ||||||
| import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; | import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; | ||||||
| import { makePaginationQuery } from '../../common/make-pagination-query'; | import { makePaginationQuery } from '../../common/make-pagination-query'; | ||||||
| import { Brackets } from 'typeorm'; | import { Brackets } from 'typeorm'; | ||||||
|  | import { readMention } from '../../../../services/note/read-mention'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
|  | @ -79,9 +79,7 @@ export default define(meta, async (ps, user) => { | ||||||
| 
 | 
 | ||||||
| 	const mentions = await query.take(ps.limit!).getMany(); | 	const mentions = await query.take(ps.limit!).getMany(); | ||||||
| 
 | 
 | ||||||
| 	for (const note of mentions) { | 	readMention(user.id, mentions.map(n => n.id)); | ||||||
| 		read(user.id, note.id); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return await Notes.packMany(mentions, user); | 	return await Notes.packMany(mentions, user); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ import autobind from 'autobind-decorator'; | ||||||
| import * as websocket from 'websocket'; | import * as websocket from 'websocket'; | ||||||
| import { readNotification } from '../common/read-notification'; | import { readNotification } from '../common/read-notification'; | ||||||
| import call from '../call'; | import call from '../call'; | ||||||
| import readNote from '../../../services/note/read'; |  | ||||||
| import Channel from './channel'; | import Channel from './channel'; | ||||||
| import channels from './channels'; | import channels from './channels'; | ||||||
| import { EventEmitter } from 'events'; | import { EventEmitter } from 'events'; | ||||||
|  | @ -14,6 +13,8 @@ import { AccessToken } from '../../../models/entities/access-token'; | ||||||
| import { UserProfile } from '../../../models/entities/user-profile'; | import { UserProfile } from '../../../models/entities/user-profile'; | ||||||
| import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '../../../services/stream'; | import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '../../../services/stream'; | ||||||
| import { UserGroup } from '../../../models/entities/user-group'; | import { UserGroup } from '../../../models/entities/user-group'; | ||||||
|  | import { readMention } from '../../../services/note/read-mention'; | ||||||
|  | import { readSpecifiedNote } from '../../../services/note/read-specified-note'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Main stream connection |  * Main stream connection | ||||||
|  | @ -86,9 +87,10 @@ export default class Connection { | ||||||
| 		switch (type) { | 		switch (type) { | ||||||
| 			case 'api': this.onApiRequest(body); break; | 			case 'api': this.onApiRequest(body); break; | ||||||
| 			case 'readNotification': this.onReadNotification(body); break; | 			case 'readNotification': this.onReadNotification(body); break; | ||||||
| 			case 'subNote': this.onSubscribeNote(body, true); break; | 			case 'readMention': this.onReadMention(body); break; | ||||||
| 			case 'sn': this.onSubscribeNote(body, true); break; // alias
 | 			case 'readSpecifiedNote': this.onReadSpecifiedNote(body); break; | ||||||
| 			case 's': this.onSubscribeNote(body, false); break; | 			case 'subNote': this.onSubscribeNote(body); break; | ||||||
|  | 			case 'sn': this.onSubscribeNote(body); break; // alias
 | ||||||
| 			case 'unsubNote': this.onUnsubscribeNote(body); break; | 			case 'unsubNote': this.onUnsubscribeNote(body); break; | ||||||
| 			case 'un': this.onUnsubscribeNote(body); break; // alias
 | 			case 'un': this.onUnsubscribeNote(body); break; // alias
 | ||||||
| 			case 'connect': this.onChannelConnectRequested(body); break; | 			case 'connect': this.onChannelConnectRequested(body); break; | ||||||
|  | @ -141,11 +143,31 @@ export default class Connection { | ||||||
| 		readNotification(this.user!.id, [payload.id]); | 		readNotification(this.user!.id, [payload.id]); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@autobind | ||||||
|  | 	private onReadMention(payload: any) { | ||||||
|  | 		if (!payload.id) return; | ||||||
|  | 		if (this.user) { | ||||||
|  | 			// TODO: ある程度まとめてreadMentionするようにする
 | ||||||
|  | 			// 具体的には、この箇所ではキュー的な配列にread予定ノートを溜めておくに留めて、別の箇所で定期的にキューにあるノートを配列でreadMentionに渡すような実装にする
 | ||||||
|  | 			readMention(this.user.id, [payload.id]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@autobind | ||||||
|  | 	private onReadSpecifiedNote(payload: any) { | ||||||
|  | 		if (!payload.id) return; | ||||||
|  | 		if (this.user) { | ||||||
|  | 			// TODO: ある程度まとめてreadSpecifiedNoteするようにする
 | ||||||
|  | 			// 具体的には、この箇所ではキュー的な配列にread予定ノートを溜めておくに留めて、別の箇所で定期的にキューにあるノートを配列でreadSpecifiedNoteに渡すような実装にする
 | ||||||
|  | 			readSpecifiedNote(this.user.id, [payload.id]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * 投稿購読要求時 | 	 * 投稿購読要求時 | ||||||
| 	 */ | 	 */ | ||||||
| 	@autobind | 	@autobind | ||||||
| 	private onSubscribeNote(payload: any, read: boolean) { | 	private onSubscribeNote(payload: any) { | ||||||
| 		if (!payload.id) return; | 		if (!payload.id) return; | ||||||
| 
 | 
 | ||||||
| 		if (this.subscribingNotes[payload.id] == null) { | 		if (this.subscribingNotes[payload.id] == null) { | ||||||
|  | @ -157,12 +179,6 @@ export default class Connection { | ||||||
| 		if (this.subscribingNotes[payload.id] === 1) { | 		if (this.subscribingNotes[payload.id] === 1) { | ||||||
| 			this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage); | 			this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage); | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		if (this.user && read) { |  | ||||||
| 			// TODO: クライアントでタイムライン読み込みなどすると、一度に大量のreadNoteが発生しクエリ数がすごいことになるので、ある程度まとめてreadNoteするようにする
 |  | ||||||
| 			// 具体的には、この箇所ではキュー的な配列にread予定ノートを溜めておくに留めて、別の箇所で定期的にキューにあるノートを配列でreadNoteに渡すような実装にする
 |  | ||||||
| 			readNote(this.user.id, payload.id); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
|  |  | ||||||
							
								
								
									
										29
									
								
								src/services/note/read-mention.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/services/note/read-mention.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | import { publishMainStream } from '../stream'; | ||||||
|  | import { Note } from '../../models/entities/note'; | ||||||
|  | import { User } from '../../models/entities/user'; | ||||||
|  | import { NoteUnreads } from '../../models'; | ||||||
|  | import { In } from 'typeorm'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Mark a mention note as read | ||||||
|  |  */ | ||||||
|  | export async function readMention( | ||||||
|  | 	userId: User['id'], | ||||||
|  | 	noteIds: Note['id'][] | ||||||
|  | ) { | ||||||
|  | 	// Remove the records
 | ||||||
|  | 	await NoteUnreads.delete({ | ||||||
|  | 		userId: userId, | ||||||
|  | 		noteId: In(noteIds), | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	const mentionsCount = await NoteUnreads.count({ | ||||||
|  | 		userId: userId, | ||||||
|  | 		isMentioned: true | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	if (mentionsCount === 0) { | ||||||
|  | 		// 全て既読になったイベントを発行
 | ||||||
|  | 		publishMainStream(userId, 'readAllUnreadMentions'); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								src/services/note/read-specified-note.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/services/note/read-specified-note.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | import { publishMainStream } from '../stream'; | ||||||
|  | import { Note } from '../../models/entities/note'; | ||||||
|  | import { User } from '../../models/entities/user'; | ||||||
|  | import { NoteUnreads } from '../../models'; | ||||||
|  | import { In } from 'typeorm'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Mark a specified note as read | ||||||
|  |  */ | ||||||
|  | export async function readSpecifiedNote( | ||||||
|  | 	userId: User['id'], | ||||||
|  | 	noteIds: Note['id'][] | ||||||
|  | ) { | ||||||
|  | 	// Remove the records
 | ||||||
|  | 	await NoteUnreads.delete({ | ||||||
|  | 		userId: userId, | ||||||
|  | 		noteId: In(noteIds), | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	const specifiedCount = await NoteUnreads.count({ | ||||||
|  | 		userId: userId, | ||||||
|  | 		isSpecified: true | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	if (specifiedCount === 0) { | ||||||
|  | 		// 全て既読になったイベントを発行
 | ||||||
|  | 		publishMainStream(userId, 'readAllUnreadSpecifiedNotes'); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,105 +0,0 @@ | ||||||
| import { publishMainStream } from '../stream'; |  | ||||||
| import { Note } from '../../models/entities/note'; |  | ||||||
| import { User } from '../../models/entities/user'; |  | ||||||
| import { NoteUnreads, Antennas, AntennaNotes, Users } from '../../models'; |  | ||||||
| import { Not, IsNull } from 'typeorm'; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Mark a note as read |  | ||||||
|  */ |  | ||||||
| export default async function( |  | ||||||
| 	userId: User['id'], |  | ||||||
| 	noteId: Note['id'] |  | ||||||
| ) { |  | ||||||
| 	async function careNoteUnreads() { |  | ||||||
| 		const exist = await NoteUnreads.findOne({ |  | ||||||
| 			userId: userId, |  | ||||||
| 			noteId: noteId, |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		if (!exist) return; |  | ||||||
| 
 |  | ||||||
| 		// Remove the record
 |  | ||||||
| 		await NoteUnreads.delete({ |  | ||||||
| 			userId: userId, |  | ||||||
| 			noteId: noteId, |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		if (exist.isMentioned) { |  | ||||||
| 			NoteUnreads.count({ |  | ||||||
| 				userId: userId, |  | ||||||
| 				isMentioned: true |  | ||||||
| 			}).then(mentionsCount => { |  | ||||||
| 				if (mentionsCount === 0) { |  | ||||||
| 					// 全て既読になったイベントを発行
 |  | ||||||
| 					publishMainStream(userId, 'readAllUnreadMentions'); |  | ||||||
| 				} |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (exist.isSpecified) { |  | ||||||
| 			NoteUnreads.count({ |  | ||||||
| 				userId: userId, |  | ||||||
| 				isSpecified: true |  | ||||||
| 			}).then(specifiedCount => { |  | ||||||
| 				if (specifiedCount === 0) { |  | ||||||
| 					// 全て既読になったイベントを発行
 |  | ||||||
| 					publishMainStream(userId, 'readAllUnreadSpecifiedNotes'); |  | ||||||
| 				} |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (exist.noteChannelId) { |  | ||||||
| 			NoteUnreads.count({ |  | ||||||
| 				userId: userId, |  | ||||||
| 				noteChannelId: Not(IsNull()) |  | ||||||
| 			}).then(channelNoteCount => { |  | ||||||
| 				if (channelNoteCount === 0) { |  | ||||||
| 					// 全て既読になったイベントを発行
 |  | ||||||
| 					publishMainStream(userId, 'readAllChannels'); |  | ||||||
| 				} |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	async function careAntenna() { |  | ||||||
| 		const beforeUnread = await Users.getHasUnreadAntenna(userId); |  | ||||||
| 		if (!beforeUnread) return; |  | ||||||
| 
 |  | ||||||
| 		const antennas = await Antennas.find({ userId }); |  | ||||||
| 
 |  | ||||||
| 		await Promise.all(antennas.map(async antenna => { |  | ||||||
| 			const countBefore = await AntennaNotes.count({ |  | ||||||
| 				antennaId: antenna.id, |  | ||||||
| 				read: false |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			if (countBefore === 0) return; |  | ||||||
| 
 |  | ||||||
| 			await AntennaNotes.update({ |  | ||||||
| 				antennaId: antenna.id, |  | ||||||
| 				noteId: noteId |  | ||||||
| 			}, { |  | ||||||
| 				read: true |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			const countAfter = await AntennaNotes.count({ |  | ||||||
| 				antennaId: antenna.id, |  | ||||||
| 				read: false |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			if (countAfter === 0) { |  | ||||||
| 				publishMainStream(userId, 'readAntenna', antenna); |  | ||||||
| 			} |  | ||||||
| 		})); |  | ||||||
| 
 |  | ||||||
| 		Users.getHasUnreadAntenna(userId).then(unread => { |  | ||||||
| 			if (!unread) { |  | ||||||
| 				publishMainStream(userId, 'readAllAntennas'); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	careNoteUnreads(); |  | ||||||
| 	careAntenna(); |  | ||||||
| } |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue