fix: pre-compute stats to make the API endpoint fast
All checks were successful
pipeline / lint-and-format (push) Successful in 4m22s
pipeline / build-and-push-images (push) Successful in 2m14s

This commit is contained in:
2026-05-14 00:02:23 +02:00
parent e76cb3ea8e
commit 0224b7812c
3 changed files with 181 additions and 2 deletions

View File

@@ -1,14 +1,29 @@
import type { MongoClient } from 'mongodb' import type { MongoClient } from 'mongodb'
import { connectToDatabase, fetchLatestPatch, getAvailablePlatforms } from '../utils/mongo' import { connectToDatabase, fetchLatestPatch, getAvailablePlatforms } from '../utils/mongo'
interface StatsDocument {
patch: string
total: number
platforms: Record<string, number>
updatedAt: Date
}
async function fetchGameCount(client: MongoClient, patch: string) { async function fetchGameCount(client: MongoClient, patch: string) {
const database = client.db('matches') const database = client.db('matches')
const statsCollection = database.collection<StatsDocument>('stats')
// Check for platform-specific collections // Try to get stats from the pre-computed stats collection
const stats = await statsCollection.findOne({ patch })
if (stats) {
return { total: stats.total, platforms: stats.platforms }
}
// Fallback: compute stats from collections if stats document doesn't exist
// This handles the migration case where stats weren't pre-computed
const platforms = await getAvailablePlatforms(client, patch) const platforms = await getAvailablePlatforms(client, patch)
if (platforms.length > 0) { if (platforms.length > 0) {
// Sum counts from all platform-specific collections
let totalCount = 0 let totalCount = 0
const platformCounts: Record<string, number> = {} const platformCounts: Record<string, number> = {}

View File

@@ -4,6 +4,7 @@ const sleep_minutes = 12
import { MongoClient } from 'mongodb' import { MongoClient } from 'mongodb'
import champion_stat from './champion_stat' import champion_stat from './champion_stat'
import stats from './stats'
import { Match } from './api' import { Match } from './api'
import { PLATFORMS, getPlatformBaseUrl, getRegionalBaseUrl, getRegionForPlatform } from './platform' import { PLATFORMS, getPlatformBaseUrl, getRegionalBaseUrl, getRegionForPlatform } from './platform'
import { downloadCDragonAssets } from './cdragon_cache' import { downloadCDragonAssets } from './cdragon_cache'
@@ -98,6 +99,9 @@ async function cleanupOldPatches(client: MongoClient, keepPatches: number): Prom
} }
} }
// Delete stats for removed patches
await stats.deleteStats(client, patchesToRemove)
console.log(`Cleanup: Dropped ${droppedCount} collection(s).`) console.log(`Cleanup: Dropped ${droppedCount} collection(s).`)
} }
@@ -300,6 +304,9 @@ async function saveMatch(client: MongoClient, match: Match, patch: string, platf
const collectionName = `${patch}_${platform}` const collectionName = `${patch}_${platform}`
const matches = database.collection(collectionName) const matches = database.collection(collectionName)
await matches.insertOne(match) await matches.insertOne(match)
// Increment stats counter for this patch/platform
await stats.incrementMatchCount(client, patch, platform)
} }
/** /**
@@ -348,6 +355,11 @@ async function runWithPreloadedData() {
.filter(name => name.startsWith(`${latestPatch}_`)) .filter(name => name.startsWith(`${latestPatch}_`))
.map(name => name.replace(`${latestPatch}_`, '')) .map(name => name.replace(`${latestPatch}_`, ''))
// Recalculate match count stats from existing collections
if (platforms.length > 0) {
await stats.recalculateStats(client, latestPatch, platforms)
}
// Generate stats for each platform // Generate stats for each platform
if (platforms.length > 0) { if (platforms.length > 0) {
await champion_stat.makeChampionsStats(client, latestPatch, platforms) await champion_stat.makeChampionsStats(client, latestPatch, platforms)

View File

@@ -0,0 +1,152 @@
import { MongoClient } from 'mongodb'
/**
* Stats document structure stored in the 'stats' collection.
* One document per patch, containing total and per-platform match counts.
*/
interface StatsDocument {
patch: string
total: number
platforms: Record<string, number>
updatedAt: Date
}
const STATS_COLLECTION = 'stats'
const STATS_DATABASE = 'matches'
/**
* Initialize stats document for a patch if it doesn't exist.
* This should be called before processing matches for a new patch.
*/
async function initStats(client: MongoClient, patch: string): Promise<void> {
const database = client.db(STATS_DATABASE)
const collection = database.collection<StatsDocument>(STATS_COLLECTION)
await collection.updateOne(
{ patch },
{
$setOnInsert: {
patch,
total: 0,
platforms: {},
updatedAt: new Date()
}
},
{ upsert: true }
)
}
/**
* Increment match count for a specific patch and platform.
* This should be called each time a new match is saved.
*/
async function incrementMatchCount(
client: MongoClient,
patch: string,
platform: string
): Promise<void> {
const database = client.db(STATS_DATABASE)
const collection = database.collection<StatsDocument>(STATS_COLLECTION)
await collection.updateOne(
{ patch },
{
$inc: { total: 1, [`platforms.${platform}`]: 1 },
$set: { updatedAt: new Date() }
},
{ upsert: true }
)
}
/**
* Get stats for a specific patch.
* Returns null if no stats exist for the patch.
*/
async function getStats(client: MongoClient, patch: string): Promise<StatsDocument | null> {
const database = client.db(STATS_DATABASE)
const collection = database.collection<StatsDocument>(STATS_COLLECTION)
return await collection.findOne({ patch })
}
/**
* Get stats for all patches, sorted from latest to oldest.
*/
async function getAllStats(client: MongoClient): Promise<StatsDocument[]> {
const database = client.db(STATS_DATABASE)
const collection = database.collection<StatsDocument>(STATS_COLLECTION)
const stats = await collection.find({}).toArray()
// Sort patches from latest to oldest
return stats.sort((a, b) => {
const [aMajor, aMinor] = a.patch.split('.').map(Number)
const [bMajor, bMinor] = b.patch.split('.').map(Number)
if (aMajor !== bMajor) return bMajor - aMajor
return bMinor - aMinor
})
}
/**
* Delete stats for patches that are being cleaned up.
* This should be called when old patch collections are dropped.
*/
async function deleteStats(client: MongoClient, patches: string[]): Promise<void> {
if (patches.length === 0) return
const database = client.db(STATS_DATABASE)
const collection = database.collection<StatsDocument>(STATS_COLLECTION)
await collection.deleteMany({ patch: { $in: patches } })
console.log(`Stats: Deleted stats for patches: ${patches.join(', ')}`)
}
/**
* Recalculate stats from existing match collections.
* This is useful for migration or fixing inconsistent stats.
*/
async function recalculateStats(
client: MongoClient,
patch: string,
platforms: string[]
): Promise<void> {
const database = client.db(STATS_DATABASE)
let total = 0
const platformCounts: Record<string, number> = {}
for (const platform of platforms) {
const collectionName = `${patch}_${platform}`
const collection = database.collection(collectionName)
const count = await collection.countDocuments()
platformCounts[platform] = count
total += count
}
const statsCollection = database.collection<StatsDocument>(STATS_COLLECTION)
await statsCollection.updateOne(
{ patch },
{
$set: {
patch,
total,
platforms: platformCounts,
updatedAt: new Date()
}
},
{ upsert: true }
)
console.log(`Stats: Recalculated stats for patch ${patch}: ${total} total matches`)
}
export default {
initStats,
incrementMatchCount,
getStats,
getAllStats,
deleteStats,
recalculateStats
}
export type { StatsDocument }