perf: replace Prisma relation queries with raw SQL for getExistingMatchIds and getMissingScopes - fixes Pi hang
Deploy Iddaai Backend / build-and-deploy (push) Successful in 39s

This commit is contained in:
2026-04-26 17:07:19 +03:00
parent bc461429f6
commit 691c52f610
@@ -853,49 +853,34 @@ export class FeederPersistenceService {
}
async getExistingMatchIds(matchIds: string[]): Promise<string[]> {
const matches = await this.prisma.match.findMany({
where: {
id: { in: matchIds },
oddCategories: { some: {} },
OR: [
{
sport: "football",
footballTeamStats: { some: {} },
playerParticipations: { some: { isStarting: true } },
},
{
sport: "basketball",
basketballTeamStats: { some: {} },
basketballPlayerStats: { some: {} },
},
],
},
select: { id: true, sport: true },
});
if (matchIds.length === 0) return [];
const footballIds = matches
.filter((m) => m.sport === "football")
.map((m) => m.id);
const completeFootballIds = new Set<string>();
// Use raw SQL for performance — Prisma's { some: {} } relation filters
// generate heavy correlated subqueries that hang on Raspberry Pi with
// large tables (15M+ odd_selections, 3M+ participations).
const result = await this.prisma.$queryRawUnsafe<
Array<{ id: string }>
>(
`
SELECT m.id
FROM matches m
WHERE m.id = ANY($1::text[])
AND EXISTS (SELECT 1 FROM odd_categories oc WHERE oc.match_id = m.id)
AND (
(m.sport = 'football'
AND EXISTS (SELECT 1 FROM football_team_stats fts WHERE fts.match_id = m.id)
AND (SELECT count(*) FROM match_player_participation mpp
WHERE mpp.match_id = m.id AND mpp.is_starting = true) >= 18)
OR
(m.sport = 'basketball'
AND EXISTS (SELECT 1 FROM basketball_team_stats bts WHERE bts.match_id = m.id)
AND EXISTS (SELECT 1 FROM basketball_player_stats bps WHERE bps.match_id = m.id))
)
`,
matchIds,
);
if (footballIds.length > 0) {
const starterCounts = await this.prisma.matchPlayerParticipation.groupBy({
by: ["matchId"],
where: {
matchId: { in: footballIds },
isStarting: true,
},
_count: { _all: true },
});
for (const row of starterCounts) {
if (row._count._all >= 18) completeFootballIds.add(row.matchId);
}
}
return matches
.filter((m) => m.sport !== "football" || completeFootballIds.has(m.id))
.map((m) => m.id);
return result.map((r) => r.id);
}
/**
@@ -909,38 +894,45 @@ export class FeederPersistenceService {
const result = new Map<string, string[]>();
if (matchIds.length === 0) return result;
const matches = await this.prisma.match.findMany({
where: {
id: { in: matchIds },
state: "Ended",
},
select: {
id: true,
sport: true,
_count: {
select: {
playerParticipations: true,
footballTeamStats: true,
basketballTeamStats: true,
basketballPlayerStats: true,
oddCategories: true,
},
},
},
});
// Use raw SQL for performance on Raspberry Pi.
// Note: state is 'postGame' in DB, not 'Ended'.
const rows = await this.prisma.$queryRawUnsafe<
Array<{
id: string;
sport: string;
fts_count: bigint;
pp_count: bigint;
bts_count: bigint;
bps_count: bigint;
oc_count: bigint;
}>
>(
`
SELECT m.id, m.sport::text,
(SELECT count(*) FROM football_team_stats fts WHERE fts.match_id = m.id) as fts_count,
(SELECT count(*) FROM match_player_participation mpp WHERE mpp.match_id = m.id) as pp_count,
(SELECT count(*) FROM basketball_team_stats bts WHERE bts.match_id = m.id) as bts_count,
(SELECT count(*) FROM basketball_player_stats bps WHERE bps.match_id = m.id) as bps_count,
(SELECT count(*) FROM odd_categories oc WHERE oc.match_id = m.id) as oc_count
FROM matches m
WHERE m.id = ANY($1::text[])
AND m.state = 'postGame'
`,
matchIds,
);
for (const m of matches) {
for (const m of rows) {
const missing: string[] = [];
if (m.sport === "football") {
if (m._count.footballTeamStats === 0) missing.push("stats");
if (m._count.playerParticipations < 18) missing.push("lineups");
if (Number(m.fts_count) === 0) missing.push("stats");
if (Number(m.pp_count) < 18) missing.push("lineups");
} else if (m.sport === "basketball") {
if (m._count.basketballTeamStats === 0) missing.push("stats");
if (m._count.basketballPlayerStats === 0) missing.push("lineups");
if (Number(m.bts_count) === 0) missing.push("stats");
if (Number(m.bps_count) === 0) missing.push("lineups");
}
if (m._count.oddCategories === 0) missing.push("odds");
if (Number(m.oc_count) === 0) missing.push("odds");
if (missing.length > 0) {
result.set(m.id, missing);