Skip to content

Commit

Permalink
Merge pull request #129 from game-node-app/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Lamarcke authored Dec 8, 2024
2 parents 1f26621 + 58fbda8 commit fe982bb
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 41 deletions.
2 changes: 1 addition & 1 deletion server_swagger.json

Large diffs are not rendered by default.

33 changes: 8 additions & 25 deletions src/game/game-repository/game-repository.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Game } from "./entities/game.entity";
import { DataSource, FindOptionsRelations, In, Repository } from "typeorm";
import { DataSource, In, Repository } from "typeorm";
import { GameGenre } from "./entities/game-genre.entity";
import { GamePlatform } from "./entities/game-platform.entity";
import { GameTheme } from "./entities/game-theme.entity";
Expand All @@ -26,6 +26,7 @@ import {
getStoreNameForExternalGameCategory,
} from "./game-repository.utils";
import { toMap } from "../../utils/toMap";
import { getRelationLoadStrategy } from "../../utils/getRelationLoadStrategy";

/**
* Look-up table between resource names and their respective entities
Expand All @@ -44,7 +45,6 @@ export type TAllowedResource = keyof typeof resourceToTargetEntityMap;
@Injectable()
export class GameRepositoryService {
private readonly logger = new Logger(GameRepositoryService.name);
private readonly maximumAllowedRelationsQuery = 3;

/**
* @param dataSource
Expand All @@ -62,37 +62,17 @@ export class GameRepositoryService {
private readonly gameExternalGameRepository: Repository<GameExternalGame>,
) {}

private validateMaximumRelations(
relations: FindOptionsRelations<Game> | undefined,
) {
if (!relations) return;
const totalQueriedEntries = Object.entries(relations).filter(
([key, value]) => {
// E.g.: genres: true
return key != undefined && value;
},
).length;
if (totalQueriedEntries > this.maximumAllowedRelationsQuery) {
throw new HttpException(
`For performance reasons, queries with more than ${this.maximumAllowedRelationsQuery} relations are not allowed. Send multiple requests instead.`,
HttpStatus.BAD_REQUEST,
);
}
}

async findOneById(
id: number,
dto?: GameRepositoryFindOneDto,
): Promise<Game> {
this.validateMaximumRelations(dto?.relations);

const game = await this.gameRepository.findOne({
where: {
id,
},
relations: dto?.relations,
cache: minutes(5),
relationLoadStrategy: "query",
relationLoadStrategy: getRelationLoadStrategy(dto?.relations),
});
if (!game) {
throw new HttpException("Game not found.", HttpStatus.NOT_FOUND);
Expand All @@ -119,15 +99,14 @@ export class GameRepositoryService {
) {
throw new HttpException("Invalid query.", HttpStatus.BAD_REQUEST);
}
this.validateMaximumRelations(dto?.relations);

const games = await this.gameRepository.find({
where: {
id: In(dto?.gameIds),
},
relations: dto.relations,
cache: minutes(5),
relationLoadStrategy: "query",
relationLoadStrategy: getRelationLoadStrategy(dto?.relations),
});

if (games.length === 0) {
Expand Down Expand Up @@ -268,6 +247,10 @@ export class GameRepositoryService {
uid: In(sourceIds),
category,
},
cache: {
id: `external-games-ids-${category}-${sourceIds}`,
milliseconds: days(1),
},
});
}
}
6 changes: 4 additions & 2 deletions src/importer/entity/importer-notification.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export class ImporterWatchNotification {
})
source: EImporterSource;

@ManyToMany(() => GameExternalGame)
@ManyToMany(() => GameExternalGame, {
nullable: true,
})
@JoinTable()
games: GameExternalGame[];
games: GameExternalGame[] | null;
}
18 changes: 5 additions & 13 deletions src/statistics/game-statistics.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class GameStatisticsService implements StatisticsService {
const { period, criteria, offset, limit } = data;
const offsetToUse = offset || 0;
// We save up to this N statistics entities on cache to improve load performance.
const fixedStatisticsLimit = 10000;
const fixedStatisticsLimit = 25000;
// User supplied limit
const limitToUse = limit || 20;
const minusDays = StatisticsPeriodToMinusDays[period];
Expand All @@ -167,6 +167,9 @@ export class GameStatisticsService implements StatisticsService {
const queryBuilder =
this.gameStatisticsRepository.createQueryBuilder("s");

/**
* Made with query builder, so we can further optimize the query
*/
const query = queryBuilder
.select()
.leftJoin(UserView, `uv`, `uv.gameStatisticsId = s.id`)
Expand All @@ -181,22 +184,11 @@ export class GameStatisticsService implements StatisticsService {
excludedThemeId: MATURE_THEME_ID,
},
)
// Excludes admin excluded games
.andWhere(() => {
const excludedQuery =
this.gameFilterService.buildQueryBuilderSubQuery(
`s.gameId`,
);
return `NOT EXISTS ${excludedQuery}`;
})
.addOrderBy(`s.viewsCount`, `DESC`)
.skip(0)
.take(fixedStatisticsLimit)
.cache(`trending-games-statistics-${period}`, hours(6));
.cache(`trending-games-statistics-${period}`, hours(24));

/**
* Made with query builder, so we can further optimize the query
*/
const statistics = await query.getMany();

const gameIds = statistics.map((s) => s.gameId);
Expand Down
36 changes: 36 additions & 0 deletions src/utils/getRelationLoadStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { FindOneOptions, FindOptionsRelations } from "typeorm";

/**
* Gets the most performant relation strategy based on amount of relations being loaded.
* @see https://github.com/typeorm/typeorm/issues/3857
* @param relations
*/
export function getRelationLoadStrategy(
relations: FindOptionsRelations<any> | undefined,
): FindOneOptions["relationLoadStrategy"] {
if (relations == undefined) return "join";

/**
* Maximum number of relations allowed to be used with the "join" method.
* Anything above this will greatly reduce performance.
*/
const MAX_JOIN_RELATION_COUNT = 2;

let totalQueriedEntries = 0;

for (const [key, value] of Object.entries(relations)) {
if (typeof value === "string" && value.length > 0) {
totalQueriedEntries += value.split(".").length;

// e.g. { cover: true }
} else if (value != undefined && value) {
totalQueriedEntries += 1;
}
}

if (totalQueriedEntries >= MAX_JOIN_RELATION_COUNT) {
return "query";
}

return "join";
}

0 comments on commit fe982bb

Please sign in to comment.