Собесов

OBenner/data-engineering: Spark vs pandas, когда переходить

Кейсы и метрикиData EngineeringСредняяMiddle

Условие

Аналитик жалуется, что pandas-скрипт обрабатывает 50M строк по 4 часа и иногда падает с MemoryError. Стоит ли переходить на Spark? Когда переход оправдан, а когда — нет?

Решение

Подход

Сначала — оптимизировать pandas.

Перед прыжком на Spark проверьте, что pandas использован эффективно:

  1. Типы данных. int64 → int32, float64 → float32, object → category. Часто экономит 50-80% памяти.
  2. Чтение по чанкам: pd.read_csv(..., chunksize=1_000_000) + агрегация по чанкам.
  3. Векторизация. Избавиться от .apply и for, использовать groupby, merge, transform.
  4. Полярный путь: попробовать polars — часто 10× быстрее pandas на одной машине, поддерживает lazy.
  5. DuckDB: in-process OLAP с SQL. Прекрасно делает joins по multi-GB CSV/Parquet на ноутбуке.

После этих оптимизаций 50M строк часто становятся 30 минут на сильном ноутбуке — Spark не нужен.

Когда Spark действительно нужен.

  • Данные не помещаются на одну машину (сотни ГБ, ТБ).
  • Регулярные пайплайны на схожих объёмах — DAG, оркестрация, fault tolerance.
  • Распределённые joins больших таблиц.
  • ML на больших данных (Spark MLlib).
  • Streaming (Spark Structured Streaming).

Когда Spark — overkill.

  • Меньше 50-100 ГБ умещается в памяти ноды → DuckDB / Polars / Dask на одной мощной машине дешевле.
  • Один-разовая аналитика, а не повторяемый pipeline.
  • Маленькая команда без DevOps — поддержка Spark-кластера дорого.

Сравнение производительности на 50M строк.

Tool Время Память Сложность
pandas (наивно) 4 ч + crash 60 ГБ средняя
pandas (оптимизировано) 30 мин 8 ГБ средняя
polars 5-10 мин 4 ГБ низкая
DuckDB (SQL) 5 мин 4 ГБ низкая
Spark (local) 15 мин 8 ГБ высокая
Spark (кластер 8 нод) 1-2 мин распределённо очень высокая
# Если всё-таки Spark
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("analytics").getOrCreate()
df = spark.read.parquet("s3://bucket/data/")
agg = (df.groupBy("user_id")
         .agg({"amount": "sum"})
         .orderBy("sum(amount)", ascending=False))
agg.write.mode("overwrite").parquet("s3://bucket/agg/")

Подводные камни

  1. Spark «бесплатно ускоряет» — миф. Spark медленнее pandas на маленьких данных из-за overhead JVM, сериализации, shuffle.
  2. Driver memory. Часто рушится не executor, а driver, если делать .collect() на большом dataframe. Никогда не .collect() без проверки размера.
  3. Skewed joins. Один ключ с 90% данных = одна executor-таска делает всю работу. Решается salt-keys.
  4. Lazy evaluation Spark. Цепочка трансформаций ничего не делает; .show() / .write() запускает план. Без .cache() повторные .count() пересчитывают всё.
  5. Parquet вместо CSV. Хранение в parquet с partition + pushdown predicates даёт ×10 ускорение, независимо от движка.

Эталонный ответ

Сначала оптимизируйте pandas (типы, chunks, polars/DuckDB) — часто этого хватает. Spark оправдан, когда данные не помещаются на одну машину, нужен распределённый join, или есть продакшен-pipeline на десятках ГБ ежедневно. Для разовой аналитики на 50M строк DuckDB или polars обычно быстрее, проще и дешевле.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти