Pandas отстаёт от Polars и DuckDB в десятки раз: бенчмарк на миллиарде строк

Pandas отстаёт от Polars и DuckDB в десятки раз: бенчмарк на миллиарде строк

Разработчик Эдди Аткинсон опубликовал в своём блоге eddie.codes пост по мотивам доклада, который он читал на Latency Conference (запись и слайды доклада он публикует отдельными ссылками, но дату и место самой конференции в тексте не называет). Центральный тезис: неэффективность Pandas, самой известной Python-библиотеки для работы с табличными данными, заставляет разработчиков переходить на распределённые системы вроде Spark, Databricks, Snowflake и Dask раньше, чем их рабочие нагрузки это реально оправдывают. По его словам, большинству нагрузок такие системы вообще никогда не понадобятся: это просто хорошо разрекламированные «серебряные пули».

Аткинсон описывает типичный путь: команды начинают с Excel, переходят на Pandas при объёмах в единицы гигабайт, нормально работают с ней вплоть до нескольких десятков гигабайт, а затем упираются в нехватку памяти, медленные вычисления и неудобный API, и в этот момент традиционно «выпускаются» на дорогие инструменты для по-настоящему больших данных. Но, по его наблюдению, между «обрывом Pandas» и точкой, где распределённые системы действительно необходимы, есть растущий разрыв, примерно на отметке 100 ГБ, который способны закрыть современные быстрые однохостовые инструменты. Он называет два: Polars, DataFrame-библиотеку на Rust с похожим на Pandas API, но с ленивым (отложенным) выполнением запросов и потоковой обработкой; и DuckDB, встраиваемую аналитическую СУБД с SQL-интерфейсом, по сути «SQLite для аналитики», которая умеет напрямую обращаться к объектам Python (например, к DataFrame) в памяти.

Чтобы показать, насколько редко компаниям реально требуются по-настоящему большие данные, автор ссылается на статью Amazon 2024 года «Why TPC is not enough: An analysis of the Amazon Redshift fleet» (в переводе, «Почему TPC недостаточно: анализ парка Amazon Redshift»), которая сравнивает телеметрию собственного облачного хранилища Amazon Redshift с шаблонами запросов из индустриальных тестов производительности. Взяв за основу собственные допущения, средний размер строки в таблице Redshift 1 КБ, кластер из 10 машин, каждая забирает данные из S3 со скоростью 8 ГБ/с, Аткинсон вычисляет по опубликованной Amazon статистике, что 94,68% таблиц в парке Redshift содержат менее 100 ГБ данных, а 86,9% запросов оперируют объёмом не более 80 ГБ. Он оговаривается, что даже если допущение о размере строки занижено вдесятеро (10 КБ вместо 1 КБ), порог всё равно останется в пределах 1 ТБ. Отдельно Аткинсон ссылается на более подробный разбор того же датасета от Джордана Тигани из MotherDuck, и сам же предупреждает, что MotherDuck продаёт хостинг DuckDB, так что к этому разбору стоит относиться со скепсисом.

Чтобы сравнить инструменты напрямую, Аткинсон взял популярный тест One Billion Row Challenge: посчитать минимум, среднее и максимум по столбцу измерений в CSV-файле из миллиарда строк с данными метеостанций, сгруппированных по станции. Лучшее принятое на конкурсе Java-решение выполняло эту задачу за 1,5 секунды. Аткинсон повторил тест на облачном сервере AWS m7a.8xlarge (32 ядра AMD, 128 ГБ ОЗУ, Debian 12), по характеристикам он подобран под конфигурацию оригинального теста на выделенном сервере Hetzner AX161, который сам автор арендовать не стал, объясняя это и ленью, и требованием Hetzner заранее наработать «репутацию» для аренды мощных серверов. Он признаёт, что замена «железа» на облачное могла немного повлиять на воспроизводимость результатов. По итогам 30 повторов после двух прогревочных запусков (приведены медианные значения) Pandas выполнила задачу за 4 минуты 28 секунд, задействовав медианный пик CPU 113,0% и медианный пик памяти (USS) 38,12 ГБ; Polars справилась за 5,04 секунды при пике CPU 3202,60% и памяти 18,02 ГБ; DuckDB, за 5,19 секунды при пике CPU 3174,64% и памяти всего 1,93 ГБ. Подкачка (swap) на этом тесте не потребовалась ни одному из инструментов. То есть и Polars, и DuckDB обошли Pandas больше чем в 50 раз по скорости, использовав примерно в 2 и в 19 раз меньше памяти соответственно, и почти сравнялись по времени с ручной Java-реализацией. Аткинсон называет результат DuckDB «безупречным», «феноменальная производительность при минимуме кода и без всякой настройки», а по поводу памяти Polars замечает, что она кажется ему великоватой, и предполагает, что не все вычисления в тесте были потоковыми.

