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
Deploy Iddaai Backend / build-and-deploy (push) Successful in 39s
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user