Сборка 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
Важно: по умолчанию (без профилей) собирается минимальный дистрибутив: 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
Некоторые тесты 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