Как реализовать агрегацию звонков по месяцам, неделям и годам с большими объемами данных
Что проверяют
Понимаете ли вы, что агрегацию на больших объёмах делает база, а не цикл в памяти, и умеете ли подготовить данные так, чтобы отчёт не сканировал всю таблицу каждый раз. Следом обычно спрашивают про неделю на стыке годов и часовые пояса.
Как отвечать
Первое правило: считать там, где лежат данные. В EF Core GroupBy(c => new { c.StartedAt.Year, c.StartedAt.Month }) с Count() и Sum(c => c.Duration) внутри Select транслируется в SQL GROUP BY, и из базы приходят десятки строк вместо миллионов. Какой SQL получился, покажет ToQueryString().
Второе: фильтр по периоду задавать диапазоном, StartedAt >= from && StartedAt < to. Такое условие использует индекс по дате, а WHERE YEAR(StartedAt) = 2025 нет. Для группировки есть date_trunc('month', started_at) в PostgreSQL и DATETRUNC в SQL Server 2022.
Дальше то, что отличает ответ про большие объёмы:
- предагрегация: таблица дневных итогов (количество звонков, сумма длительности), которую фоновая задача пересчитывает инкрементально. Месяцы, недели и годы собираются из дней, это сотни строк вместо миллиардов;
- материализованные представления: в PostgreSQL
REFRESH MATERIALIZED VIEW CONCURRENTLY(нужен уникальный индекс), в SQL Server индексированные представления; - партиционирование по месяцу и columnstore-индексы в SQL Server для аналитики;
- отдельное аналитическое хранилище: ClickHouse или TimescaleDB с непрерывными агрегатами, когда основная OLTP-база не справляется.
Если сырые строки всё же нужны в C#, их читают потоком через AsAsyncEnumerable(), а не через ToListAsync().
Где спотыкаются
Вызывают ToList() до GroupBy. Вся таблица звонков едет в память сервиса, и отчёт падает по памяти или таймауту на первом же крупном клиенте.
Игнорируют часовые пояса и ISO-недели. Даты хранят в UTC, а месяц для клиента начинается в его полночь. Первая ISO-неделя года может начаться в декабре, в .NET для этого есть ISOWeek.GetWeekOfYear и ISOWeek.GetYear.
Усредняют средние: средняя длительность за месяц не равна среднему дневных средних. В итоговых таблицах храните сумму и количество, а среднее считайте при выводе.