perf(backend): avoid N+1 selects from user table when packing many entities (#13911)

* perf(backend): avoid N+1 selects from `user` table when packing many entities

* perf(backend): use `packMany` instead of mapping to `pack`
This commit is contained in:
zyoshoka 2024-05-31 15:32:42 +09:00 committed by GitHub
parent 97be1a53ad
commit 514a65e453
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 268 additions and 87 deletions

View file

@ -28,13 +28,15 @@ export class ReversiGameEntityService {
@bindThis
public async packDetail(
src: MiReversiGame['id'] | MiReversiGame,
hint?: {
packedUser1?: Packed<'UserLite'>,
packedUser2?: Packed<'UserLite'>,
},
): Promise<Packed<'ReversiGameDetailed'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
const users = await Promise.all([
this.userEntityService.pack(game.user1 ?? game.user1Id),
this.userEntityService.pack(game.user2 ?? game.user2Id),
]);
const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
return await awaitAll({
id: game.id,
@ -49,10 +51,10 @@ export class ReversiGameEntityService {
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: users[0],
user2: users[1],
user1,
user2,
winnerId: game.winnerId,
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
winner: game.winnerId ? [user1, user2].find(u => u.id === game.winnerId)! : null,
surrenderedUserId: game.surrenderedUserId,
timeoutUserId: game.timeoutUserId,
black: game.black,
@ -68,22 +70,35 @@ export class ReversiGameEntityService {
}
@bindThis
public packDetailMany(
xs: MiReversiGame[],
public async packDetailMany(
games: MiReversiGame[],
) {
return Promise.all(xs.map(x => this.packDetail(x)));
const _user1s = games.map(({ user1, user1Id }) => user1 ?? user1Id);
const _user2s = games.map(({ user2, user2Id }) => user2 ?? user2Id);
const _userMap = await this.userEntityService.packMany([..._user1s, ..._user2s])
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(
games.map(game => {
return this.packDetail(game, {
packedUser1: _userMap.get(game.user1Id),
packedUser2: _userMap.get(game.user2Id),
});
}),
);
}
@bindThis
public async packLite(
src: MiReversiGame['id'] | MiReversiGame,
hint?: {
packedUser1?: Packed<'UserLite'>,
packedUser2?: Packed<'UserLite'>,
},
): Promise<Packed<'ReversiGameLite'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
const users = await Promise.all([
this.userEntityService.pack(game.user1 ?? game.user1Id),
this.userEntityService.pack(game.user2 ?? game.user2Id),
]);
const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
return await awaitAll({
id: game.id,
@ -94,10 +109,10 @@ export class ReversiGameEntityService {
isEnded: game.isEnded,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: users[0],
user2: users[1],
user1,
user2,
winnerId: game.winnerId,
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
winner: game.winnerId ? [user1, user2].find(u => u.id === game.winnerId)! : null,
surrenderedUserId: game.surrenderedUserId,
timeoutUserId: game.timeoutUserId,
black: game.black,
@ -111,10 +126,21 @@ export class ReversiGameEntityService {
}
@bindThis
public packLiteMany(
xs: MiReversiGame[],
public async packLiteMany(
games: MiReversiGame[],
) {
return Promise.all(xs.map(x => this.packLite(x)));
const _user1s = games.map(({ user1, user1Id }) => user1 ?? user1Id);
const _user2s = games.map(({ user2, user2Id }) => user2 ?? user2Id);
const _userMap = await this.userEntityService.packMany([..._user1s, ..._user2s])
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(
games.map(game => {
return this.packLite(game, {
packedUser1: _userMap.get(game.user1Id),
packedUser2: _userMap.get(game.user2Id),
});
}),
);
}
}