Тот же тест он повторил на обычном ноутбуке для разработки, Framework 13 с Intel i5-1135G7, 8 ядрами и 16 ГБ ОЗУ. Здесь Pandas отработала 12 минут 15 секунд с пиком памяти 15,67 ГБ и медианным пиком подкачки 21,02 ГБ, то есть на машине с 16 ГБ ОЗУ Pandas заметно уходила в своп; Polars уложилась в 39 секунд с пиком памяти 15,22 ГБ и почти незаметной подкачкой (35,85 МБ); DuckDB, в 47 секунд, использовав всего 546,87 МБ памяти и вовсе без подкачки. Из этого Аткинсон выводит список того, что Polars и DuckDB дают «бесплатно» по сравнению с Pandas: автоматическую многопоточность (используют все ядра CPU без ручного управления потоками или процессами), потоковую обработку данных по частям вместо разовой загрузки всего набора в память, ленивое выполнение с оптимизацией плана запроса (например, предикатный пуш-даун, раннюю фильтрацию строк до агрегации) и способность сбрасывать промежуточные результаты на диск более эффективно, чем стандартный своп операционной системы. Отдельно он ссылается на внешние результаты теста TPC-H, но сразу оговаривает: эти замеры проводила компания Coiled, продающая хостинг Dask, прямого конкурента Polars и DuckDB за внимание рынка, и скепсис в их адрес тоже уместен.

Ещё одна тема поста, формат Apache Arrow, который, по словам Аткинсона, становится де-факто стандартом представления табличных данных в памяти; придумал его тот же Уэс Маккинни, который изначально создал Pandas. Pandas поддерживает Arrow начиная с версии 2.0 (апрель 2023 года), а Polars и DuckDB работают с ним нативно, это позволяет передавать DataFrame между тремя инструментами без копирования данных в памяти, то есть переключаться между ними почти «бесплатно». Оговорка: Pandas по умолчанию не создаёт DataFrame на основе Arrow, для этого нужно явно указать dtype_backend="pyarrow" при чтении данных. Эту связку Аткинсон иллюстрирует на датасете нью-йоркских такси (данные в формате Parquet, помесячно, поездки с 2009 года по настоящее время, около 3 ГБ), на нём он проверяет, стала ли оплата наличными реже во время «пандемии» (2019, 2022 годы), реализовав чтение и вычисление и на Pandas, и на DuckDB (версию на Polars он оставил «упражнением для читателя»). На том же ноутбуке он сравнил четыре комбинации: чтение и вычисление целиком на Pandas, 41,88 секунды; чтение через DuckDB с вычислением в Pandas, 28,39 секунды; чтение в Pandas с вычислением в DuckDB, 29,25 секунды; и чтение и вычисление целиком в DuckDB, 21,70 секунды, самый быстрый вариант. Он отмечает, что DuckDB полностью загружает все ядра процессора, почти не расходуя память, тогда как всюду, где в дело вступает Pandas, скорость падает, а потребление памяти растёт. Оплата наличными действительно снизилась за отслеживаемый период, но Аткинсон сам подчёркивает: корреляция, не причинность, и делать из этого содержательные выводы не стоит.

В разделе «Почему мне не стоит вам верить» Аткинсон сам перечисляет причины сомневаться в его выводах: он всего лишь один человек, который сам прогнал бенчмарки, весь код открыт, и читателю стоит перепроверить его самостоятельно; для команд, глубоко завязанных на экосистему Pandas, стоимость перехода может оказаться слишком высокой, впрочем, по его словам, планка «слишком высокой стоимости» заметно снизилась с тех пор, как он читал этот доклад; и «дайте Pandas время», библиотека тоже развивается, пусть и медленно, учитывая её центральное место в экосистеме, хотя, добавляет он, у Polars и DuckDB и без вопроса производительности более понятный API, а SQL, на котором работает DuckDB, куда более переносимый навык. Разбираясь, что лучше, Polars или DuckDB, он не даёт однозначного ответа: выбор зависит от задачи, опыта и личных предпочтений («дата-инженеры обычно любят SQL, разработчики ПО обычно любят Polars»), и предлагает попробовать оба. Единственная настоящая ошибка, по его итоговой формулировке, не глядя переходить на распределённую систему со всей её сложностью только потому, что Pandas работает медленно: вероятность того, что она реально понадобится в перспективе, он называет «довольно небольшой», хотя и не нулевой.

