From c4c93cff026f2ef2dda56af2eb24c93906b7e428 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Sun, 26 Jul 2026 21:50:01 +0300 Subject: [PATCH] Enhance query performance by applying AsSplitQuery to multiple handlers and loaders for Channels and ScheduleTemplates, improving efficiency in data retrieval and reducing unnecessary data duplication in queries. --- .../Bumpers/AddBumperTextVariantCommandHandler.cs | 1 + .../Broadcast/Bumpers/BumperSpecLoader.cs | 1 + .../Bumpers/RemoveBumperTextVariantCommandHandler.cs | 1 + .../Broadcast/Bumpers/RenderBumperPreviewCommandHandler.cs | 1 + .../Bumpers/UpdateBumperTextVariantCommandHandler.cs | 1 + .../Broadcast/GetChannel/GetChannelQueryHandler.cs | 1 + .../Library/GetShow/GetShowQueryHandler.cs | 2 ++ .../Library/ListShows/ListShowsQueryHandler.cs | 3 +++ .../Programming/Planning/GridScheduleGenerator.cs | 7 +++++++ .../Templates/CopyTemplate/CopyTemplateCommandHandler.cs | 1 + .../GetTemplate/GetChannelTemplateQueryHandler.cs | 1 + .../Programming/Templates/Layers/LayerCommandHandlers.cs | 2 ++ .../Programming/Templates/SlotWriter.cs | 2 ++ .../Templates/Validate/ValidateTemplateQueryHandler.cs | 1 + 14 files changed, 25 insertions(+) diff --git a/backend/src/TeleWave.Application/Broadcast/Bumpers/AddBumperTextVariantCommandHandler.cs b/backend/src/TeleWave.Application/Broadcast/Bumpers/AddBumperTextVariantCommandHandler.cs index ddbdc46..3f90c9a 100644 --- a/backend/src/TeleWave.Application/Broadcast/Bumpers/AddBumperTextVariantCommandHandler.cs +++ b/backend/src/TeleWave.Application/Broadcast/Bumpers/AddBumperTextVariantCommandHandler.cs @@ -16,6 +16,7 @@ public sealed class AddBumperTextVariantCommandHandler(IAppDbContext dbContext) var channel = await dbContext .Channels.Include(c => c.BumperTemplates) .ThenInclude(t => t.Variants) + .AsSplitQuery() .FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken); if (channel is null) return Result.Failure(ChannelErrors.NotFound); diff --git a/backend/src/TeleWave.Application/Broadcast/Bumpers/BumperSpecLoader.cs b/backend/src/TeleWave.Application/Broadcast/Bumpers/BumperSpecLoader.cs index c52420a..0ce8159 100644 --- a/backend/src/TeleWave.Application/Broadcast/Bumpers/BumperSpecLoader.cs +++ b/backend/src/TeleWave.Application/Broadcast/Bumpers/BumperSpecLoader.cs @@ -42,6 +42,7 @@ public sealed class BumperSpecLoader( .Channels.AsNoTracking() .Include(c => c.BumperTemplates) .ThenInclude(t => t.Variants) + .AsSplitQuery() .FirstOrDefaultAsync(c => c.Id == cache.ChannelId, cancellationToken); var template = channel?.BumperTemplates.FirstOrDefault(t => t.Id == cache.TemplateId); var variant = template?.Variants.FirstOrDefault(v => v.Id == cache.VariantId); diff --git a/backend/src/TeleWave.Application/Broadcast/Bumpers/RemoveBumperTextVariantCommandHandler.cs b/backend/src/TeleWave.Application/Broadcast/Bumpers/RemoveBumperTextVariantCommandHandler.cs index 9c97360..191c9fd 100644 --- a/backend/src/TeleWave.Application/Broadcast/Bumpers/RemoveBumperTextVariantCommandHandler.cs +++ b/backend/src/TeleWave.Application/Broadcast/Bumpers/RemoveBumperTextVariantCommandHandler.cs @@ -16,6 +16,7 @@ public sealed class RemoveBumperTextVariantCommandHandler(IAppDbContext dbContex var channel = await dbContext .Channels.Include(c => c.BumperTemplates) .ThenInclude(t => t.Variants) + .AsSplitQuery() .FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken); if (channel is null) return Result.Failure(ChannelErrors.NotFound); diff --git a/backend/src/TeleWave.Application/Broadcast/Bumpers/RenderBumperPreviewCommandHandler.cs b/backend/src/TeleWave.Application/Broadcast/Bumpers/RenderBumperPreviewCommandHandler.cs index 9e96a91..430823e 100644 --- a/backend/src/TeleWave.Application/Broadcast/Bumpers/RenderBumperPreviewCommandHandler.cs +++ b/backend/src/TeleWave.Application/Broadcast/Bumpers/RenderBumperPreviewCommandHandler.cs @@ -33,6 +33,7 @@ public sealed class RenderBumperPreviewCommandHandler( .Channels.AsNoTracking() .Include(c => c.BumperTemplates) .ThenInclude(t => t.Variants) + .AsSplitQuery() .FirstOrDefaultAsync(c => c.Id == query.ChannelId, cancellationToken); if (channel is null) return Result.Failure(ChannelErrors.NotFound); diff --git a/backend/src/TeleWave.Application/Broadcast/Bumpers/UpdateBumperTextVariantCommandHandler.cs b/backend/src/TeleWave.Application/Broadcast/Bumpers/UpdateBumperTextVariantCommandHandler.cs index 55d1616..465febd 100644 --- a/backend/src/TeleWave.Application/Broadcast/Bumpers/UpdateBumperTextVariantCommandHandler.cs +++ b/backend/src/TeleWave.Application/Broadcast/Bumpers/UpdateBumperTextVariantCommandHandler.cs @@ -16,6 +16,7 @@ public sealed class UpdateBumperTextVariantCommandHandler(IAppDbContext dbContex var channel = await dbContext .Channels.Include(c => c.BumperTemplates) .ThenInclude(t => t.Variants) + .AsSplitQuery() .FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken); if (channel is null) return Result.Failure(ChannelErrors.NotFound); diff --git a/backend/src/TeleWave.Application/Broadcast/GetChannel/GetChannelQueryHandler.cs b/backend/src/TeleWave.Application/Broadcast/GetChannel/GetChannelQueryHandler.cs index 476f333..56a7a96 100644 --- a/backend/src/TeleWave.Application/Broadcast/GetChannel/GetChannelQueryHandler.cs +++ b/backend/src/TeleWave.Application/Broadcast/GetChannel/GetChannelQueryHandler.cs @@ -19,6 +19,7 @@ public sealed class GetChannelQueryHandler(IAppDbContext dbContext) .Channels.AsNoTracking() .Include(c => c.BumperTemplates) .ThenInclude(t => t.Variants) + .AsSplitQuery() .FirstOrDefaultAsync(c => c.Id == query.Id, cancellationToken); if (channel is null) return Result.Failure(ChannelErrors.NotFound); diff --git a/backend/src/TeleWave.Application/Library/GetShow/GetShowQueryHandler.cs b/backend/src/TeleWave.Application/Library/GetShow/GetShowQueryHandler.cs index 5165add..ee8261c 100644 --- a/backend/src/TeleWave.Application/Library/GetShow/GetShowQueryHandler.cs +++ b/backend/src/TeleWave.Application/Library/GetShow/GetShowQueryHandler.cs @@ -18,6 +18,8 @@ public sealed class GetShowQueryHandler(IAppDbContext dbContext) .Shows.AsNoTracking() .Include(s => s.Episodes) .Include(s => s.Genres) + // Сиблинги: серии × жанры перемножились бы в одном запросе (см. ListShowsQueryHandler). + .AsSplitQuery() .FirstOrDefaultAsync(s => s.Id == query.Id, cancellationToken); if (show is null) return Result.Failure(ShowErrors.NotFound); diff --git a/backend/src/TeleWave.Application/Library/ListShows/ListShowsQueryHandler.cs b/backend/src/TeleWave.Application/Library/ListShows/ListShowsQueryHandler.cs index 5c59a78..142421b 100644 --- a/backend/src/TeleWave.Application/Library/ListShows/ListShowsQueryHandler.cs +++ b/backend/src/TeleWave.Application/Library/ListShows/ListShowsQueryHandler.cs @@ -17,6 +17,9 @@ public sealed class ListShowsQueryHandler(IAppDbContext dbContext) .Shows.AsNoTracking() .Include(s => s.Episodes) .Include(s => s.Genres) + // Серии и жанры — сиблинги: одним запросом это честное перемножение (сериал на 200 серий + // с тремя жанрами даёт 600 строк вместо 203). Разделяем. + .AsSplitQuery() .Where(s => query.Interstitials ? s.Kind == ShowKind.Interstitial diff --git a/backend/src/TeleWave.Application/Programming/Planning/GridScheduleGenerator.cs b/backend/src/TeleWave.Application/Programming/Planning/GridScheduleGenerator.cs index ec05501..f705af0 100644 --- a/backend/src/TeleWave.Application/Programming/Planning/GridScheduleGenerator.cs +++ b/backend/src/TeleWave.Application/Programming/Planning/GridScheduleGenerator.cs @@ -60,6 +60,10 @@ public sealed class GridScheduleGenerator( var channel = await dbContext .Channels.Include(c => c.BumperTemplates) .ThenInclude(t => t.Variants) + // Вложенные коллекции тянем отдельными запросами: иначе колонки родителя (у шаблона — + // jsonb с правилами, у слоя — jsonb применимости) приезжают по копии на каждую строку + // листа. Тик планировщика повторяет эти два запроса на каждый канал. + .AsSplitQuery() .FirstOrDefaultAsync(c => c.Id == channelId, cancellationToken); if (channel is null || !channel.IsEnabled || channel.TemplateId is null) return new GenerationReport(0, [], ChannelSkipped: true); @@ -70,6 +74,7 @@ public sealed class GridScheduleGenerator( var template = await dbContext .ScheduleTemplates.Include(t => t.Layers) .ThenInclude(l => l.Slots) + .AsSplitQuery() .FirstOrDefaultAsync(t => t.Id == channel.TemplateId, cancellationToken); if (template is null) return new GenerationReport(0, [], ChannelSkipped: true); @@ -174,6 +179,7 @@ public sealed class GridScheduleGenerator( .Channels.AsNoTracking() .Include(c => c.BumperTemplates) .ThenInclude(t => t.Variants) + .AsSplitQuery() .FirstOrDefaultAsync(c => c.Id == channelId, cancellationToken); if (channel is null || channel.TemplateId is null) return null; @@ -182,6 +188,7 @@ public sealed class GridScheduleGenerator( .ScheduleTemplates.AsNoTracking() .Include(t => t.Layers) .ThenInclude(l => l.Slots) + .AsSplitQuery() .FirstOrDefaultAsync(t => t.Id == channel.TemplateId, cancellationToken); if (template is null) return null; diff --git a/backend/src/TeleWave.Application/Programming/Templates/CopyTemplate/CopyTemplateCommandHandler.cs b/backend/src/TeleWave.Application/Programming/Templates/CopyTemplate/CopyTemplateCommandHandler.cs index a8875ff..82d9721 100644 --- a/backend/src/TeleWave.Application/Programming/Templates/CopyTemplate/CopyTemplateCommandHandler.cs +++ b/backend/src/TeleWave.Application/Programming/Templates/CopyTemplate/CopyTemplateCommandHandler.cs @@ -25,6 +25,7 @@ public sealed class CopyTemplateCommandHandler(IAppDbContext dbContext) .ScheduleTemplates.AsNoTracking() .Include(t => t.Layers) .ThenInclude(l => l.Slots) + .AsSplitQuery() .FirstOrDefaultAsync(t => t.ChannelId == command.SourceChannelId, cancellationToken); if (source is null) return Result.Failure(ChannelErrors.TemplateNotFound); diff --git a/backend/src/TeleWave.Application/Programming/Templates/GetTemplate/GetChannelTemplateQueryHandler.cs b/backend/src/TeleWave.Application/Programming/Templates/GetTemplate/GetChannelTemplateQueryHandler.cs index 7ae1b50..5347303 100644 --- a/backend/src/TeleWave.Application/Programming/Templates/GetTemplate/GetChannelTemplateQueryHandler.cs +++ b/backend/src/TeleWave.Application/Programming/Templates/GetTemplate/GetChannelTemplateQueryHandler.cs @@ -24,6 +24,7 @@ public sealed class GetChannelTemplateQueryHandler(IAppDbContext dbContext) .ScheduleTemplates.AsNoTracking() .Include(t => t.Layers) .ThenInclude(l => l.Slots) + .AsSplitQuery() .FirstOrDefaultAsync(t => t.ChannelId == channel.Id, cancellationToken); if (template is null) return Result.Failure(ChannelErrors.TemplateNotFound); diff --git a/backend/src/TeleWave.Application/Programming/Templates/Layers/LayerCommandHandlers.cs b/backend/src/TeleWave.Application/Programming/Templates/Layers/LayerCommandHandlers.cs index cd59d7d..5977872 100644 --- a/backend/src/TeleWave.Application/Programming/Templates/Layers/LayerCommandHandlers.cs +++ b/backend/src/TeleWave.Application/Programming/Templates/Layers/LayerCommandHandlers.cs @@ -109,6 +109,7 @@ internal static class LayerLoader dbContext .ScheduleTemplates.Include(t => t.Layers) .ThenInclude(l => l.Slots) + .AsSplitQuery() .FirstOrDefaultAsync(t => t.Id == templateId, cancellationToken); public static Task ByLayerAsync( @@ -119,5 +120,6 @@ internal static class LayerLoader dbContext .ScheduleTemplates.Include(t => t.Layers) .ThenInclude(l => l.Slots) + .AsSplitQuery() .FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken); } diff --git a/backend/src/TeleWave.Application/Programming/Templates/SlotWriter.cs b/backend/src/TeleWave.Application/Programming/Templates/SlotWriter.cs index 731caac..dc7be6d 100644 --- a/backend/src/TeleWave.Application/Programming/Templates/SlotWriter.cs +++ b/backend/src/TeleWave.Application/Programming/Templates/SlotWriter.cs @@ -89,6 +89,7 @@ public sealed class SlotWriter(IAppDbContext dbContext) dbContext .ScheduleTemplates.Include(t => t.Layers) .ThenInclude(l => l.Slots) + .AsSplitQuery() .FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken); /// Загружает шаблон по слоту. @@ -99,6 +100,7 @@ public sealed class SlotWriter(IAppDbContext dbContext) dbContext .ScheduleTemplates.Include(t => t.Layers) .ThenInclude(l => l.Slots) + .AsSplitQuery() .FirstOrDefaultAsync( t => t.Layers.Any(l => l.Slots.Any(s => s.Id == slotId)), cancellationToken diff --git a/backend/src/TeleWave.Application/Programming/Templates/Validate/ValidateTemplateQueryHandler.cs b/backend/src/TeleWave.Application/Programming/Templates/Validate/ValidateTemplateQueryHandler.cs index 0f0d83d..c9c7091 100644 --- a/backend/src/TeleWave.Application/Programming/Templates/Validate/ValidateTemplateQueryHandler.cs +++ b/backend/src/TeleWave.Application/Programming/Templates/Validate/ValidateTemplateQueryHandler.cs @@ -28,6 +28,7 @@ public sealed class ValidateTemplateQueryHandler(IAppDbContext dbContext) .ScheduleTemplates.AsNoTracking() .Include(t => t.Layers) .ThenInclude(l => l.Slots) + .AsSplitQuery() .FirstOrDefaultAsync(t => t.ChannelId == channel.Id, cancellationToken); if (template is null) return Result.Failure>(ChannelErrors.TemplateNotFound);