Learning Platform
Глоссарий Troubleshooting
Урок 15.02 · 26 мин
Продвинутый
BuildMavenSBTmake-distribution.shDev Workflow

Сборка Spark из исходников

Умение собрать Spark из исходников — не просто гик-скилл. Это открывает возможности, которых нет при работе с бинарным дистрибутивом: патчить поведение движка, воспроизводить баги на конкретном коммите, тестировать собственные оптимизации перед upstream PR. Кроме того, понимание системы сборки помогает разобраться в модульной структуре проекта.


Предварительные требования

Spark 4.0 требует Java 17 (минимум; рекомендуется JDK 17 LTS или JDK 21) и Scala 2.13. Поддержка Scala 2.12 была удалена в Spark 4.0.0.

java -version
# openjdk version "17.0.11" 2024-04-16
# OpenJDK Runtime Environment (build 17.0.11+9)

# Проверь JAVA_HOME
echo $JAVA_HOME

# Клонирование
git clone https://github.com/apache/spark.git
cd spark
git checkout v4.0.0   # или последний тег: git tag | grep "^v4" | tail -5

Embedded Maven: build/mvn

Spark поставляется со встроенным Maven wrapper’ом в директории build/. Это гарантирует воспроизводимую сборку независимо от системного Maven:

# build/mvn — это shell-скрипт, который скачивает Maven при первом запуске
# и далее использует его из .m2/wrapper/

# Просмотр версии
./build/mvn --version
# Apache Maven 3.9.6
# Java version: 17.0.11

# Базовая компиляция без тестов (самое частое):
./build/mvn -DskipTests clean package

# Сборка только одного модуля (намного быстрее):
./build/mvn -DskipTests -pl core clean package

# -pl = --projects: список модулей через запятую
# -am = --also-make: собрать транзитивные зависимости
./build/mvn -DskipTests -pl sql/core -am clean package

Полная сборка без тестов занимает 8–15 минут на современном Mac M-серии или Linux с 8+ ядрами. С тестами — 2+ часа.


Профили Maven: ключевые комбинации

Maven профили активируются флагом -P. Spark использует их для включения необязательных компонентов:

# Hive metastore + HiveQL + Thrift Server:
./build/mvn -DskipTests -Phive -Phive-thriftserver clean package

# Kubernetes resource manager (включает spark-on-k8s executor):
./build/mvn -DskipTests -Pkubernetes clean package

# YARN resource manager:
./build/mvn -DskipTests -Pyarn clean package

# Hadoop версия (важно для HDFS-совместимости):
./build/mvn -DskipTests -Pyarn -Dhadoop.version=3.3.6 clean package

# Все основные профили вместе (типичный production дистрибутив):
./build/mvn -DskipTests -Phive -Phive-thriftserver -Pyarn -Pkubernetes clean package
Профили Maven и модули Spark 4.0
-PhiveВключает sql/hive и sql/hive-thriftserver модули. Hive metastore интеграция, HiveQL диалект, SerDe поддержка. Нужен для чтения таблиц из Hive metastore.
-PkubernetesВключает resource-managers/kubernetes. KubernetesClusterManager, ExecutorPodsAllocator, ConfigMap-based config. Нужен для запуска Spark на K8s.
-PyarnВключает resource-managers/yarn. YarnClusterManager, ApplicationMaster, YarnAllocator. Нужен для Spark on YARN (Hadoop кластеры).
-PconnectSpark Connect gRPC-сервер. В Spark 4.0 включён по умолчанию, но профиль позволяет явно управлять сборкой connect-модуля.

Важно: по умолчанию (без профилей) собирается минимальный дистрибутив: core, sql, streaming, mllib, graphx. Это достаточно для standalone-режима.


SBT: быстрая итерация при разработке

SBT значительно быстрее Maven при инкрементальной разработке благодаря incremental compilation (zinc). Spark включает SBT wrapper:

# Запуск SBT shell
./build/sbt

# Внутри SBT shell:
# Компиляция конкретного модуля:
sbt:spark> project core
sbt:core> compile

# Компиляция с тестами:
sbt:core> test:compile

# Выход
sbt:core> exit

Типичный dev-workflow с SBT:

# 1. Открыть SBT shell
./build/sbt

# 2. Перейти в нужный модуль и включить continuous compilation
sbt:spark> project sql-catalyst
sbt:sql-catalyst> ~compile
# SBT будет перекомпилировать при каждом изменении файла

# 3. После внесения изменений в Rule — запустить тест:
sbt:sql-catalyst> testOnly *FilterPushdownSuite

