Большие CSV и Parquet: как читать данные без перегруза памяти
Если большой CSV целиком не помещается в память, увеличение размера сервера — не единственный выход. Разбираем, когда читать файл частями, когда перенести запрос в DuckDB и почему Parquet ускоряет повторные выборки, но не спасает от любой нагрузки.

Когда большой CSV читают целиком в pandas, память может закончиться раньше, чем появится результат: библиотеке нужно не только хранить данные, но и разобрать текстовые значения. Для Python 3.11, pandas 2.2.x, DuckDB 1.1.3 и PyArrow 18.1.0 есть три практичных пути: обрабатывать CSV порциями, выполнять запрос над файлами через DuckDB или перевести повторяемые выборки на Parquet. У каждого своя цена — от ограничений по типу задачи до времени на подготовку данных.
Сначала выясните, что именно не помещается
Размер файла на диске плохо предсказывает расход оперативной памяти. CSV хранит значения как текст, а после разбора в таблицу память занимают объекты, индексы и промежуточные результаты. Поэтому файл на несколько гигабайт может потребовать заметно больше памяти, чем кажется по его размеру. Универсального коэффициента пересчёта нет: он зависит от типов колонок и распределения значений.
Перед выбором подхода ответьте на три вопроса: нужен ли весь набор колонок, повторяется ли один и тот же запрос и можно ли обработать данные частями без глобальной сортировки или объединения. Если вы каждый день выбираете несколько колонок и фильтруете даты, Parquet часто оправдан. Если это разовая агрегация по CSV, конвертация может быть лишним этапом.
Термин: предикатное проталкивание — когда движок применяет условие фильтра как можно ближе к чтению данных. В Parquet это позволяет пропустить часть групп строк, если метаданные показывают, что нужных значений там нет. Это не означает, что любой фильтр гарантированно избавит от чтения всего файла.
Три подхода и их цена
| Подход | Когда подходит | Чем платите |
|---|---|---|
| pandas с chunksize | Последовательная обработка CSV, простая агрегация | Нельзя без дополнительной логики выполнить глобальную сортировку или объединение всех строк |
| DuckDB над файлами | Фильтры, группировки, соединения; результат удобнее описать SQL-запросом | Нужно следить за настройками среды и размером промежуточных результатов |
| Parquet с PyArrow или DuckDB | Повторные выборки по колонкам и условиям | Конвертация, управление схемой и множеством файлов |
1. Читайте CSV порциями, если задача действительно потоковая
Параметр chunksize в pandas задаёт число строк в одной порции, а не её размер в байтах. Выберите только нужные колонки и задайте типы явно: так pandas не будет хранить лишнее и угадывать типы по мере чтения. Этот вариант хорошо работает для независимых порций и сводных результатов, размер которых остаётся небольшим.
from collections import defaultdict
import pandas as pd
totals = defaultdict(int)
for chunk in pd.read_csv(
'events.csv',
usecols=['region', 'amount_cents'],
dtype={'region': 'string', 'amount_cents': 'Int64'},
chunksize=250_000,
):
partial = chunk.groupby('region')['amount_cents'].sum()
for region, amount in partial.items():
totals[region] += int(amount)
print(sorted(totals.items(), key=lambda item: item[1], reverse=True))Здесь строки с пустым значением region не попадут в группировку: стандартное поведение groupby исключает пропуски. Если их нужно учитывать, сначала задайте для пропусков отдельную категорию. Числа хранятся в минимальных денежных единицах и суммируются как целые — это надёжнее, чем складывать цены в float.
Начните с порции в 250 тысяч строк, затем замерьте максимальное потребление памяти и время обработки на типичном файле. Уменьшайте размер, если упираетесь в память; увеличивайте, если памяти достаточно и из-за большого числа маленьких порций растут накладные расходы. Само число не универсально.
2. Отдайте фильтрацию и агрегацию DuckDB
Если запрос удобнее выразить через SQL — например, отобрать период, сгруппировать строки и записать компактный результат, — попробуйте DuckDB. Он умеет читать Parquet напрямую и выполнять над файлами SQL-запросы. Следующий пример для DuckDB 1.1.3 читает все подходящие файлы и сохраняет сводку в Parquet. Папка warehouse/events должна существовать; в файлах должны быть колонки event_date, region и amount_cents.
import duckdb
connection = duckdb.connect()
connection.execute('''
COPY (
SELECT region, sum(amount_cents) AS total_cents
FROM read_parquet('warehouse/events/*.parquet')
WHERE event_date >= DATE '2025-01-01'
GROUP BY region
) TO 'summary.parquet'
(FORMAT PARQUET, COMPRESSION ZSTD)
''')Выходной файл создаётся в текущей рабочей папке. Если выгрузка сама по себе огромна, не превращайте результат запроса в один pandas DataFrame: это снова может потребовать памяти для всех строк сразу. Записывайте результат на диск или обрабатывайте его порциями. И проверяйте типы и смысл дат: строковое сравнение и сравнение дат — не одно и то же.
3. Переводите повторные выборки на Parquet
Parquet хранит данные по колонкам, а CSV — в текстовом виде по строкам. Поэтому запрос, которому нужны три колонки из широкого набора, может прочитать только их, а не разбирать весь CSV целиком. PyArrow и DuckDB поддерживают выбор колонок и фильтры при чтении Parquet.
Конвертация оправдана, если вы многократно обращаетесь к одним данным, строите отчёты или запускаете разные запросы по одним и тем же наборам. Для одноразовой задачи сначала сравните цену конвертации с ценой повторного чтения CSV. Сохраняйте исходный CSV, пока не проверили итог: количество строк, пропуски, типы и контрольные суммы агрегатов. Конвертер может иначе истолковать дату, пустую строку или число с разделителем.
Для больших выгрузок полезно делить данные на файлы так, чтобы один запрос не открывал тысячи крошечных объектов. Разбиение по дате может помочь, если запросы обычно ограничены периодом, но чрезмерное число разделов и мелких файлов добавляет накладные расходы. Сначала посмотрите на реальные фильтры в запросах — не выбирайте схему разбиения только по привычке.
Parquet ускоряет повторное чтение подходящих колонок, но не исправляет неудачную схему данных и не уменьшает результат, который вы всё равно собираете целиком в память.
Как выбрать подход и не получить новую проблему
- Разовая группировка CSV, итог небольшой: начните с pandas и
chunksize. Убедитесь, что накопительный результат — например, число групп — сам помещается в память. - Фильтр, несколько агрегаций или соединение: выполните запрос в DuckDB. Сравните время и пик памяти на типичной выборке, а не на нескольких первых строках.
- Те же данные читают регулярно: конвертируйте в Parquet и сравните полное время цикла — подготовку плюс последующие запросы — с прежним CSV-процессом.
- Нужен сложный Python-анализ по порциям: оставьте pandas, но определите заранее, как будут обрабатываться глобальные операции и пропуски.
Что чаще всего ломается
Явное описание типов уменьшает неоднозначность, но делает загрузку чувствительной к грязным строкам. Если в целочисленной колонке встретится текст, чтение завершится ошибкой вместо тихого угадывания. Проверьте значения на небольшой выборке и решите, считать ли некорректные строки ошибкой, пропуском или отдельным случаем.
Другой источник ошибок — несовпадающие схемы файлов. Если каждый месяц сохраняет колонку по-разному, общий запрос может завершиться ошибкой либо дать результат с неожиданными типами. Сверяйте схему после выгрузок и не рассчитывайте, что имя файла или раздела автоматически гарантирует одинаковое содержимое.
Наконец, обработка порциями не делает автоматически потоковыми сортировку, точное соединение больших таблиц и подсчёт множества уникальных значений. Такие операции могут потребовать хранения большого состояния. В DuckDB следите за местом на диске, объёмом временных данных и памятью процесса; в pandas проверяйте размер промежуточных объектов, а не только исходного фрагмента.
Что измерить дальше
Возьмите один репрезентативный файл и один реальный запрос. Запишите время от начала чтения до готового результата, пиковое потребление памяти, размер результата и число ошибок преобразования типов. Затем сравните три сценария: CSV целиком как контрольный вариант, CSV порциями и Parquet с теми же фильтрами. Не сравнивайте только скорость: если Parquet требует долгой конвертации, для разовой задачи он может проиграть.
После выбора подхода зафиксируйте схему колонок, правила обработки пропусков и способ проверки результата. Для практики по pandas сверяйтесь с документацией именно вашей версии: в примерах выше используется pandas 2.2.x. Для чтения Parquet через DuckDB проверяйте актуальный раздел документации и следите за количеством файлов, размером результата и пиковым расходом памяти в рабочем запуске.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: pandas 2.2: pandas.read_csv, DuckDB: чтение и запись Parquet
Частые вопросы
Как понять, какой chunksize поставить?
Parquet всегда быстрее CSV?
Можно ли обработать через pandas CSV, который не помещается в память?
Подойдут ли эти примеры для другой версии pandas или DuckDB?
Что проверить, если Parquet-запрос падает или возвращает не те суммы?
Какие подводные камни есть при запуске в проде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.