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. Включает:

  1. Запись на диск map output
  2. Передача по сети между executors
  3. Чтение и обработка на 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. Пайплайн:

  1. Parsed logical plan — из SQL
  2. Analyzed logical plan — резолвены имена колонок, типы
  3. Optimized logical plan — predicate pushdown, projection pruning, constant folding
  4. Physical plans — несколько вариантов с разными join-стратегиями
  5. Cost model — выбирает лучший
  6. Selected physical plan — финальный
  7. 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 минут. План диагностики?»

  1. Открыть Spark UI. Посмотреть stage-ы, identify самый медленный
  2. Проверить shuffle. Огромный shuffle read/write?
  3. Skewed partitions. Одна задача в 10× медленнее остальных?
  4. OOM на executor. Нужно больше памяти или меньше partitions
  5. Optimize joins. Broadcast возможен? Bucketing?
  6. 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 — общее понимание.

Смотрите также