Тильда ~ перед командой включает watch mode: SBT мониторит изменения в .scala-файлах и автоматически перекомпилирует. Это экономит время по сравнению с повторным запуском mvn compile.


Запуск unit-тестов конкретного модуля

Запускать все тесты Spark (~100K тестов) практически никогда не нужно. Цель — тестировать только релевантный модуль.

Через Maven:

# Все тесты модуля sql/catalyst:
./build/mvn test -pl sql/catalyst

# Конкретный тест-класс:
./build/mvn test -pl sql/catalyst \
    -Dtest=org.apache.spark.sql.catalyst.optimizer.FilterPushdownSuite

# Конкретный метод внутри класса:
./build/mvn test -pl sql/catalyst \
    -Dtest="org.apache.spark.sql.catalyst.optimizer.FilterPushdownSuite#testPushDownFilter"

# Тест в core:
./build/mvn test -pl core \
    -Dtest=org.apache.spark.scheduler.DAGSchedulerSuite

Через SBT (быстрее):

./build/sbt "project sql-catalyst" "testOnly org.apache.spark.sql.catalyst.optimizer.FilterPushdownSuite"

# Glob-паттерн для нескольких классов:
./build/sbt "project core" "testOnly *DAGScheduler*"

Параллельный запуск (ускоряет в 3–4 раза):

# Maven с параллельными форками:
./build/mvn test -pl sql/core -T 4  # 4 параллельных потока

# Переменная окружения для числа форков:
SPARK_SCALA_TESTING_PROCESSES=4 ./build/mvn test -pl sql/core
WARNING

Некоторые тесты Spark чувствительны к порядку запуска и параллелизму. Если тест падает в параллельном режиме, но проходит в одиночном — это инфраструктурная проблема теста, а не баг в коде. Официальный CI Spark запускает тесты через ./dev/run-tests.py.


make-distribution.sh: сборка production дистрибутива

./dev/make-distribution.sh — скрипт для сборки полноценного дистрибутива, аналогичного официальным архивам с downloads.apache.org:

# Минимальный дистрибутив:
./dev/make-distribution.sh --tgz

# Production дистрибутив с Hive, YARN, Kubernetes:
./dev/make-distribution.sh \
    --name custom-spark-4.0 \
    --tgz \
    -Phive \
    -Phive-thriftserver \
    -Pyarn \
    -Pkubernetes

# С PySpark (требует Python 3.8+):
./dev/make-distribution.sh \
    --name custom-spark-4.0 \
    --pip \
    --tgz \
    -Phive -Pyarn -Pkubernetes

Результат: spark-4.0.0-bin-custom-spark-4.0.tgz в корне репозитория. Структура внутри:

spark-4.0.0-bin-custom-spark-4.0/
├── bin/          # spark-submit, spark-shell, spark-sql, pyspark
├── sbin/         # start-master.sh, start-worker.sh, start-history-server.sh
├── conf/         # spark-defaults.conf.template, spark-env.sh.template, log4j2.properties.template
├── jars/         # Все JAR-файлы (spark-core_2.13-4.0.0.jar, spark-sql_2.13-4.0.0.jar...)
├── python/       # PySpark, если собиралось с --pip
├── kubernetes/   # Dockerfile для executor образов
└── examples/     # Примеры (SparkPi, WordCount...)

Dev-workflow: локальная сборка и spark-shell

Типичный workflow при разработке патча:

1. Собрать изменённый модуль:

# Изменили файл в sql/catalyst (например, добавили новое правило)
./build/mvn -DskipTests -pl sql/catalyst -am clean package
# -am гарантирует пересборку зависимостей (sql/core зависит от sql/catalyst)

2. Запустить spark-shell с локально собранными JAR:

# Указываем явно JAR из target/ вместо jars/ директории дистрибутива
SPARK_PREPEND_CLASSES=true \
    ./bin/spark-shell \
    --master local[2]

SPARK_PREPEND_CLASSES=true заставляет spark-shell добавлять target/scala-2.13/classes директории перед JAR-файлами в classpath. Это позволяет тестировать изменения без пересборки всего дистрибутива.

3. Тестирование конкретного изменения:

// В spark-shell:
import org.apache.spark.sql.catalyst.optimizer.SimplifyBooleanExpressions

// Создаём тестовый DataFrame
val df = spark.range(1000).filter("id > 100 AND true")

// Смотрим план с нашим изменением
df.explain("extended")
// Должны увидеть, что "AND true" убрано на этапе Optimized Logical Plan

