@@ -23,8 +23,8 @@ import { getPreviousDate } from "./statistics.utils";
23
23
import { hours } from "@nestjs/throttler" ;
24
24
import { GameRepositoryService } from "../game/game-repository/game-repository.service" ;
25
25
import { Cache } from "@nestjs/cache-manager" ;
26
- import { MATURE_THEME_ID } from "../game/game-filter/game-filter.constants" ;
27
26
import isEmptyObject from "../utils/isEmptyObject" ;
27
+ import { MATURE_THEME_ID } from "../game/game-filter/game-filter.constants" ;
28
28
29
29
@Injectable ( )
30
30
export class GameStatisticsService implements StatisticsService {
@@ -180,12 +180,12 @@ export class GameStatisticsService implements StatisticsService {
180
180
}
181
181
182
182
async findTrending (
183
- data : FindStatisticsTrendingGamesDto ,
183
+ dto : FindStatisticsTrendingGamesDto ,
184
184
) : Promise < TPaginationData < GameStatistics > > {
185
- const { period, criteria, offset, limit } = data ;
185
+ const { period, criteria, offset, limit } = dto ;
186
186
const offsetToUse = offset || 0 ;
187
- // We save up to this N statistics entities on cache to improve load performance.
188
- const fixedStatisticsLimit = 25000 ;
187
+
188
+ const FIXED_STATISTICS_LIMIT = 50000 ;
189
189
// User supplied limit
190
190
const limitToUse = limit || 20 ;
191
191
const minusDays = StatisticsPeriodToMinusDays [ period ] ;
@@ -194,31 +194,46 @@ export class GameStatisticsService implements StatisticsService {
194
194
let statistics = await this . getCachedStatistics ( period ) ;
195
195
196
196
if ( statistics == undefined ) {
197
- const queryBuilder =
198
- this . gameStatisticsRepository . createQueryBuilder ( "s" ) ;
199
-
200
197
/**
201
198
* Made with query builder, so we can further optimize the query
202
199
*/
200
+ const queryBuilder =
201
+ this . gameStatisticsRepository . createQueryBuilder ( "gs" ) ;
202
+
203
+ const userViewSubQuery = this . userViewRepository
204
+ . createQueryBuilder ( "uv" )
205
+ . select ( "uv.gameStatisticsId, COUNT(uv.id) AS total" )
206
+ . where ( "uv.gameStatisticsId IS NOT NULL" )
207
+ . groupBy ( "uv.gameStatisticsId" ) ;
208
+
209
+ if ( period !== StatisticsPeriod . ALL ) {
210
+ userViewSubQuery . andWhere ( "uv.createdAt >= :viewsStartDate" ) ;
211
+ }
212
+
203
213
const query = queryBuilder
204
- . select ( )
205
- . leftJoin ( UserView , `uv` , `uv.gameStatisticsId = s.id` )
206
- . where ( `(uv.createdAt >= :uvDate OR s.viewsCount = 0)` , {
207
- uvDate : viewsStartDate ,
208
- } )
209
- // Excludes games with mature theme
210
- . andWhere (
211
- `NOT EXISTS (SELECT 1 FROM game_themes_game_theme AS gtgt WHERE gtgt.gameId = s.gameId
214
+ . addSelect ( "IFNULL(in_period.total, 0) AS views_in_period" )
215
+ . leftJoin (
216
+ `(${ userViewSubQuery . getQuery ( ) } )` ,
217
+ "in_period" ,
218
+ "in_period.gameStatisticsId = gs.id" ,
219
+ )
220
+ . where (
221
+ // Excludes games with mature theme
222
+ `NOT EXISTS (SELECT 1 FROM game_themes_game_theme AS gtgt WHERE gtgt.gameId = gs.gameId
212
223
AND gtgt.gameThemeId = :excludedThemeId)` ,
213
- {
214
- excludedThemeId : MATURE_THEME_ID ,
215
- } ,
216
224
)
217
- . addOrderBy ( `s.viewsCount` , `DESC` )
218
- . skip ( 0 )
219
- . take ( fixedStatisticsLimit ) ;
225
+ . orderBy ( "views_in_period" , "DESC" )
226
+ . addOrderBy ( "gs.viewsCount" , "DESC" )
227
+ . limit ( FIXED_STATISTICS_LIMIT ) ;
228
+
229
+ query . setParameters ( {
230
+ viewsStartDate : viewsStartDate ,
231
+ excludedThemeId : MATURE_THEME_ID ,
232
+ } ) ;
220
233
221
234
statistics = await query . getMany ( ) ;
235
+ // Storing the entire table takes roughly ~16mb in Redis.
236
+ // 16mb * 7 = ~112mb to store statistics for all periods
222
237
this . setCachedStatistics ( period , statistics ) ;
223
238
}
224
239
@@ -228,15 +243,15 @@ export class GameStatisticsService implements StatisticsService {
228
243
offsetToUse ,
229
244
offsetToUse + limitToUse ,
230
245
) ;
231
- return [ slicedStatistics , fixedStatisticsLimit ] ;
246
+ return [ slicedStatistics , FIXED_STATISTICS_LIMIT ] ;
232
247
}
233
248
234
249
const gameIds = statistics . map ( ( s ) => s . gameId ) ;
235
250
const games = await this . gameRepositoryService . findAllIdsWithFilters ( {
236
251
...criteria ,
237
252
ids : gameIds ,
238
253
// We need to return all entities to maintain pagination order
239
- limit : fixedStatisticsLimit ,
254
+ limit : FIXED_STATISTICS_LIMIT ,
240
255
offset : 0 ,
241
256
} ) ;
242
257
const totalAvailableGames = games . length ;
0 commit comments