PySpark vs pandas на собеседовании Data Engineer

Проверь себя · 1/3разбор после ответа
Что будет результатом выражения arr + 10, если arr = np.array([1, 2, 3])?

Почему это спрашивают на собесе

pandas и PySpark решают одну задачу — обработку табличных данных, — но живут в разных мирах. pandas держит весь датафрейм в оперативке одной машины, PySpark распределяет его по кластеру. На собесе DE проверяют не то, знаете ли вы синтаксис (его подскажет документация), а понимаете ли, где проходит граница: когда данные перестают влезать в память и pandas превращается в тыкву.

Типичная формулировка вопроса: «У вас датасет 500 ГБ, обработать надо ежедневно — берёте pandas или Spark и почему?» Здесь интервьюер ждёт не название инструмента, а рассуждение про объём, инфраструктуру (есть ли кластер) и стоимость. Ответ «всегда Spark» так же плох, как «всегда pandas»: Spark на 100-мегабайтном файле — это overhead на запуск JVM и сериализацию, который убивает всю выгоду.

Эта статья закрывает базовый блок: чем инструменты различаются под капотом, где грабли и как аргументировать выбор.

Сравнение

pandas PySpark
Где считает Одна машина Кластер (распределённо)
Память Ограничена RAM ноды Распределена по нодам
Малые данные Быстро Overhead на запуск
Большие данные OOM или медленно Спроектирован под это
Зрелость API Очень зрелый DataFrame API, беднее по методам
Вычисления Немедленные (eager) Ленивые (lazy)
Кастомный Python Нативно, быстро Через UDF, медленнее

Главное различие — модель вычислений. pandas выполняет каждую операцию сразу (eager): написали df.groupby(...) — оно посчиталось. Spark строит план (lazy): операции копятся в граф, а реальный расчёт запускается только на «действии» вроде .count(), .collect() или .write(). Благодаря этому оптимизатор Catalyst успевает переставить фильтры, склеить проекции и не тащить лишние колонки. На собесе про ленивость спрашивают почти всегда — знать разницу transformation vs action обязательно.

Разница в API

Синтаксис похож, но не идентичен. Один и тот же group-by выглядит так.

pandas:

df.groupby('country').agg({'amount': 'sum'})

PySpark:

from pyspark.sql import functions as F
df.groupBy('country').agg(F.sum('amount').alias('total'))

Отличия, которые ловят на собесе: в Spark агрегатные функции берутся из pyspark.sql.functions, метод называется groupBy (camelCase), а результат — ленивый план, а не готовая таблица. В pandas после groupby данные уже посчитаны и лежат в памяти. Ещё одна ловушка — кастомная Python-логика: в pandas вы применяете любую функцию через apply нативно, а в Spark каждый вызов Python-UDF гоняет данные между JVM и Python-процессом, что резко замедляет джоб. Поэтому в Spark стараются выражать логику встроенными функциями, а не UDF.

Pandas API on Spark

pyspark.pandas (раньше проект назывался Koalas) — это pandas-подобный API поверх Spark. Пишете привычный pandas-код, а под капотом он выполняется распределённо.

import pyspark.pandas as ps
psdf = ps.read_parquet('s3://...')
psdf.groupby('country').sum()  # синтаксис как в pandas, движок — Spark

Зачем это нужно: перенести существующий pandas-код на большие данные без переучивания на DataFrame API. Аналитик, привыкший к pandas, продолжает писать как обычно, но джоб масштабируется на кластер.

Ограничения. Это не 100% pandas: часть методов отсутствует или ведёт себя иначе, а некоторые операции (например, обращение к строке по индексу) в распределённой модели просто дороги. Для новых пайплайнов чаще берут нативный Spark DataFrame API — он предсказуемее по производительности.

Готовишься к собесу Data Engineer?
Spark, Airflow, ClickHouse, SQL для DE — вопросы с разборами в Telegram
Тренировать DE в Telegram

Polars и DuckDB

Polars — современная альтернатива pandas на движке из Rust. Работает на одной машине, но использует все ядра, колоночное хранение (Arrow) и поддерживает ленивые вычисления.

import polars as pl
df = pl.read_parquet('file.parquet')
df.lazy().group_by('country').agg(pl.col('amount').sum()).collect()

