
Введение
Привет! Меня зовут Егор Лукьянов, я старший аналитик данных в Ozon Tech. В своей работе я часто сталкиваюсь с проблемой масштабируемости в pandas. Код, который быстро работает на гигабайте данных, начинает невыносимо тормозить на десяти. Уверен, эта боль знакома многим.
Сейчас есть быстрые альтернативы, например, Polars. Я сам пробовал переводить на него свои проекты. Скорость действительно впечатляет, но как в анекдоте есть нюанс: приходится переписывать чуть ли не весь код и привыкать к новому синтаксису. А это большая работа, на которую не всегда есть время.
И вот здесь я наткнулся на FireDucks — библиотеку, которая обещает решить эту проблему, просто заменив одну строку импорта. Звучало слишком хорошо, чтобы быть правдой. После опыта с Polars я был уверен, что где-то должен быть подвох.
Я решил проверить FireDucks на нескольких типичных задачах. В этой статье я хочу без лишнего хайпа поделиться тем, что у меня получилось. Мы посмотрим на реальные примеры кода, сравним скорость и разберёмся, где эта библиотека действительно хороша, а где могут быть проблемы.
Место FireDucks в мире инструментов для данных
Чтобы понять, зачем вообще нужен FireDucks, давайте быстро посмотрим на другие инструменты для работы с данными в Python.
Долгое время главным инструментом был pandas. Он удобный, у него понятный синтаксис и огромное сообщество. Но у него есть проблема. Он создавался давно, и когда данных становится действительно много (десятки миллионов строк и гигабайты на диске), он начинает работать медленно.
Из-за этого появились альтернативы:
PySpark позволил обрабатывать огромные наборы данных, распределяя вычисления на несколько машин.
Dask предложил похожий на pandas синтаксис, но с возможностью параллельных вычислений.
Polars сделал ставку на максимальную скорость на одной машине, используя Rust.
Все эти инструменты быстрее pandas. Но у них есть общий недостаток. Чтобы их использовать, нужно переписывать старый код. Если у вас накопились сотни скриптов на pandas, переход на что-то новое становится большой головной болью.
Именно эту проблему пытается решить FireDucks.
Он занимает особое место. Это не просто ещё одна библиотека, а скорее переходник. Он соединяет привычный вам код на pandas с более быстрым движком под капотом. Вы пишете код как обычно, а FireDucks сам транслирует его в более производительные операции. Это другой подход: не вы адаптируете код под новый API, а быстрый движок адаптируется под API pandas.
Как это работает под капотом?
В основе этой библиотеки лежит простая идея: разработчики не должны переучиваться, чтобы их код работал быстрее.
Главная цель библиотеки — быть drop-in заменой pandas. Это значит, что вы можете заменить pandas на FireDucks почти без изменений в коде. На практике достаточно заменить одну строчку импорта:
# Было import pandas as pd
# Стало import fireducks.pandas as pd
Как это возможно? Секрет в том, что FireDucks не выполняет ваши команды сразу. Он работает как умный компилятор для данных. Сначала он смотрит на всю цепочку ваших команд (фильтрация, группировка, агрегация), строит оптимальный план выполнения, и только потом запускает вычисления.
В обычном pandas каждая такая операция выполняется по шагам и часто создаёт промежуточные копии данных. Это лишняя работа и лишняя память. FireDucks видит всю цепочку целиком и выполняет её за один проход, без ненужных копий. К тому же он умеет распараллеливать вычисления и использовать все ядра вашего процессора.
Важно понимать, что FireDucks – это не новый pandas, написанный с нуля. Это умная прослойка, которая перехватывает ваши привычные команды и отправляет их на выполнение в более быстрый движок.
Моя тестовая среда и установка
Все тесты я проводил на следующей конфигурации:
Окружение:
Jupyterhubв кластереkubernetes,Python 3.10.12.CPU: 12 vcpu.
RAM: 120GB.
Библиотеки:
pandas==2.3.3,fireducks==1.4.4.
Установить FireDucks можно стандартным способом через pip. Обратите внимание, что он устанавливается как отдельный пакет, а используется через import fireducks.pandas.
pip install fireducks
Проверяем FireDucks на реальной задаче
Хватит теории. Давайте посмотрим, как FireDucks справляется с задачами, которые аналитики делают каждый день.
Мы разберём типичный сценарий. У нас есть два файла:
user_actions.csv— лог действий пользователей;user_info.csv— информация о пользователях.
Наша задача — посчитать средний чек и общую сумму покупок для пользователей из разных городов, но только для тех, кто зарегистрировался после определённой даты. Это классическая задача, которая включает в себя очистку, объединение таблиц, фильтрацию и агрегацию.
Шаг 0: Подготовка данных
Чтобы вы могли повторить этот эксперимент, вот код для генерации тестовых данных. Он создаст два файла: user_actions.csv (10 млн записей) и user_info.csv (100 тыс. записей).
# Код для генерации данных. Запустите его один раз. import pandas as pd import numpy as np # Генерируем действия пользователей num_actions = 10_000_000 actions_df = pd.DataFrame({ 'user_id': np.random.randint(1, 100_001, size=num_actions), 'product_price': np.random.uniform(10, 500, size=num_actions).round(2), 'quantity': np.random.randint(1, 5, size=num_actions) }) actions_df.loc[actions_df.sample(frac=0.01).index, 'product_price'] = np.nan actions_df.to_csv('user_actions.csv', index=False) # Генерируем информацию о пользователях num_users = 100_000 users_df = pd.DataFrame({ 'user_id': range(1, num_users + 1), 'city': np.random.choice(['Moscow', 'St. Petersburg', 'Kazan', 'Novosibirsk', 'Yekaterinburg'], size=num_users), 'registration_date': pd.to_datetime(np.random.randint(pd.to_datetime('2022-01-01').value, pd.to_datetime('2026-02-01').value, size=num_users)) }) users_df.to_csv('user_info.csv', index=False)
Шаг 1: Бенчмарки
Решение на pandas
Теперь напишем скрипт, который решает нашу задачу. %%time в Jupyter Notebook покажет, сколько времени займёт выполнение ячейки.
%%time import pandas as pd actions = pd.read_csv('user_actions.csv') users = pd.read_csv('user_info.csv', parse_dates=['registration_date']) actions_cleaned = actions.dropna(subset=['product_price']) actions_cleaned['total_price'] = actions_cleaned['product_price'] * actions_cleaned['quantity'] full_data = pd.merge(actions_cleaned, users, on='user_id') report = ( full_data[full_data['registration_date'] >= '2023-01-01'] .groupby('city') .agg( avg_check=('total_price', 'mean'), total_revenue=('total_price', 'sum') ) .round(2) .sort_values('total_revenue', ascending=False) )
То же самое на FireDucks
А теперь делаем то же самое, но с FireDucks. Код остаётся абсолютно таким же, меняется только одна строка импорта.
%%time import fireducks.pandas as pd # Единственное изменение actions = pd.read_csv('user_actions.csv') users = pd.read_csv('user_info.csv', parse_dates=['registration_date']) actions_cleaned = actions.dropna(subset=['product_price']) actions_cleaned['total_price'] = actions_cleaned['product_price'] * actions_cleaned['quantity'] full_data = pd.merge(actions_cleaned, users, on='user_id') report = ( full_data[full_data['registration_date'] >= '2023-01-01'] .groupby('city') .agg( avg_check=('total_price', 'mean'), total_revenue=('total_price', 'sum') ) .round(2) .sort_values('total_revenue', ascending=False) )
Результаты
Библиотека | CPU (user) | CPU (sys) | CPU (total) | Wall‑time | Speed‑up |
|---|---|---|---|---|---|
Pandas | 3.69 s | 1.94 s | 5.63 s | 5.63 s | 1× (база) |
FireDucks | 0.143 s | 0.0958 s | 0.239 s | 0.230 s | ≈ 24× |
Этот пример показывает, что на типичных аналитических задачах, где есть несколько этапов обработки, FireDucks даёт реальный и очень заметный выигрыш.
Ограничения и подводные камни
FireDucks — не серебряная пуля. У него есть свои ограничения, и о них нужно знать. Я сам наткнулся на несколько проблем, когда пробовал его в реальных задачах. Вот главные из них.
1. Метод .apply() — главная головная боль
Самая большая проблема (лично для меня) это .apply(). В pandas мы часто используем его для применения своих сложных функций. Это гибко, но медленно. FireDucks пытается оптимизировать apply, но если внутри функции сложная логика, он просто переключится на медленный режим pandas. В этом случае вы не получите ускорения, а столкнётесь с падением производительности или ошибкой выполнения, если ваша функция несовместима с внутренним движком FireDucks.
2. Параллельная обработка может убить ваш процесс
Это самый неочевидный, но важный момент: pandas — однопоточный. FireDucks пытается запустить всё параллельно, используя все ядра процессора. Если каждый такой параллельный поток попытается выделить много памяти одновременно, вы получите мгновенный пик потребления. Я столкнулся с этим на практике: код, который стабильно работал на pandas, падал с ошибкой нехватки памяти (Kernel Died или SIGKILL) на FireDucks, потому что тот пытался обработать все куски данных одновременно.