Ключевые факты

  • Тезис поста: неэффективность Pandas преждевременно толкает разработчиков на дорогие распределённые системы (Spark, Databricks, Snowflake, Dask); по расчётам автора на статистике парка Amazon Redshift (2024), 94,68% таблиц весят меньше 100 ГБ, а 86,9% запросов обрабатывают не больше 80 ГБ, то есть распределённые системы реально нужны редко.
  • На тесте One Billion Row Challenge (1 млрд строк) на облачном сервере AWS (32 ядра, 128 ГБ ОЗУ) Pandas отработала за 4 минуты 28 секунд против 5,04 секунды у Polars и 5,19 секунды у DuckDB, быстрее чем в 50 раз, при этом заняв в 2 и в 19 раз меньше пиковой памяти соответственно (38,12 ГБ у Pandas против 18,02 ГБ и 1,93 ГБ).
  • На обычном ноутбуке (8 ядер, 16 ГБ ОЗУ) тот же тест занял у Pandas 12 минут 15 секунд и увёл её в своп на 21,02 ГБ, тогда как Polars справилась за 39 секунд, а DuckDB, за 47 секунд, почти не тронув память (546,87 МБ).
  • На примере с ~3 ГБ данных нью-йоркских такси связка «чтение и вычисление целиком в DuckDB» заняла 21,70 секунды против 41,88 секунды у чистого Pandas; благодаря общей поддержке формата Apache Arrow инструменты можно комбинировать почти без потерь на копировании памяти (Pandas поддерживает Arrow с версии 2.0, но требует явно указать dtype_backend="pyarrow").
  • Автор сам перечисляет причины ему не доверять: методику и код можно проверить (всё открыто), но у него нет ни данных о разбросе по 30 повторам, ни заявленного конфликта интересов, в отличие от процитированных им внешних тестов MotherDuck и Coiled, чьи компании продают хостинг DuckDB и Dask соответственно; итоговый совет, не переходить на распределённые системы вслепую, поскольку реальная потребность в них возникает редко.

Почему это важно

Есть устоявшийся шаблон: команда доросла на Pandas до десятков гигабайт, начала упираться в память и скорость, и тут же «выпускается» на Spark, Databricks, Snowflake или Dask, хотя по-настоящему больших объёмов данных у неё, скорее всего, никогда не было и не будет. Аткинсон формулирует это резко уже в заголовке поста («Pandas должна вымереть»), не потому, что данные стали больше, а потому, что несовершенство самой библиотеки выталкивает людей на сложные и дорогие инструменты раньше времени. Аргумент подкреплён не только личным мнением: по расчётам на статистике Amazon о парке Redshift (2024 год), 94,68% таблиц весят меньше 100 ГБ, а 86,9% запросов укладываются в 80 ГБ, то есть подавляющее большинство аналитических нагрузок в реальности умещается в то, что автор называет Medium Data, для которых достаточно одной машины. Именно эту нишу, между тем, где Pandas начинает буксовать, и тем, где реально нужна распределённая система, по его данным закрывают Polars и DuckDB: в тесте на миллиард строк оба обошли Pandas больше чем в 50 раз по скорости.

Кому это важно

В первую очередь, тем, кто уже работает с Pandas на объёмах в десятки гигабайт и начинает упираться в нехватку памяти или медленные вычисления: пост прямо адресован тем, кто в этой точке готовится «выпускаться» на Spark, Databricks, Snowflake или Dask. Полезен он и тем, кто выбирает между Polars и DuckDB: Аткинсон честно признаёт, что универсального ответа нет, выбор зависит от задачи, опыта и личных предпочтений, «дата-инженеры обычно любят SQL» (то есть DuckDB), а «разработчики ПО обычно любят Polars». И отдельно, командам, уже глубоко завязанным на экосистему Pandas: автор прямо говорит, что для них стоимость перехода может быть неоправданно высокой, хотя, по его наблюдению, эта планка заметно снизилась с момента доклада.

