Как считать счетчик звонков пакетно, разбивая данные на части для обработки
Что проверяют
Умеете ли вы агрегировать данные, которые не помещаются в память, и собрать итог из частичных результатов. Следом обычно спрашивают, как распараллелить обработку пакетов и что будет, если процесс упадёт посередине.
Как отвечать
Ключевая мысль: счётчик звонков аддитивен. Посчитали по каждому пакету, сложили по ключам и получили то же, что на всех данных сразу. Поэтому пакеты обрабатываются независимо, в любом порядке и параллельно.
Каркас решения:
- чтение потоком: данные не грузятся целиком в
List<Call>. Из базы читаете черезIAsyncEnumerable<T>(в EF Core этоAsAsyncEnumerable(), в Dapper небуферизованный запрос), из файла построчно; - нарезка:
Enumerable.Chunk(size)из .NET 6 заменяет самописный цикл соSkip/Take. Из базы пакеты берут по ключу,WHERE Id > @lastId ORDER BY Id, а не черезOFFSET, который с каждым пакетом дорожает; - слияние: на пакет свой
Dictionary<string, int>, затем сложение в общий.CollectionsMarshal.GetValueRefOrAddDefaultделает это без двойного поиска по ключу; - параллельность:
Parallel.ForEachAsyncили конвейер наSystem.Threading.Channelsс ограниченной ёмкостью. Каждый поток считает в свой словарь, слияние в конце.ConcurrentDictionary.AddOrUpdateтоже корректен, но на горячих ключах конкуренция съедает выигрыш; - восстановление: после пакета сохраняете последний
Idвместе с промежуточными счётчиками, чтобы после падения не начинать сначала и не посчитать звонки дважды.
Если данные уже в базе, скажите, что GROUP BY Caller на стороне СУБД почти всегда быстрее обработки в приложении, а пакетный код нужен для файлов, потоков событий и нескольких источников.
Где спотыкаются
Считают словарь по каждому пакету и останавливаются. Получается список частичных счётчиков, а не ответ на вопрос.
Пишут в один обычный Dictionary из нескольких потоков. Он не потокобезопасен: можно потерять инкременты и даже испортить его внутреннюю структуру.
Загружают все звонки в память и только потом режут на пакеты. Пиковое потребление памяти остаётся тем же, и пакетная обработка ничего не экономит.