Плюсы: на типичных операциях в 5–50 раз быстрее pandas и экономнее по памяти. Минусы: экосистема моложе, меньше готовых интеграций и примеров, чем у pandas.

DuckDB — встраиваемая аналитическая СУБД, «SQLite для аналитики». Позволяет гонять SQL прямо по файлам Parquet/CSV на одной машине, без поднятия отдельного сервера. Удобен, когда логику проще выразить на SQL, чем на датафреймах, и когда движок нужно встроить в приложение.

Когда что

pandas — данные влезают в RAM (условно до 5–10 ГБ), быстрый разведочный анализ, прототип ML, поддержка существующего кода на pandas.

PySpark — данные на десятки-сотни ГБ и терабайты, есть кластер (или managed-сервис вроде Databricks/EMR), нужны распределённые вычисления и отказоустойчивость джоба.

Polars — одна машина, но производительность критична и данные близки к пределу RAM; хороший выбор для новых проектов, где не нужен кластер.

DuckDB — аналитика на одной машине через SQL поверх файлов, встраивание в приложение или ноутбук, замена «pandas + ручные джоины» на понятный SQL.

Практическое правило для ответа на собесе: пока данные влезают в память одной ноды — распределённый Spark чаще всего избыточен; кластер начинает окупаться, когда датасет перестаёт помещаться на одну машину или когда джоб надо надёжно гонять по расписанию с ретраями.

Частые ошибки

Тащить Spark на маленькие данные. На файле в сотни мегабайт overhead на запуск JVM, планирование и сериализацию съедает всю выгоду. pandas или Polars отработают в разы быстрее.

Забывать про ленивость Spark. Кандидат ждёт, что df.filter(...) сразу что-то посчитает, и удивляется, что джоб «ничего не делает» до .collect(). Не понимать разницу transformation/action — красный флаг на собесе.

Массово использовать Python-UDF в Spark. Каждый UDF гоняет данные между JVM и Python. На больших объёмах это узкое место — почти всегда логику можно выразить встроенными функциями pyspark.sql.functions.

collect() большого датафрейма на драйвер. .collect() стягивает все данные в память драйвера и роняет его OOM. Для выгрузки используют .write(), для отладки — .limit(n).collect().

Считать pyspark.pandas полной заменой pandas. Часть методов отсутствует или отличается по поведению; слепой перенос кода может дать другой результат или неожиданную деградацию скорости.

Связанные темы

FAQ

Когда pandas точно не подойдёт?

Когда датасет не влезает в память одной машины: pandas загружает всё в RAM и падает с MemoryError либо начинает свопить и тормозить. Ориентир — данные заметно больше половины доступной оперативки. Тогда берут Spark (кластер) или Polars/DuckDB (одна мощная нода), в зависимости от масштаба.

В чём разница между eager и lazy вычислениями?

pandas выполняет операции немедленно (eager): каждая строка кода сразу считает результат. Spark ленив (lazy): он копит операции в план и запускает расчёт только на действии (count, collect, write). Ленивость даёт оптимизатору возможность переставить фильтры и не читать лишние данные, но требует понимать, что до «действия» ничего не посчитано.

Почему UDF в Spark медленные?

Стандартные Python-UDF выполняют код в отдельном Python-процессе, поэтому Spark сериализует данные, гоняет их из JVM в Python и обратно построчно. На больших объёмах это дорого. Ускорить можно pandas UDF (векторизованные, через Arrow) или, что лучше, выразить логику встроенными функциями pyspark.sql.functions.

Polars или Spark для 50 ГБ?

Если есть одна мощная машина с достаточным объёмом RAM или быстрым диском — Polars часто справится и будет проще в эксплуатации, чем кластер. Если нужна распределённая обработка, отказоустойчивость и джоб по расписанию поверх ещё больших объёмов — берут Spark. 50 ГБ — как раз пограничная зона, где ответ зависит от инфраструктуры.

Это официальная информация?

Нет. Статья основана на документации Spark, pandas, Polars и DuckDB и на типичных вопросах собеседований Data Engineer. Конкретные требования зависят от компании и стека.


Тренируйте Data Engineering — откройте тренажёр с 1500+ вопросами для собесов.