Как разбивать данные по файлам для обработки на одной машине
Что проверяют
Умеете ли вы обработать данные, которые не помещаются в память, и понимаете ли, где на одной машине узкое место: диск, память или процессор. Следом обычно спрашивают, как не разрезать запись пополам и сколько частей обрабатывать параллельно.
Как отвечать
Сначала выясните, нужно ли вообще резать. Если обработка построчная и независимая, файл читается потоком через File.ReadLines или StreamReader, память остаётся постоянной, и деление на файлы ничего не даёт.
Резать имеет смысл в трёх случаях:
- параллельная обработка: файл делится на части по границам записей. Берёте смещение
size / N, переходите к нему и дочитываете до ближайшего\n. В UTF-8 байт перевода строки не встречается внутри многобайтового символа, так что граница безопасна; - агрегация по ключу: записи раскладываются по файлам по
hash(key) % N. Все записи одного ключа попадают в один файл, и каждый файл агрегируется в памяти отдельно; - внешняя сортировка: читаете куски, которые помещаются в память, сортируете, пишете во временные файлы, затем сливаете их k-путевым слиянием через
PriorityQueue<TElement, TPriority>.
Размер части считают от памяти: одна часть с запасом влезает в ОЗУ, частей примерно столько, сколько обработчиков. Для параллелизма есть Parallel.ForEachAsync с MaxDegreeOfParallelism или конвейер на System.Threading.Channels, где чтение, обработка и запись идут отдельными стадиями. Чтению помогают буфер FileStream побольше и FileOptions.SequentialScan.
Результат пишут во временный файл и переименовывают через File.Move, чтобы упавший процесс не оставил половину файла.
Где спотыкаются
Режут по фиксированному числу байт. Строка или JSON-объект разрываются пополам, а если записать весь буфер после последнего неполного Read, в конец файла попадает мусор от предыдущей итерации.
Читают файл целиком через File.ReadAllLines или ReadAllText. На гигабайтах это OutOfMemoryException или долгие паузы сборщика мусора.
Ставят параллелизм по числу ядер, не глядя на диск. На HDD десяток потоков, читающих разные места файла, работает медленнее одного последовательного чтения, а тысячи мелких файлов упираются в файловую систему.