diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-10-08 08:10:28 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-10-08 08:10:28 +0900 |
| commit | 2859cbab913dc836f4beb55cf967d1feeb87e6c3 (patch) | |
| tree | 063f62cf27488ca6fdbf8c41bce334aa7e945ab0 /packages/backend/src | |
| parent | perf(backend): use HyperLogLog instead of Set to improve hashtag chart perfor... (diff) | |
| download | misskey-2859cbab913dc836f4beb55cf967d1feeb87e6c3.tar.gz misskey-2859cbab913dc836f4beb55cf967d1feeb87e6c3.tar.bz2 misskey-2859cbab913dc836f4beb55cf967d1feeb87e6c3.zip | |
perf(backend): WebSocket接続が多い場合のパフォーマンスを向上
https: //github.com/MisskeyIO/misskey/pull/176
Co-Authored-By: KOBA789 <kobahide789@gmail.com>
Diffstat (limited to 'packages/backend/src')
| -rw-r--r-- | packages/backend/src/core/entities/NoteEntityService.ts | 12 | ||||
| -rw-r--r-- | packages/backend/src/misc/loader.ts | 52 |
2 files changed, 63 insertions, 1 deletions
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index e45a7992bb..abe4aafd6e 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -17,6 +17,7 @@ import type { MiNoteReaction } from '@/models/NoteReaction.js'; import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { isNotNull } from '@/misc/is-not-null.js'; +import { DebounceLoader } from '@/misc/loader.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; @@ -29,6 +30,7 @@ export class NoteEntityService implements OnModuleInit { private driveFileEntityService: DriveFileEntityService; private customEmojiService: CustomEmojiService; private reactionService: ReactionService; + private noteLoader = new DebounceLoader(this.findNoteOrFail); constructor( private moduleRef: ModuleRef, @@ -285,7 +287,7 @@ export class NoteEntityService implements OnModuleInit { }, options); const meId = me ? me.id : null; - const note = typeof src === 'object' ? src : await this.notesRepository.findOneOrFail({ where: { id: src }, relations: ['user'] }); + const note = typeof src === 'object' ? src : await this.noteLoader.load(src); const host = note.userHost; let text = note.text; @@ -450,4 +452,12 @@ export class NoteEntityService implements OnModuleInit { } return emojis.filter(x => x.name != null && x.host != null) as { name: string; host: string; }[]; } + + @bindThis + private findNoteOrFail(id: string): Promise<MiNote> { + return this.notesRepository.findOneOrFail({ + where: { id }, + relations: ['user'], + }); + } } diff --git a/packages/backend/src/misc/loader.ts b/packages/backend/src/misc/loader.ts new file mode 100644 index 0000000000..25f7b54d31 --- /dev/null +++ b/packages/backend/src/misc/loader.ts @@ -0,0 +1,52 @@ +export type FetchFunction<K, V> = (key: K) => Promise<V>; + +type ResolveReject<V> = Parameters<ConstructorParameters<typeof Promise<V>>[0]>; + +type ResolverPair<V> = { + resolve: ResolveReject<V>[0]; + reject: ResolveReject<V>[1]; +}; + +export class DebounceLoader<K, V> { + private resolverMap = new Map<K, ResolverPair<V>>(); + private promiseMap = new Map<K, Promise<V>>(); + private resolvedPromise = Promise.resolve(); + constructor(private loadFn: FetchFunction<K, V>) {} + + public load(key: K): Promise<V> { + const promise = this.promiseMap.get(key); + if (typeof promise !== 'undefined') { + return promise; + } + + const isFirst = this.promiseMap.size === 0; + const newPromise = new Promise<V>((resolve, reject) => { + this.resolverMap.set(key, { resolve, reject }); + }); + this.promiseMap.set(key, newPromise); + + if (isFirst) { + this.enqueueDebouncedLoadJob(); + } + + return newPromise; + } + + private runDebouncedLoad(): void { + const resolvers = [...this.resolverMap]; + this.resolverMap.clear(); + this.promiseMap.clear(); + + for (const [key, { resolve, reject }] of resolvers) { + this.loadFn(key).then(resolve, reject); + } + } + + private enqueueDebouncedLoadJob(): void { + this.resolvedPromise.then(() => { + process.nextTick(() => { + this.runDebouncedLoad(); + }); + }); + } +} |