Skip to content

Commit

Permalink
perf(reversi): improve performance of reversi backend
Browse files Browse the repository at this point in the history
  • Loading branch information
syuilo committed Jan 22, 2024
1 parent 259992c commit 94e282b
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 55 deletions.
37 changes: 34 additions & 3 deletions packages/backend/src/core/ReversiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,13 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
map: Reversi.maps.eighteight.data,
bw: 'random',
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);

const packed = await this.reversiGameEntityService.packDetail(game, { id: parentId });
const packed = await this.reversiGameEntityService.packDetail(game);
this.globalEventService.publishReversiStream(parentId, 'matched', { game: packed });

return game;
Expand Down Expand Up @@ -267,6 +270,9 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
.returning('*')
.execute()
.then((response) => response.raw[0]);
// キャッシュ効率化のためにユーザー情報は再利用
updatedGame.user1 = game.user1;
updatedGame.user2 = game.user2;
this.cacheGame(updatedGame);

//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
Expand Down Expand Up @@ -314,6 +320,9 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
.returning('*')
.execute()
.then((response) => response.raw[0]);
// キャッシュ効率化のためにユーザー情報は再利用
updatedGame.user1 = game.user1;
updatedGame.user2 = game.user2;
this.cacheGame(updatedGame);

this.globalEventService.publishReversiGameStream(game.id, 'ended', {
Expand Down Expand Up @@ -483,14 +492,36 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
public async get(id: MiReversiGame['id']): Promise<MiReversiGame | null> {
const cached = await this.redisClient.get(`reversi:game:cache:${id}`);
if (cached != null) {
// TODO: この辺りのデシリアライズ処理をどこか別のサービスに切り出したい
const parsed = JSON.parse(cached) as Serialized<MiReversiGame>;
return {
...parsed,
startedAt: parsed.startedAt != null ? new Date(parsed.startedAt) : 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 {
const game = await this.reversiGamesRepository.findOneBy({ id });
const game = await this.reversiGamesRepository.findOne({
where: { id },
relations: ['user1', 'user2'],
});
if (game == null) return null;

this.cacheGame(game);
Expand Down
35 changes: 18 additions & 17 deletions packages/backend/src/core/entities/ReversiGameEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type { ReversiGamesRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
import type { MiUser } from '@/models/User.js';
import type { MiReversiGame } from '@/models/ReversiGame.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
Expand All @@ -29,10 +28,14 @@ export class ReversiGameEntityService {
@bindThis
public async packDetail(
src: MiReversiGame['id'] | MiReversiGame,
me?: { id: MiUser['id'] } | null | undefined,
): 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),
]);

return await awaitAll({
id: game.id,
createdAt: this.idService.parse(game.id).date.toISOString(),
Expand All @@ -46,10 +49,10 @@ export class ReversiGameEntityService {
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: this.userEntityService.pack(game.user1Id, me),
user2: this.userEntityService.pack(game.user2Id, me),
user1: users[0],
user2: users[1],
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,
timeoutUserId: game.timeoutUserId,
black: game.black,
Expand All @@ -66,35 +69,34 @@ export class ReversiGameEntityService {
@bindThis
public packDetailMany(
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
public async packLite(
src: MiReversiGame['id'] | MiReversiGame,
me?: { id: MiUser['id'] } | null | undefined,
): 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),
]);

return await awaitAll({
id: game.id,
createdAt: this.idService.parse(game.id).date.toISOString(),
startedAt: game.startedAt && game.startedAt.toISOString(),
endedAt: game.endedAt && game.endedAt.toISOString(),
isStarted: game.isStarted,
isEnded: game.isEnded,
form1: game.form1,
form2: game.form2,
user1Ready: game.user1Ready,
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: this.userEntityService.pack(game.user1Id, me),
user2: this.userEntityService.pack(game.user2Id, me),
user1: users[0],
user2: users[1],
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,
timeoutUserId: game.timeoutUserId,
black: game.black,
Expand All @@ -109,9 +111,8 @@ export class ReversiGameEntityService {
@bindThis
public packLiteMany(
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)));
}
}

16 changes: 0 additions & 16 deletions packages/backend/src/models/json-schema/reversi-game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,6 @@ export const packedReversiGameLiteSchema = {
type: 'boolean',
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: {
type: 'string',
optional: false, nullable: false,
Expand Down
6 changes: 4 additions & 2 deletions packages/backend/src/server/api/endpoints/reversi/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
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) {
query.andWhere(new Brackets(qb => {
Expand All @@ -55,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-

const games = await query.take(ps.limit).getMany();

return await this.reversiGameEntityService.packLiteMany(games, me);
return await this.reversiGameEntityService.packLiteMany(games);
});
}
}
2 changes: 1 addition & 1 deletion packages/backend/src/server/api/endpoints/reversi/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-

if (game == null) return;

return await this.reversiGameEntityService.packDetail(game, me);
return await this.reversiGameEntityService.packDetail(game);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchGame);
}

return await this.reversiGameEntityService.packDetail(game, me);
return await this.reversiGameEntityService.packDetail(game);
});
}
}
6 changes: 5 additions & 1 deletion packages/backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,11 @@ export type Serialized<T> = {
? (string | null)
: T[K] extends Record<string, any>
? 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<
Expand Down
4 changes: 2 additions & 2 deletions packages/misskey-js/src/autogen/apiClientJSDoc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* version: 2023.12.2
* generatedAt: 2024-01-21T01:01:12.332Z
* version: 2024.2.0-beta.2
* generatedAt: 2024-01-22T06:08:45.879Z
*/

import type { SwitchCaseResponseType } from '../api.js';
Expand Down
4 changes: 2 additions & 2 deletions packages/misskey-js/src/autogen/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* version: 2023.12.2
* generatedAt: 2024-01-21T01:01:12.330Z
* version: 2024.2.0-beta.2
* generatedAt: 2024-01-22T06:08:45.877Z
*/

import type {
Expand Down
4 changes: 2 additions & 2 deletions packages/misskey-js/src/autogen/entities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* version: 2023.12.2
* generatedAt: 2024-01-21T01:01:12.328Z
* version: 2024.2.0-beta.2
* generatedAt: 2024-01-22T06:08:45.876Z
*/

import { operations } from './types.js';
Expand Down
4 changes: 2 additions & 2 deletions packages/misskey-js/src/autogen/models.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* version: 2023.12.2
* generatedAt: 2024-01-21T01:01:12.327Z
* version: 2024.2.0-beta.2
* generatedAt: 2024-01-22T06:08:45.875Z
*/

import { components } from './types.js';
Expand Down
8 changes: 2 additions & 6 deletions packages/misskey-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
/* eslint @typescript-eslint/no-explicit-any: 0 */

/*
* version: 2023.12.2
* generatedAt: 2024-01-21T01:01:12.246Z
* version: 2024.2.0-beta.2
* generatedAt: 2024-01-22T06:08:45.796Z
*/

/**
Expand Down Expand Up @@ -4469,10 +4469,6 @@ export type components = {
endedAt: string | null;
isStarted: boolean;
isEnded: boolean;
form1: Record<string, never> | null;
form2: Record<string, never> | null;
user1Ready: boolean;
user2Ready: boolean;
/** Format: id */
user1Id: string;
/** Format: id */
Expand Down

0 comments on commit 94e282b

Please sign in to comment.