Торговое предложение. Я предлагаю, ты получаешь.
Избегайте паттернов, где вы создаёте много больших объектов в цикле. Старайтесь выполнять операции над одним большим DataFrame.
3. Не все методы одинаково оптимизированы
FireDucks отлично справляется с базовыми операциями: filter, groupby, merge. Но чем сложнее и реже метод (rolling,resample, работа сMultiIndex), тем выше шанс, что он не оптимизирован. Прежде чем переводить критически важный код, обязательно проверьте, что результаты совпадают.
Где FireDucks полезен больше всего?
Есть несколько сценариев, где замена импорта может принести реальную пользу:
Ускорить интерактивный анализ. Когда вы работаете в Jupyter, и ячейка с
groupbyвыполняется минуту, это сбивает с мысли.FireDucksсокращает это время до секунд, позволяя успевать проверить не одну, а пять гипотез за то же время.
«Починить» тяжёлые ETL-пайплайны. У многих есть скрипты на
pandas, которые гоняются по расписанию и со временем начинают тормозить или падать из-за нехватки памяти. Простая замена импорта наFireDucksможет «оживить» такой процесс, сократив время выполнения и потребление памяти.
Ускорить подготовку данных для Machine Learning. Feature engineering — часто самая долгая часть в ML-проектах. Ускорив этот этап с помощью
FireDucks, вы можете быстрее пробовать новые гипотезы, создавать больше признаков и в итоге получать более качественные модели.
Q&A
После прочтения статьи у опытного аналитика, который годами работает с pandas, могут возникнуть резонные вопросы. Давайте попробуем на них ответить.
Вопрос 1: Звучит слишком хорошо. Просто поменят�� импорт, и всё ускорится в 5 раз? В чём подвох?
Ответ: Вы правы, подвох есть. Ускорение достигается на конкретном классе задач: длинные цепочки операций (merge, groupby, filter) над большими данными. Если ваш код состоит из одной операции или на 90% из сложных функций в .apply(), вы не увидите прироста. Компромисс такой: вы получаете огромное ускорение на «стандартных» ETL-операциях, но взамен должны более осознанно подходить к написанию кода.
Вопрос 2: Что за «быстрый движок под капотом»? Это обёртка над Dask или Ray?
Ответ: Нет, это собственный движок на C++, использующий принципы аналитических баз данных: ленивые вычисления и оптимизацию плана запроса. Он использует формат Apache Arrow для эффективной работы с памятью, что роднит его с Polars. Ключевое отличие в том, что вся эта мощь «спрятана» за полностью совместимым API pandas.
Вопрос 3: Не получится ли так, что простой код станет работать медленнее из-за накладных расходов вашего «умного компилятора»?
Ответ: Да, такое возможно. У FireDucks есть небольшие накладные расходы на анализ кода. Если вы выполняете одну простую векторизованную операцию, они могут «съесть» всё преимущество. Сила FireDucks проявляется именно в цепочках из нескольких операций.
Вопрос 4: Как мне безопасно переключиться обратно на обычный pandas для одной операции? Ведь любая конвертация убьёт всю выгоду в производительности.
Ответ: Вы правы, явная конвертация — это затратная операция. В FireDucks нет специального метода .to_pandas(), потому что его DataFrame и так совместим с большинством библиотек. Но если вам всё же нужен «чистый» pandas-объект, вы можете сделать это через стандартный конструктор:
pandas_df = pandas.DataFrame(fireducks_df)
Стратегия в том, чтобы выполнить 95% тяжёлой работы в FireDucks, и только в самом конце над уже уменьшенным DataFrame, выполнить одну операцию конвертации. Затраты на это несоизмеримо малы по сравнению с полученной выгодой.
Вопрос 5: Как мне это внедрять в продакшен? Заменить импорт во всём проекте и надеяться на лучшее?
Ответ: «Заменить и надеяться» — это худшая стратегия (проверено на себе). Безопасное внедрение должно быть постепенным. Начните с поиска самого медленного скрипта. Создайте его копию, замените импорт и напишите тесты для валидации результатов. Только после этого переносите изменения в продакшен. Такой подход «один скрипт за раз» позволяет получить выгоду с минимальными рисками.
Заключение
FireDucks это не очередная замена pandas. Это попытка решить дилемму: скорость против удобства. Он предлагает получить ускорение, не переписывая привычный код.
Конечно, это не универсальное лекарство. Как мы выяснили, если ваш код построен на сложных .apply() или использует специфические методы, чуда не произойдёт.
Так стоит ли пробовать? Я думаю, да.
Лучший способ понять, подходит ли вам FireDucks, это просто взять и проверить его на своей задаче. Возьмите свой самый медленный Jupyter Notebook. Замените import pandas as pd на import fireducks.pandas as pd и замерьте время.
В худшем случае вы потратите 15 минут. В лучшем — сэкономите часы рабочего времени в будущем.
Надеюсь, мой опыт был вам полезен. Если попробуете FireDucks, будет очень интересно почитать о ваших результатах в комментариях.
Полезные ссылки:
Официальная документация
FireDucks: https://fireducks-dev.github.io.Бенчмарки: https://fireducks-dev.github.io/db-benchmark.
Сравнения PySpark, Dask и Ray: https://domino.ai/blog/spark-dask-ray-choosing-the-right-framework.
Про движок на плюсах под капотом: https://fireducks-dev.github.io/files/20241003_PyConZA.pdf, страница 16.
Репозиторий на GitHub: https://github.com/Luckyenough64/fireducks-pandas-benchmarks (много разных тестов).