Как это применить

Практический путь, который описывает Аткинсон, не требует переписывать всё сразу: и Polars, и DuckDB нативно поддерживают формат Apache Arrow, как и сам Pandas начиная с версии 2.0 (апрель 2023 года), а значит, DataFrame можно передавать между тремя инструментами без копирования данных в памяти. Единственная тонкость, Pandas не создаёт DataFrame на основе Arrow по умолчанию, нужно явно указать dtype_backend="pyarrow" при чтении. Дальше можно комбинировать точечно: в примере с данными нью-йоркских такси даже частичная замена, например, чтение файлов через DuckDB при вычислениях на Pandas, ускорила обработку с 41,88 до 28,39 секунды; полный переход на DuckDB довёл время до 21,70 секунды. Из готовых преимуществ, которые оба инструмента дают «бесплатно» по сравнению с Pandas: автоматическая многопоточность на все ядра CPU без ручного управления потоками, потоковая обработка данных по частям вместо загрузки всего набора в память, ленивое выполнение с оптимизацией плана запроса (например, ранняя фильтрация строк, предикатный пуш-даун) и более аккуратный сброс промежуточных результатов на диск при нехватке памяти, чем у обычного свопа операционной системы.

Можно ли доверять

Это личный блог-пост по мотивам доклада на профильной конференции (Latency Conference), а не рецензируемое исследование, но методика описана подробно и весь код открыт, так что выводы можно перепроверить самостоятельно: сам автор явно к этому призывает. Бенчмарки прогонялись отдельным скриптом, который запускал каждую библиотеку в свежем процессе Python и опрашивал память и CPU через psutil каждые 50 мс после двух прогревочных итераций, по тридцать повторов на каждый тест; в статье приведены только медианные значения, без разброса или доверительных интервалов по этим тридцати повторам. Автор сам называет причины сомневаться в себе: указывает, что стоимость перехода может быть неоправданной для команд, глубоко завязанных на Pandas, и честно предупреждает о конфликте интересов у двух внешних источников, на которые ссылается, у Джордана Тигани из MotherDuck (компания продаёт хостинг DuckDB) и у Coiled (продаёт хостинг Dask, конкурента Polars и DuckDB за внимание рынка). При этом собственную позицию и возможную выгоду от продвижения Polars и DuckDB он никак не раскрывает, в отличие от чужих конфликтов интересов, которые сам же называет. Расчёт про 94,68% и 86,9% построен на собственных допущениях автора о размере строки в Redshift (1 КБ) и параметрах кластера (10 машин по 8 ГБ/с), а не на цифрах, которые Amazon в своей статье называет напрямую; впрочем, автор проверяет чувствительность расчёта и показывает, что даже при вдесятеро большем размере строки (10 КБ) порог остаётся в пределах 1 ТБ.

Риски и подводные камни

Главный методологический пробел, отсутствие разброса результатов: приведены только медианы по тридцати повторам, без стандартного отклонения или диапазона, так что не видно, насколько стабильны результаты между запусками. Ключевая статистика о редкости по-настоящему больших данных (94,68% и 86,9%), это не цифры Amazon напрямую, а вывод из собственных допущений автора о размере строки и мощности кластера, которые могут не совпадать с реальностью конкретной компании. Пример с нью-йоркскими такси намеренно иллюстративный, а не содержательный: сам автор предупреждает, что снижение доли оплаты наличными за 2019, 2022 годы не доказывает причинно-следственной связи с пандемией, и просит не делать из этого выводов. Выбор между Polars и DuckDB пост не закрывает, по признанию автора, это вопрос задачи и личных предпочтений, а не однозначно лучшего инструмента. И главная практическая ловушка, на которую указывает сам Аткинсон: единственная настоящая ошибка, вслепую перейти на распределённую систему со всей её сложностью только из-за того, что Pandas работает медленно, хотя вероятность того, что распределённая система реально понадобится в долгосрочной перспективе, по его словам, «довольно небольшая», то есть не нулевая, для по-настоящему больших нагрузок Spark, Databricks, Snowflake или Dask всё равно остаются оправданным выбором.

«Я полагаю, что большинству рабочих нагрузок такие системы никогда не понадобятся, это просто хорошо разрекламированные «серебряные пули».»

— Эдди Аткинсон, автор поста в блоге eddie.codes