perf(backend): индексы, кэш чартов, пропуск upsert, фикс N+1 обогащения
- track_plays(played_at,track_id,station_id) покрывающий + tracks(first_seen_at) WHERE pending частичный (применены CONCURRENTLY на проде + миграция idempotent) - ChartsService.getTopTracks: in-memory TTL-кэш 90с по (period,genre,limit) → детальная страница и параллельные запросы не пересчитывают тяжёлые агрегации - NowPlayingService.ingest: не пишем now_playing и не шлём сокет, если трек не изменился (было ~20k бесполезных upsert/час) - enrichNowPlaying: вместо N+1 (300 upsert/мин) — один batched findMany по normKey Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -92,19 +92,39 @@ export class EnrichmentService {
|
||||
const rows = await this.prisma.nowPlaying.findMany({
|
||||
select: { artist: true, song: true },
|
||||
});
|
||||
const todo: { id: string; artist: string; song: string; normKey: string }[] = [];
|
||||
// Треки уже созданы в ChartsService.recordPlay — не upsert'им построчно (был
|
||||
// N+1 на ~300 строк/мин), а читаем пачкой по normKey. Мусор/исключённые
|
||||
// станции трек не создавали → их и не обогащаем (это правильно).
|
||||
const normKeys = new Set<string>();
|
||||
for (const r of rows) {
|
||||
const artist = (r.artist ?? '').trim();
|
||||
const song = (r.song ?? '').trim();
|
||||
if (!artist || !song) continue;
|
||||
const normKey = this.buildNormKey(artist, song);
|
||||
const track = await this.prisma.track.upsert({
|
||||
where: { normKey },
|
||||
create: { normKey, artist, song },
|
||||
update: {},
|
||||
select: { id: true, coverUrl: true, enrichStatus: true },
|
||||
});
|
||||
if (!track.coverUrl) todo.push({ id: track.id, artist, song, normKey });
|
||||
if (artist && song) normKeys.add(this.buildNormKey(artist, song));
|
||||
}
|
||||
if (normKeys.size === 0) return;
|
||||
|
||||
const tracks = await this.prisma.track.findMany({
|
||||
where: { normKey: { in: [...normKeys] } },
|
||||
select: {
|
||||
id: true,
|
||||
artist: true,
|
||||
song: true,
|
||||
normKey: true,
|
||||
coverUrl: true,
|
||||
enrichStatus: true,
|
||||
},
|
||||
});
|
||||
|
||||
const todo: { id: string; artist: string; song: string; normKey: string }[] = [];
|
||||
for (const track of tracks) {
|
||||
if (!track.coverUrl) {
|
||||
todo.push({
|
||||
id: track.id,
|
||||
artist: track.artist,
|
||||
song: track.song,
|
||||
normKey: track.normKey,
|
||||
});
|
||||
}
|
||||
// полное обогащение (жанр) — в общую очередь, если ещё не сделано
|
||||
if (track.enrichStatus !== 'done') this.enqueue(track.id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user