perf(reversi): improve performance of reversi backend
This commit is contained in:
parent
259992c65f
commit
94e282b612
12 changed files with 73 additions and 55 deletions
|
@ -234,10 +234,13 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
map: Reversi.maps.eighteight.data,
|
map: Reversi.maps.eighteight.data,
|
||||||
bw: 'random',
|
bw: 'random',
|
||||||
isLlotheo: false,
|
isLlotheo: false,
|
||||||
}).then(x => this.reversiGamesRepository.findOneByOrFail(x.identifiers[0]));
|
}).then(x => this.reversiGamesRepository.findOneOrFail({
|
||||||
|
where: { id: x.identifiers[0].id },
|
||||||
|
relations: ['user1', 'user2'],
|
||||||
|
}));
|
||||||
this.cacheGame(game);
|
this.cacheGame(game);
|
||||||
|
|
||||||
const packed = await this.reversiGameEntityService.packDetail(game, { id: parentId });
|
const packed = await this.reversiGameEntityService.packDetail(game);
|
||||||
this.globalEventService.publishReversiStream(parentId, 'matched', { game: packed });
|
this.globalEventService.publishReversiStream(parentId, 'matched', { game: packed });
|
||||||
|
|
||||||
return game;
|
return game;
|
||||||
|
@ -267,6 +270,9 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
.returning('*')
|
.returning('*')
|
||||||
.execute()
|
.execute()
|
||||||
.then((response) => response.raw[0]);
|
.then((response) => response.raw[0]);
|
||||||
|
// キャッシュ効率化のためにユーザー情報は再利用
|
||||||
|
updatedGame.user1 = game.user1;
|
||||||
|
updatedGame.user2 = game.user2;
|
||||||
this.cacheGame(updatedGame);
|
this.cacheGame(updatedGame);
|
||||||
|
|
||||||
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
|
//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
|
||||||
|
@ -314,6 +320,9 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
.returning('*')
|
.returning('*')
|
||||||
.execute()
|
.execute()
|
||||||
.then((response) => response.raw[0]);
|
.then((response) => response.raw[0]);
|
||||||
|
// キャッシュ効率化のためにユーザー情報は再利用
|
||||||
|
updatedGame.user1 = game.user1;
|
||||||
|
updatedGame.user2 = game.user2;
|
||||||
this.cacheGame(updatedGame);
|
this.cacheGame(updatedGame);
|
||||||
|
|
||||||
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
|
this.globalEventService.publishReversiGameStream(game.id, 'ended', {
|
||||||
|
@ -483,14 +492,36 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
||||||
public async get(id: MiReversiGame['id']): Promise<MiReversiGame | null> {
|
public async get(id: MiReversiGame['id']): Promise<MiReversiGame | null> {
|
||||||
const cached = await this.redisClient.get(`reversi:game:cache:${id}`);
|
const cached = await this.redisClient.get(`reversi:game:cache:${id}`);
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
|
// TODO: この辺りのデシリアライズ処理をどこか別のサービスに切り出したい
|
||||||
const parsed = JSON.parse(cached) as Serialized<MiReversiGame>;
|
const parsed = JSON.parse(cached) as Serialized<MiReversiGame>;
|
||||||
return {
|
return {
|
||||||
...parsed,
|
...parsed,
|
||||||
startedAt: parsed.startedAt != null ? new Date(parsed.startedAt) : null,
|
startedAt: parsed.startedAt != null ? new Date(parsed.startedAt) : null,
|
||||||
endedAt: parsed.endedAt != null ? new Date(parsed.endedAt) : null,
|
endedAt: parsed.endedAt != null ? new Date(parsed.endedAt) : null,
|
||||||
|
user1: parsed.user1 != null ? {
|
||||||
|
...parsed.user1,
|
||||||
|
avatar: null,
|
||||||
|
banner: null,
|
||||||
|
updatedAt: parsed.user1.updatedAt != null ? new Date(parsed.user1.updatedAt) : null,
|
||||||
|
lastActiveDate: parsed.user1.lastActiveDate != null ? new Date(parsed.user1.lastActiveDate) : null,
|
||||||
|
lastFetchedAt: parsed.user1.lastFetchedAt != null ? new Date(parsed.user1.lastFetchedAt) : null,
|
||||||
|
movedAt: parsed.user1.movedAt != null ? new Date(parsed.user1.movedAt) : null,
|
||||||
|
} : null,
|
||||||
|
user2: parsed.user2 != null ? {
|
||||||
|
...parsed.user2,
|
||||||
|
avatar: null,
|
||||||
|
banner: null,
|
||||||
|
updatedAt: parsed.user2.updatedAt != null ? new Date(parsed.user2.updatedAt) : null,
|
||||||
|
lastActiveDate: parsed.user2.lastActiveDate != null ? new Date(parsed.user2.lastActiveDate) : null,
|
||||||
|
lastFetchedAt: parsed.user2.lastFetchedAt != null ? new Date(parsed.user2.lastFetchedAt) : null,
|
||||||
|
movedAt: parsed.user2.movedAt != null ? new Date(parsed.user2.movedAt) : null,
|
||||||
|
} : null,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const game = await this.reversiGamesRepository.findOneBy({ id });
|
const game = await this.reversiGamesRepository.findOne({
|
||||||
|
where: { id },
|
||||||
|
relations: ['user1', 'user2'],
|
||||||
|
});
|
||||||
if (game == null) return null;
|
if (game == null) return null;
|
||||||
|
|
||||||
this.cacheGame(game);
|
this.cacheGame(game);
|
||||||
|
|
|
@ -9,7 +9,6 @@ import type { ReversiGamesRepository } from '@/models/_.js';
|
||||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import type { } from '@/models/Blocking.js';
|
import type { } from '@/models/Blocking.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
|
||||||
import type { MiReversiGame } from '@/models/ReversiGame.js';
|
import type { MiReversiGame } from '@/models/ReversiGame.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
@ -29,10 +28,14 @@ export class ReversiGameEntityService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async packDetail(
|
public async packDetail(
|
||||||
src: MiReversiGame['id'] | MiReversiGame,
|
src: MiReversiGame['id'] | MiReversiGame,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
|
||||||
): Promise<Packed<'ReversiGameDetailed'>> {
|
): Promise<Packed<'ReversiGameDetailed'>> {
|
||||||
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
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),
|
||||||
|
]);
|
||||||
|
|
||||||
return await awaitAll({
|
return await awaitAll({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
createdAt: this.idService.parse(game.id).date.toISOString(),
|
createdAt: this.idService.parse(game.id).date.toISOString(),
|
||||||
|
@ -46,10 +49,10 @@ export class ReversiGameEntityService {
|
||||||
user2Ready: game.user2Ready,
|
user2Ready: game.user2Ready,
|
||||||
user1Id: game.user1Id,
|
user1Id: game.user1Id,
|
||||||
user2Id: game.user2Id,
|
user2Id: game.user2Id,
|
||||||
user1: this.userEntityService.pack(game.user1Id, me),
|
user1: users[0],
|
||||||
user2: this.userEntityService.pack(game.user2Id, me),
|
user2: users[1],
|
||||||
winnerId: game.winnerId,
|
winnerId: game.winnerId,
|
||||||
winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
|
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
|
||||||
surrenderedUserId: game.surrenderedUserId,
|
surrenderedUserId: game.surrenderedUserId,
|
||||||
timeoutUserId: game.timeoutUserId,
|
timeoutUserId: game.timeoutUserId,
|
||||||
black: game.black,
|
black: game.black,
|
||||||
|
@ -66,18 +69,21 @@ export class ReversiGameEntityService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public packDetailMany(
|
public packDetailMany(
|
||||||
xs: MiReversiGame[],
|
xs: MiReversiGame[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
|
||||||
) {
|
) {
|
||||||
return Promise.all(xs.map(x => this.packDetail(x, me)));
|
return Promise.all(xs.map(x => this.packDetail(x)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async packLite(
|
public async packLite(
|
||||||
src: MiReversiGame['id'] | MiReversiGame,
|
src: MiReversiGame['id'] | MiReversiGame,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
|
||||||
): Promise<Packed<'ReversiGameLite'>> {
|
): Promise<Packed<'ReversiGameLite'>> {
|
||||||
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
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),
|
||||||
|
]);
|
||||||
|
|
||||||
return await awaitAll({
|
return await awaitAll({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
createdAt: this.idService.parse(game.id).date.toISOString(),
|
createdAt: this.idService.parse(game.id).date.toISOString(),
|
||||||
|
@ -85,16 +91,12 @@ export class ReversiGameEntityService {
|
||||||
endedAt: game.endedAt && game.endedAt.toISOString(),
|
endedAt: game.endedAt && game.endedAt.toISOString(),
|
||||||
isStarted: game.isStarted,
|
isStarted: game.isStarted,
|
||||||
isEnded: game.isEnded,
|
isEnded: game.isEnded,
|
||||||
form1: game.form1,
|
|
||||||
form2: game.form2,
|
|
||||||
user1Ready: game.user1Ready,
|
|
||||||
user2Ready: game.user2Ready,
|
|
||||||
user1Id: game.user1Id,
|
user1Id: game.user1Id,
|
||||||
user2Id: game.user2Id,
|
user2Id: game.user2Id,
|
||||||
user1: this.userEntityService.pack(game.user1Id, me),
|
user1: users[0],
|
||||||
user2: this.userEntityService.pack(game.user2Id, me),
|
user2: users[1],
|
||||||
winnerId: game.winnerId,
|
winnerId: game.winnerId,
|
||||||
winner: game.winnerId ? this.userEntityService.pack(game.winnerId, me) : null,
|
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
|
||||||
surrenderedUserId: game.surrenderedUserId,
|
surrenderedUserId: game.surrenderedUserId,
|
||||||
timeoutUserId: game.timeoutUserId,
|
timeoutUserId: game.timeoutUserId,
|
||||||
black: game.black,
|
black: game.black,
|
||||||
|
@ -109,9 +111,8 @@ export class ReversiGameEntityService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public packLiteMany(
|
public packLiteMany(
|
||||||
xs: MiReversiGame[],
|
xs: MiReversiGame[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
|
||||||
) {
|
) {
|
||||||
return Promise.all(xs.map(x => this.packLite(x, me)));
|
return Promise.all(xs.map(x => this.packLite(x)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,22 +34,6 @@ export const packedReversiGameLiteSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
form1: {
|
|
||||||
type: 'any',
|
|
||||||
optional: false, nullable: true,
|
|
||||||
},
|
|
||||||
form2: {
|
|
||||||
type: 'any',
|
|
||||||
optional: false, nullable: true,
|
|
||||||
},
|
|
||||||
user1Ready: {
|
|
||||||
type: 'boolean',
|
|
||||||
optional: false, nullable: false,
|
|
||||||
},
|
|
||||||
user2Ready: {
|
|
||||||
type: 'boolean',
|
|
||||||
optional: false, nullable: false,
|
|
||||||
},
|
|
||||||
user1Id: {
|
user1Id: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -43,7 +43,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId)
|
const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId)
|
||||||
.andWhere('game.isStarted = TRUE');
|
.andWhere('game.isStarted = TRUE')
|
||||||
|
.innerJoinAndSelect('game.user1', 'user1')
|
||||||
|
.innerJoinAndSelect('game.user2', 'user2');
|
||||||
|
|
||||||
if (ps.my && me) {
|
if (ps.my && me) {
|
||||||
query.andWhere(new Brackets(qb => {
|
query.andWhere(new Brackets(qb => {
|
||||||
|
@ -55,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
const games = await query.take(ps.limit).getMany();
|
const games = await query.take(ps.limit).getMany();
|
||||||
|
|
||||||
return await this.reversiGameEntityService.packLiteMany(games, me);
|
return await this.reversiGameEntityService.packLiteMany(games);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
if (game == null) return;
|
if (game == null) return;
|
||||||
|
|
||||||
return await this.reversiGameEntityService.packDetail(game, me);
|
return await this.reversiGameEntityService.packDetail(game);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.noSuchGame);
|
throw new ApiError(meta.errors.noSuchGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.reversiGameEntityService.packDetail(game, me);
|
return await this.reversiGameEntityService.packDetail(game);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,7 +277,11 @@ export type Serialized<T> = {
|
||||||
? (string | null)
|
? (string | null)
|
||||||
: T[K] extends Record<string, any>
|
: T[K] extends Record<string, any>
|
||||||
? Serialized<T[K]>
|
? Serialized<T[K]>
|
||||||
: T[K];
|
: T[K] extends (Record<string, any> | null)
|
||||||
|
? (Serialized<T[K]> | null)
|
||||||
|
: T[K] extends (Record<string, any> | undefined)
|
||||||
|
? (Serialized<T[K]> | undefined)
|
||||||
|
: T[K];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FilterUnionByProperty<
|
export type FilterUnionByProperty<
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2024.2.0-beta.2
|
||||||
* generatedAt: 2024-01-21T01:01:12.332Z
|
* generatedAt: 2024-01-22T06:08:45.879Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { SwitchCaseResponseType } from '../api.js';
|
import type { SwitchCaseResponseType } from '../api.js';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2024.2.0-beta.2
|
||||||
* generatedAt: 2024-01-21T01:01:12.330Z
|
* generatedAt: 2024-01-22T06:08:45.877Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2024.2.0-beta.2
|
||||||
* generatedAt: 2024-01-21T01:01:12.328Z
|
* generatedAt: 2024-01-22T06:08:45.876Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { operations } from './types.js';
|
import { operations } from './types.js';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2024.2.0-beta.2
|
||||||
* generatedAt: 2024-01-21T01:01:12.327Z
|
* generatedAt: 2024-01-22T06:08:45.875Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { components } from './types.js';
|
import { components } from './types.js';
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2024.2.0-beta.2
|
||||||
* generatedAt: 2024-01-21T01:01:12.246Z
|
* generatedAt: 2024-01-22T06:08:45.796Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4469,10 +4469,6 @@ export type components = {
|
||||||
endedAt: string | null;
|
endedAt: string | null;
|
||||||
isStarted: boolean;
|
isStarted: boolean;
|
||||||
isEnded: boolean;
|
isEnded: boolean;
|
||||||
form1: Record<string, never> | null;
|
|
||||||
form2: Record<string, never> | null;
|
|
||||||
user1Ready: boolean;
|
|
||||||
user2Ready: boolean;
|
|
||||||
/** Format: id */
|
/** Format: id */
|
||||||
user1Id: string;
|
user1Id: string;
|
||||||
/** Format: id */
|
/** Format: id */
|
||||||
|
|
Loading…
Reference in a new issue