Apache Spark на собеседовании Data Engineer
Зачем DE спрашивают про Spark
Apache Spark — главный движок для пакетной обработки больших данных в современных компаниях. Если ты Data Engineer и претендуешь на middle+ позицию, Spark будет на собесе обязательно. Уровень — от «знаю синтаксис PySpark» до «могу спроектировать distributed query с custom partitioner для PB-данных».
На собесе Data Engineer Spark проверяют двумя путями: (а) теоретические вопросы — что такое Catalyst, как работает shuffle; (б) практические — кейс с оптимизацией медленного job-а. Это часть основного раунда «инструменты и архитектура».
Архитектура Spark
Driver — главный процесс, который запускает Spark Context, строит DAG задач, координирует executors. На драйвере выполняется collect(), take(), count().
Executors — worker-процессы, которые выполняют задачи. Каждый держит куски данных в памяти и на диске, выполняет вычисления.
Cluster manager — YARN, Kubernetes, Mesos или standalone — распределяет ресурсы между приложениями.
Catalyst optimizer — оптимизатор SQL/DataFrame запросов. Делает rewrite, predicate pushdown, выбирает join-стратегии.
Tungsten engine — оптимизация работы с памятью (off-heap, code generation).
RDD vs DataFrame vs Dataset
RDD — низкоуровневое API. Гибкое, но не оптимизируется Catalyst. На современном Spark используется редко — только когда нужен полный контроль.
DataFrame — таблично-структурированные данные с column names и types. Catalyst оптимизирует. Рекомендуемый API.
Dataset — типизированная версия DataFrame (есть в Scala/Java, нет в Python). Type-safety + Catalyst.
На собесе: «Используйте DataFrame, кроме случаев, когда нужны RDD-функции (например, mapPartitions для custom logic)».
Shuffle
Самая дорогая операция в Spark. Когда нужно перетасовать данные между executors — например, groupBy, join, repartition. Включает:
- Запись на диск map output
- Передача по сети между executors
- Чтение и обработка на receiving side
Признаки проблемного shuffle: долгие задачи, OOM, медленные joins, дисковая активность.
Как сократить shuffle:
- Broadcast joins. Если одна таблица < 10 MB (или сколько в config), broadcast её на все executors. Нет shuffle, только map join.
- Pre-partitioning. Если данные уже партиционированы по join key, shuffle избежать можно.
- Bucketing. Аналогично pre-partitioning, но физически на диске.
- Salting для skewed данных. Добавить случайный suffix к hot key, чтобы распределить нагрузку.
Partitioning
Partition в Spark — единица параллельной работы. Количество = degree of parallelism.
Слишком мало partitions (например, 10 на 100GB) — недоиспользуются executors, медленно.
Слишком много (например, 10000 на 1GB) — overhead от шедулинга, мелкие задачи.
Правило большого пальца: 100-200 MB на partition.
repartition(N) vs coalesce(N):
repartition— full shuffle, может увеличивать и уменьшатьcoalesce— только уменьшение, без полного shuffle (быстрее), но может оставить unbalanced partitions
Joins в Spark
Broadcast hash join. Маленькая таблица копируется на все nodes. Самый быстрый. По умолчанию для таблиц < 10 MB.
Shuffle hash join. Обе таблицы партиционируются по join key, потом hash join. Используется когда broadcast невозможен.
Sort merge join. Обе таблицы сортируются и партиционируются, потом merge. Default для больших таблиц. Используется когда обе слишком большие для hash.
Broadcast nested loop join. Только когда нет equi-join condition. Очень медленный, избегай.
На собесе: «По умолчанию я explain plan смотрю, потом думаю, можно ли сделать broadcast».
Catalyst и оптимизатор
Catalyst — rule-based + cost-based optimizer. Пайплайн:
- Parsed logical plan — из SQL
- Analyzed logical plan — резолвены имена колонок, типы
- Optimized logical plan — predicate pushdown, projection pruning, constant folding
- Physical plans — несколько вариантов с разными join-стратегиями
- Cost model — выбирает лучший
- Selected physical plan — финальный
- Generated code — компилируется в bytecode (whole-stage code generation)
Predicate pushdown — фильтры выталкиваются как можно ниже, к источнику. Если используешь Parquet с column statistics, Spark пропускает блоки.
Skewed data
Skew — когда один partition сильно больше других. Часто связан с hot keys (например, NULL или один популярный user_id).
Признаки: одна задача выполняется в 10× дольше других, OOM на одном executor.
Решения:
- Salting. Добавить случайный suffix к skewed key (key + random_int). Шафлится равномернее.
- AQE (Adaptive Query Execution). Spark 3+ автоматически распознаёт skew и сплитит партиции.
- Replicated key для small-side в join.
Spark Streaming
DStream — старое API. RDD-based.
Structured Streaming — DataFrame-based. Рекомендуемый. Поддерживает event-time processing, watermarks, late data.
Watermark — позволяет Spark отбрасывать слишком поздние данные. Если watermark «10 минут», события старше 10 минут от max timestamp игнорируются.
Типичные вопросы
«Spark job работает 4 часа вместо 30 минут. План диагностики?»
- Открыть Spark UI. Посмотреть stage-ы, identify самый медленный
- Проверить shuffle. Огромный shuffle read/write?
- Skewed partitions. Одна задача в 10× медленнее остальных?
- OOM на executor. Нужно больше памяти или меньше partitions
- Optimize joins. Broadcast возможен? Bucketing?
- Optimize storage. Parquet с partitions vs CSV — огромная разница
«Что такое broadcast join и когда не работает?»
Маленькая таблица копируется на все executors. Не работает: (1) таблица слишком большая (> default 10 MB), (2) memory constraint на executor, (3) очень много executors (broadcast overhead).
«Почему df.cache() помогает не всегда?»
Cache хранит данные в памяти после первого вычисления. Помогает если данные используются несколько раз. Не помогает если: (1) данные только один раз, (2) cache evicted из-за нехватки памяти, (3) lazy evaluation — cache применяется только после action.
«Чем отличается repartition от coalesce?»
repartition — full shuffle, может изменять в любую сторону. coalesce — только уменьшение, без full shuffle. Используй coalesce когда нужно уменьшить partitions без передвижения данных по сети.
«Spark vs Flink?»
Spark — batch + micro-batch streaming. Flink — true streaming + batch. Для большинства DE-задач Spark достаточен и проще. Flink — когда нужна низкая latency (<100ms).
Частые ошибки
- Использовать
collect()на больших данных. Все данные в driver — OOM UDFв Python без оптимизации. Spark не оптимизирует Python UDF. Используй pandas_udf или native expressions- Игнорировать Spark UI. UI — главный инструмент диагностики. Не открыл — не понимаешь job
cache()без понимания. Cache — это не magic. Cache применяется только когда action триггерится- Default partition count. 200 partitions по умолчанию (
spark.sql.shuffle.partitions). Не всегда подходит для твоего размера данных
FAQ
Какую версию Spark учить?
3.x. AQE (Adaptive Query Execution), dynamic partition pruning — это 3+. Spark 2.x пока используется, но новые фичи только в 3+.
Нужен ли Scala или Python хватит?
Большинство компаний — Python (PySpark). Scala даёт лучшую производительность, но синтаксис сложнее.
Какие книги читать?
«Spark: The Definitive Guide» (Chambers, Zaharia) — фундамент. «High Performance Spark» (Karau) — для optimization.
Сколько готовиться к Spark-блоку собеса?
Зависит от опыта. С нуля — 2-3 месяца. Уже работал — 2-4 недели для систематизации.
Спрашивают ли алгоритмы внутри Spark?
Редко. Distributed-algorithms типа external merge sort, distributed hash join — на senior. На middle — общее понимание.