4. Запуск отдельного теста перед коммитом:

./build/mvn test -pl sql/catalyst \
    -Dtest=org.apache.spark.sql.catalyst.optimizer.SimplifyBooleanExpressionsSuite \
    2>&1 | tail -20

Сборка конкретного тестового JAR для Spark UI

Иногда нужно запустить примеры из examples/ чтобы наблюдать поведение в Spark UI:

# Собрать examples:
./build/mvn -DskipTests -pl examples -am clean package

# Запустить SparkPi с наблюдением в UI:
./bin/spark-submit \
    --class org.apache.spark.examples.SparkPi \
    --master local[4] \
    --conf spark.ui.port=4040 \
    examples/target/scala-2.13/jars/spark-examples_2.13-4.0.0.jar \
    1000
# UI доступен на localhost:4040

Это полезно при изучении изменений в Spark UI или при debugging планировщика — видно реальные stages, tasks, метрики.


Сборка с кастомными JVM-флагами

При разработке иногда нужно включить дополнительную диагностику:

# Включаем компиляцию с отладочными символами и без оптимизаций
export MAVEN_OPTS="-Xmx4g -XX:+TieredCompilation -XX:TieredStopAtLevel=1"

# Или для диагностики Zinc (SBT incremental compiler):
export SBT_OPTS="-Xmx4g -XX:+UseG1GC -Xlog:gc:build/gc.log"
./build/sbt "project core" compile

Для выявления memory leak в коде компилятора:

# Увеличенная куча для Maven при сборке большого числа модулей
export MAVEN_OPTS="-Xmx8g -XX:+UseG1GC -XX:G1HeapRegionSize=32m"
./build/mvn -DskipTests clean package
Выбор инструмента сборки

SBT (dev)

SBT: быстрый итеративный цикл компиляции. zinc incremental compiler пересобирает только изменённые файлы и их зависимости. Идеален при частых мелких изменениях.

Maven (CI/release)

Maven: официальный build of reference. Используется для CI, финальных сборок, release. build/mvn — embedded wrapper с фиксированной версией. Медленнее SBT, но надёжнее.
для дистрибутива

dev/make-distribution.sh

make-distribution.sh: обёртка над Maven для сборки полного дистрибутива. Создаёт правильную структуру директорий, копирует скрипты, генерирует tgz. Принимает те же -P профили что и mvn.

Попробуй сам

1. Клонируй и собери core.

git clone https://github.com/apache/spark.git
cd spark
git checkout v4.0.0

# Собери только core (занимает ~3-5 минут):
./build/mvn -DskipTests -pl core -am clean package 2>&1 | tail -5

Ожидаемый финал: [INFO] BUILD SUCCESS.

2. Запусти конкретный тест.

# Тест DAGScheduler — самый информативный для понимания планировщика
./build/mvn test -pl core \
    -Dtest=org.apache.spark.scheduler.DAGSchedulerSuite \
    -Dsurefire.useFile=false 2>&1 | grep -E "Tests run:|FAIL|ERROR" | tail -5

3. Собери минимальный дистрибутив и запусти spark-shell.

./dev/make-distribution.sh --tgz
# После завершения:
tar xzf spark-*.tgz
cd spark-*/
./bin/spark-shell --master local[2]
# В shell:
# scala> spark.version
# res0: String = 4.0.0
Проверка знанийKnowledge check
Команда ./build/mvn -DskipTests -pl sql/core clean package завершилась ошибкой: "Could not resolve dependency: org.apache.spark:spark-catalyst_2.13:4.0.0". При этом sql/catalyst точно есть в репозитории. Почему возникла эта ошибка и как её исправить одним флагом?
ОтветAnswer
Ошибка возникает потому, что sql/core зависит от sql/catalyst, но sql/catalyst ещё не установлен в локальный Maven-репозиторий (~/.m2). Флаг -pl собирает только указанный модуль, но не его зависимости. Решение: добавить флаг -am (--also-make), который заставляет Maven сначала собрать все транзитивные зависимости. Правильная команда: ./build/mvn -DskipTests -pl sql/core -am clean package. Флаг -am сначала соберёт sql/catalyst (и его зависимость от core, common и т.д.), а затем sql/core. Альтернатива: предварительно запустить ./build/mvn -DskipTests install в корне, чтобы установить все модули в ~/.m2, — но это медленнее.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Команда ./build/mvn -DskipTests -pl sql/core clean package падает с ошибкой о неразрешённой зависимости на spark-catalyst. Какой флаг нужно добавить и почему?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4