fix n+1 queries in comfortGenerator and deepDiveGenerator (#110)

both generators ran N sequential queries per artist/album.
rewritten with ROW_NUMBER() OVER (PARTITION BY ...) to get per-group
limits in a single round-trip, preserving existing semantics:
- comfortGenerator: up to 2 tracks per artist (was 20 queries → 1)
- deepDiveGenerator: up to 5 tracks per album (was 20 queries → 1)
This commit is contained in:
kami
2026-07-15 11:23:54 +04:00
parent 3bc9f2d303
commit f4af906e22
2 changed files with 82 additions and 54 deletions
+62 -54
View File
@@ -52,41 +52,46 @@ async function comfortGenerator(db: DbService, ctx: GeneratorContext): Promise<C
.sort((a, b) => b.value - a.value)
.slice(0, 20);
const candidates: Candidate[] = [];
if (topArtists.length === 0) return [];
for (const belief of topArtists) {
const res = await db.pgClient.query(
`SELECT t.id
const artistValueMap = new Map(topArtists.map(b => [b.entity_id, b.value]));
const artistIds = topArtists.map(b => b.entity_id);
const res = await db.pgClient.query(
`SELECT sub.id, sub.artist_id
FROM (
SELECT t.id, cf.object_id AS artist_id,
ROW_NUMBER() OVER (PARTITION BY cf.object_id ORDER BY cf.fused_value DESC) AS rn
FROM tracks t
JOIN claim_fusion cf ON cf.subject_type = 'track' AND cf.subject_id = t.id
AND cf.predicate IN ('credited_main_on', 'featured_on')
AND cf.object_type = 'artist' AND cf.object_id = $1
AND cf.object_type = 'artist'
AND cf.object_id = ANY($1::uuid[])
AND (cf.user_id = $2 OR cf.user_id = $3)
WHERE t.state = 'LIBRARY'
AND NOT (t.id = ANY($4::uuid[]))
ORDER BY cf.fused_value DESC
LIMIT 2`,
[belief.entity_id, OBJECTIVE_USER, ctx.userId, ctx.recentExclusions]
);
) sub
WHERE sub.rn <= 2
ORDER BY sub.artist_id, sub.rn`,
[artistIds, OBJECTIVE_USER, ctx.userId, ctx.recentExclusions]
);
for (const row of res.rows as { id: string }[]) {
candidates.push({
trackId: row.id,
generatorId: 'comfort',
explanation: [{
subjectType: 'artist',
subjectId: belief.entity_id,
predicate: 'credited_main_on',
objectType: 'track',
objectId: row.id,
fusedValue: belief.value,
}],
relevance: belief.value,
});
}
}
return candidates;
return (res.rows as { id: string; artist_id: string }[]).map(row => {
const value = artistValueMap.get(row.artist_id) ?? 0.5;
return {
trackId: row.id,
generatorId: 'comfort',
explanation: [{
subjectType: 'artist',
subjectId: row.artist_id,
predicate: 'credited_main_on',
objectType: 'track',
objectId: row.id,
fusedValue: value,
}],
relevance: value,
};
});
}
// ---------------------------------------------------------------------------
@@ -243,37 +248,40 @@ async function deepDiveGenerator(db: DbService, ctx: GeneratorContext): Promise<
[obsessedIds]
);
const candidates: Candidate[] = [];
const albumRows = albumRes.rows as { album_id: string; artist_id: string }[];
if (albumRows.length === 0) return [];
for (const album of albumRes.rows as { album_id: string; artist_id: string }[]) {
const trackRes = await db.pgClient.query(
`SELECT t.id
const albumIds = albumRows.map(a => a.album_id);
const albumArtistMap = new Map(albumRows.map(a => [a.album_id, a.artist_id]));
const trackRes = await db.pgClient.query(
`SELECT sub.id, sub.album_id
FROM (
SELECT t.id, t.album_id,
ROW_NUMBER() OVER (PARTITION BY t.album_id ORDER BY t.title ASC) AS rn
FROM tracks t
WHERE t.album_id = $1 AND t.state = 'LIBRARY'
WHERE t.album_id = ANY($1::uuid[])
AND t.state = 'LIBRARY'
AND NOT (t.id = ANY($2::uuid[]))
ORDER BY t.title ASC
LIMIT 5`,
[album.album_id, ctx.recentExclusions]
);
) sub
WHERE sub.rn <= 5
ORDER BY sub.album_id, sub.rn`,
[albumIds, ctx.recentExclusions]
);
for (const row of trackRes.rows as { id: string }[]) {
candidates.push({
trackId: row.id,
generatorId: 'deep-dive',
explanation: [{
subjectType: 'artist',
subjectId: album.artist_id,
predicate: 'credited_main_on',
objectType: 'track',
objectId: row.id,
fusedValue: 0.7,
}],
relevance: 0.7,
});
}
}
return candidates;
return (trackRes.rows as { id: string; album_id: string }[]).map(row => ({
trackId: row.id,
generatorId: 'deep-dive',
explanation: [{
subjectType: 'artist',
subjectId: albumArtistMap.get(row.album_id) ?? 'unknown',
predicate: 'credited_main_on',
objectType: 'track',
objectId: row.id,
fusedValue: 0.7,
}],
relevance: 0.7,
}));
}
// ---------------------------------------------------------------------------