Какие поля должны быть в таблице для агрегации звонков по времени и абонентам
Что проверяют
Умеете ли вы проектировать схему от запросов, а не от списка «всё, что известно о звонке». Следом обычно просят написать запрос «сумма минут по абоненту по часам» и спрашивают, что станет с таблицей на сотнях миллионов строк.
Как отвечать
Сначала уточните, какие отчёты нужны: по часам или дням, по вызывающему или по обеим сторонам, считаются ли деньги. От этого зависит набор полей. Базовый вариант таблицы сырых звонков:
Id:bigint, суррогатный ключ;CallerNumberиCalleeNumber: номера в формате E.164, строкой, а не числом, иначе потеряется+и ведущие нули;StartedAt: момент начала в UTC, в C# этоDateTimeOffsetилиDateTimeсDateTimeKind.Utc;DurationSec: целое число секунд. Время окончания вычисляется, а хранить обе величины значит однажды получить расхождение;DirectionиStatus: входящий или исходящий, отвечен, пропущен, занято. В EF Core этоenum, сохранённый какsmallint;Cost, если есть тарификация:decimal, никогда неdouble.
Под агрегацию нужен составной индекс (CallerNumber, StartedAt): он закрывает и фильтр по абоненту, и диапазон по времени. На больших объёмах таблицу партиционируют по дате, а для отчётов держат отдельную свёртку (CallerNumber, HourBucket, CallsCount, TotalSec), которую пересчитывает фоновый BackgroundService или материализованное представление. В коде агрегат пишется через LINQ GroupBy по абоненту и часу начала; стоит убедиться по логу SQL, что EF Core перевёл группировку в запрос, а не потянул строки в память.
Где спотыкаются
Хранят время в локальной зоне сервера. Переход на летнее время или второй регион, и почасовая агрегация начинает врать.
Не думают о звонке, который пересёк границу часа или суток. Заранее решите, куда он относится: к интервалу начала или с разбиением длительности между интервалами.
Складывают полезные атрибуты в JSON-поле Metadata, а потом группируют по ним. Индексировать такое неудобно, и отчёт превращается в полный скан.