Выбор движка: Comet vs Velox vs RAPIDS vs Photon - матрица по кейсам
Выбор движка: Comet vs Velox vs RAPIDS vs Photon - матрица по кейсам
Эра пост-JVM в распределённых вычислениях¶
Этот урок - финальный в модуле о нативных движках. Его цель не повторение технических деталей каждого движка, а формирование инженерного суждения: умения анализировать конкретную ситуацию и выбирать правильный инструмент. В реальных проектах неправильный выбор движка обходится дорого - не только деньгами, но и временем, стабильностью и доверием команды.
Почему рынок разделился на несколько движков¶
В 2016–2018 годах в индустрии Big Data стало ясно: WholeStageCodegen и Tungsten - это потолок JVM-оптимизации. Они дали значительный прирост по сравнению с Spark 1.x, но дальнейший прогресс упирался в архитектурные ограничения JVM: GC-паузы, невозможность детерминированного SIMD, объектная модель с многократным разыменованием указателей.
Ответом стал рынок нативных ускорителей - каждый из которых появился как решение конкретной задачи конкретного игрока:
- NVIDIA RAPIDS (2019): NVIDIA хотела показать, что GPU применимы не только в DL, но и в аналитике. Инвестиция в экосистему cuDF/RAPIDS была стратегической.
- Databricks Photon (2021): Databricks искала конкурентное преимущество над открытым Spark. Закрытый C++ движок, глубоко интегрированный с Delta Lake - это дифференциатор, который нельзя скопировать конкурентам.
- Gluten + Velox (2022): Meta уже использовала Velox внутри Presto. Intel и Microsoft присоединились к Gluten как способу принести этот движок в Spark. Цель - максимальная производительность на enterprise on-premise кластерах.
- Apache DataFusion Comet (2023): Сообщество Apache искало открытую и безопасную альтернативу, которую легко встроить в любой Spark-кластер. Ставка на Rust-экосистему Arrow + DataFusion.
Четыре движка - четыре разные философии. Нет одного правильного ответа. Задача архитектора - понять, какая философия совпадает с контекстом проекта.
По каким параметрам сравниваем¶
Для корректного сравнения нужна многомерная оценка, а не просто TPC-H speedup:
- Производительность: ускорение на CPU-bound аналитических задачах (TPC-H SF=100)
- Покрытие операторов: какой процент реального плана выполняется нативно
- Стабильность: риск OOM, SIGSEGV, непредсказуемого fallback
- Стоимость TCO: инфраструктура, лицензии, время инженеров
- Сложность деплоя: от добавления JAR до сборки нативных бинарей
- Вендорская независимость: можно ли сменить провайдера завтра?
- Экосистема: зрелость инструментария, документации, сообщества
Глубокий анализ: ДНК каждого движка¶
Apache DataFusion Comet - безопасный Rust как философия¶
Происхождение и ДНК. Comet родился внутри Apple (первоначально как arrow-datafusion-comet), затем перешёл в Apache Software Foundation как проект в стадии инкубации. Его техническое ядро - Rust + Apache DataFusion + Apache Arrow. Rust выбран намеренно: безопасность памяти на уровне компилятора исключает целый класс ошибок (use-after-free, buffer overflow), которые в C++ приводят к SIGSEGV и core dump.
Архитектурная модель. Comet работает как Spark Plugin: JVM-классы перехватывают физический план, сериализуют операторы в protobuf, передают через JNI в Rust-библиотеку DataFusion. Там они десериализуются в нативный план и выполняются над Arrow RecordBatch. Результат возвращается в JVM как Arrow-совместимый ColumnarBatch.
Сильные стороны:
- Минимальный риск краша всего Executor-а: Rust-код не имеет неопределённого поведения по определению. SIGSEGV в нативном коде - исключительная редкость. Если что-то не так, Comet корректно деградирует в JVM.
- Простота деплоя: один JAR, два параметра конфигурации (
spark.plugins+spark.sql.extensions). Работает на любом Spark 3.4+ без сборки нативных бинарей под конкретный CPU. - Активная разработка: с момента перехода в ASF количество поддерживаемых операторов быстро растёт с каждым релизом.
Слабые стороны:
- Незрелость покрытия: сложные оконные функции (
COLLECT_LIST, некоторые аналитические функции), ряд CAST-операций,MapType- всё это fallback в JVM. На реальных enterprise-пайплайнах доля нативного исполнения может быть 50–70% вместо ожидаемых 100%. - Нет нативного shuffle по умолчанию:
CometShuffleManagerтребует дополнительной настройки и не всегда включён в базовую конфигурацию. - Меньшее ускорение на join-тяжёлых запросах: DataFusion уступает Velox по производительности hash join (нет аналога hardware prefetching).
Типичный профиль задачи для Comet:
- SQL-аналитика на Parquet, Silver/Gold слои
- Команда небольшая, нет DevOps-экспертизы по сборке нативных бинарей
- Kubernetes-кластер с минималистичным стеком
- Открытый стек, нет зависимости от cloud-провайдера
Gluten + Velox - зрелый C++ для максимального перформанса¶
Происхождение и ДНК. Velox - это библиотека от Meta (Facebook), созданная в 2019 году для унификации execution runtime между Presto и другими аналитическими системами Meta. C++ без GC, SIMD через intrinsics, кастомный MemoryPool - Velox оптимизирован для максимальной производительности на enterprise-железе. Gluten (от Intel, Microsoft, Alibaba) - это мост между Catalyst и Velox: он транслирует Spark-план в Substrait IR и передаёт в Velox.
Архитектурная модель. Catalyst → CometSparkSessionExtensions → Substrait protobuf → JNI → Velox native operators → Arrow RecordBatch обратно в JVM. Substrait - это ключевое архитектурное решение: он позволяет подключать не только Velox, но и ClickHouse (существует gluten-ch backend).
Сильные стороны:
- Максимальный перформанс среди open-source: 5–8× ускорение на TPC-H SF=100 (против 3–5× у Comet). Hardware prefetching в hash join, более зрелая агрегация, нативный spill.
- Нативный columnar shuffle:
ColumnarShuffleManager- Arrow IPC по сети без Java-сериализации. Критично для shuffle-тяжёлых запросов. - Substrait как архитектурный задел: Gluten+Velox - единственный из четырёх движков, реализующий стандартный IR. Это делает его более переносимым в будущем.
- Зрелый Memory Pool: Velox умеет нативно спиллить на диск при нехватке RAM - предсказуемое поведение вместо OOM crash.
Слабые стороны:
- Сложный деплой: нужно собирать нативные бинари под конкретный CPU (AVX2 или AVX-512), что занимает 30–60 минут и требует CI. Готовые бинари есть только под Ubuntu 22.04 x86-64.
- C++ - это C++: ошибка в Velox-коде или неправильная настройка памяти → SIGSEGV → весь Executor падает. Дебаг JNI-границы нетривиален: нужно уметь читать core dump нативного кода.
- Высокий барьер входа: понять, почему один оператор падает в JVM, другой работает нативно, а третий спиллит - требует хорошего понимания Substrait-трансляции.
Типичный профиль задачи для Gluten+Velox:
- On-premise кластер с большим числом нод (50+ Executor)
- Петабайтные аналитические нагрузки: join двух таблиц по 500+ GB
- Есть платформенная команда, умеющая поддерживать нативные зависимости
- Максимальный ROI важнее операционной простоты
Databricks Photon - best-in-class без права на портативность¶
Происхождение и ДНК. Photon - закрытый C++ движок от Databricks, созданный командой по принципу "переписать всё заново без ограничений JVM". У Databricks был доступ к тысячам production-пайплайнов клиентов, что позволило оптимизировать именно те операторы, которые встречаются чаще всего в реальности, а не в синтетическом бенчмарке.
Архитектурная модель. Catalyst → Photon Extensions → физические операторы заменяются закрытыми реализациями (PhotonHashAggregateExec, PhotonScanExec и т.д.). Delta Lake интегрирован на уровне Deletion Vectors и Z-order statistics - что даёт дополнительный выигрыш при чтении с predicate pushdown.
Сильные стороны:
- Лучший автоматический fallback: Photon никогда не ломается из-за unsupported operator - он тихо переключается на JVM. Это делает его самым стабильным из четырёх.
- Идеальная Delta Lake-синергия: Photon - единственный движок, который глубоко понимает Delta Lake internals (Deletion Vectors, Z-order, liquid clustering). На Delta-таблицах он даёт дополнительные 20–40% ускорения, недоступные другим движкам.
- Нулевые операционные расходы: включается одним чекбоксом в Databricks UI. Никаких JAR, никакой сборки, никакого тюнинга OOM.
- Высокое покрытие операторов: годы production-использования позволили реализовать большинство реальных Spark операторов нативно.
Слабые стороны:
- Вендор lock-in: абсолютный. Photon работает только внутри Databricks. Перейти на другую платформу - потерять всё ускорение Photon и заново настраивать альтернативу.
- Стоимость: Databricks DBU (Databricks Unit) включает наценку за Photon. На крупных кластерах это может быть $50,000–$200,000 в год дополнительно к стоимости облачных VM.
- Нет контроля: если Photon не поддерживает ваш оператор - вы не можете это исправить. Нужно ждать следующего релиза Databricks.
Типичный профиль задачи для Photon:
- Организация уже использует Databricks (нет альтернативы)
- Delta Lake как основной формат
- Небольшая data engineering команда без инфраструктурной экспертизы
- Быстрый time-to-market важнее операционного контроля
NVIDIA RAPIDS - GPU как новое измерение параллелизма¶
Происхождение и ДНК. NVIDIA RAPIDS возник в 2018 году как стратегическая инициатива NVIDIA: доказать применимость GPU в аналитике, а не только в DL. cuDF - это GPU DataFrame library, совместимая с pandas API. RAPIDS Accelerator for Apache Spark - плагин, который заменяет Spark SQL операторы CUDA-kernels.
Архитектурная модель. Catalyst → RAPIDS Extensions → физические операторы переносятся на GPU (GpuFilterExec, GpuHashAggregateExec, GpuBroadcastHashJoinExec). Данные перемещаются из CPU RAM в GPU VRAM через PCIe, обрабатываются параллельно тысячами CUDA-ядер, результат возвращается в CPU RAM. UCX + GPUDirect RDMA - опциональный shuffle напрямую GPU↔GPU по InfiniBand без участия CPU.
Сильные стороны:
- Качественно другой параллелизм: H100 содержит 16896 CUDA-ядер. На по-настоящему compute-heavy задачах (большие aggregations, тяжёлые joins) ускорение 8–12× недостижимо для CPU-движков.
- GPU + ML = натуральный союз: RAPIDS живёт в том же пространстве, что PyTorch и cuML. GPU-кластер для аналитики одновременно - кластер для ML feature engineering и distributed training.
- Pропускная способность памяти: HBM3 (H100) = 3.35 TB/s vs DDR5 (лучший CPU) = 460 GB/s. Для агрегаций над широкими таблицами это критично.
Слабые стороны:
- GPU OOM - жёсткая граница: когда VRAM исчерпана, RAPIDS спиллит в CPU RAM (через PCIe: ~30 GB/s), затем на NVMe. Это в 10–100 раз медленнее работы из VRAM. Неправильная настройка
batchSizeRows- и задача становится медленнее, чем на CPU. - Стоимость железа: H100 SXM стоит $30,000–$40,000 за карту. Даже аренда (
p4d.24xlargeAWS с 8 × A100) = $32.77/час. Если GPU не утилизирован на 70%+ - это выброшенные деньги. - IO-bound = бесполезно: если задача ждёт S3, JDBC или shuffle - GPU простаивает. На IO-bound задачах RAPIDS не даёт ничего.
- CUDA compatibility matrix: каждая новая версия Spark/RAPIDS/CUDA должна строго совпадать. Обновление любого компонента - потенциальный рабочий день на диагностику.
Типичный профиль задачи для RAPIDS:
- Вычислительно тяжёлые задачи: миллиарды строк, тяжёлые агрегации, large join без IO bottleneck
- ML feature engineering: числовые трансформации на сотнях признаков
- Данные в Parquet без малых файлов (нет PCIe-overhead на каждый файл)
- Команда с GPU-опытом или готовность его приобрести
Дерево принятия решений¶
Дерево отражает главный принцип: выбор движка определяется инфраструктурой и профилем нагрузки, а не маркетинговыми бенчмарками. Первые два вопроса - инфраструктурные (Databricks? GPU?). Дальше - профиль нагрузки. И лишь в конце - готовность команды к операционной сложности.
Сводная архитектурная матрица¶
Техническое сравнение¶
| Критерий | Apache Comet | Gluten + Velox | Databricks Photon | NVIDIA RAPIDS |
|---|---|---|---|---|
| Язык runtime | Rust | C++ | C++ (закрытый) | CUDA C++ |
| IR-формат | Custom protobuf | Substrait (открытый стандарт) | Закрытый | Нет IR |
| Arrow-совместимость | Полная | Полная | Частичная | Полная (cuDF) |
| Нативный shuffle | CometShuffleManager | ColumnarShuffleManager | Встроен | UCX/GPUDirect |
| Нативный spill | Нет (JVM fallback) | Да (Velox MemPool) | Да (авто) | Да (→CPU RAM→NVMe) |
| OOM-риск | Низкий | Средний (JNI) | Минимальный | Высокий (VRAM) |
| SIGSEGV-риск | Нет | Есть | Нет | Нет (изолированный CUDA) |
| AQE-совместимость | Полная | Полная | Полная | Полная |
| Iceberg-поддержка | Да | Да | Да | Да |
| Delta Lake-поддержка | Да | Да | Нативная | Да |
| Python UDF | Fallback | Fallback | Fallback | Fallback |
| Оконные функции | Частичная | Широкая | Широкая | Частичная |
| TPC-H SF=100 speedup | 3–5× | 5–8× | 3–10× | 8–12× |
Операционное и экономическое сравнение¶
| Критерий | Apache Comet | Gluten + Velox | Databricks Photon | NVIDIA RAPIDS |
|---|---|---|---|---|
| Лицензия | Apache 2.0 | Apache 2.0 | Проприетарная | Apache 2.0 |
| Стоимость лицензии | $0 | $0 | +30–50% к DBU | $0 (железо дорогое) |
| Деплой: добавить JAR | 5 минут | 30–60 минут (сборка) | 1 клик (UI) | 1 JAR + CUDA setup |
| Деплой: сборка бинарей | Нет | Да (под CPU arch) | Нет | Нет |
| Вендор lock-in | Нет | Нет | Жёсткий (Databricks only) | Нет (нужен GPU) |
| Портативность | Любой Spark 3.4+ | Любой Spark 3.3+ | Только Databricks | Любой Spark + GPU |
| Барьер входа команды | Низкий | Высокий | Минимальный | Средний |
| Отладка через explain() | Стандартная | Требует Substrait-знания | Минимальная | Стандартная |
| Community size | Растёт (ASF) | Крупное (Meta/Intel/MS) | Closed (Databricks) | Крупное (NVIDIA) |
Матрица операторов (покрытие)¶
| Оператор | Comet | Velox | Photon | RAPIDS |
|---|---|---|---|---|
| Parquet Scan | Нативный | Нативный | Нативный | GPU-native |
| ORC Scan | Нет | Нативный | Нативный | GPU-native |
| Filter | Нативный | Нативный | Нативный | GPU-native |
| Project | Нативный | Нативный | Нативный | GPU-native |
| HashAggregate | Нативный | Нативный | Нативный | GPU-native |
| SortMergeJoin | Нативный | Нативный | Нативный | GPU-native |
| BroadcastHashJoin | Нативный | Нативный | Нативный | GPU-native |
| FullOuterJoin | Частично | Нативный | Нативный | GPU-native |
| Sort | Нативный | Нативный | Нативный | GPU-native |
| Window: rank/row_number | Да | Да | Да | Да |
| Window: lag/lead | Нет | Да | Да | Частично |
| COLLECT_LIST/SET | Нет | Нет | Да | Нет |
| Python UDF | Fallback | Fallback | Fallback | Fallback |
| Pandas UDF | Частично | Частично | Частично | Частично |
| MapType ops | Нет | Частично | Да | Нет |
| DecimalType (прецизия > 18) | Частично | Нативный | Нативный | Нативный |
Разбор боевых кейсов¶
Кейс 1: On-Premise банк с жёстким контуром безопасности¶
Ситуация. Банк использует ArenaData Hadoop (Cloudera-совместимый дистрибутив) на bare-metal кластере из 200 нод (32 vCPU, 256 GB RAM каждая). Данные - транзакции за 7 лет, ~800 TB в HDFS. Ночной отчётный пайплайн работает 5–6 часов, бизнес хочет сократить до 2 часов. Бюджет: $0 на лицензии, есть своя платформенная команда из 4 инженеров.
Анализ по дереву решений:
- Databricks? Нет - on-premise, и контур безопасности не позволяет cloud.
- GPU? Нет - bare-metal серверы без GPU.
- On-premise enterprise? Да.
- Команда умеет нативные либы? Да - есть 4 платформенных инженера.
Решение: Gluten + Velox.
Математика: 200 нод × 32 vCPU = 6400 CPU. При 5.5× ускорении на join-тяжёлых запросах пайплайн займёт ~1 час 6 минут. Стоимость: $0 (opensource) + ~2 недели работы команды на сборку и тюнинг.
Конфигурация памяти - критически важна:
# Для ноды 256 GB RAM (32 vCPU)
spark_config = {
# JVM heap: минимально (Catalyst, scheduler, метаданные)
"spark.executor.memory": "16g",
# JVM offHeap: Arrow-буферы на JNI-границе
"spark.memory.offHeap.enabled": "true",
"spark.memory.offHeap.size": "32g",
# Velox Memory Pool: основная операционная память C++-операторов
# Для join двух таблиц по 500GB нужно много!
"spark.gluten.memory.offHeap.size.in.bytes": str(192 * 1024**3), # 192 GB
# memoryOverhead: OS + нативные либы
"spark.executor.memoryOverhead": "16g",
# Итого: 16 + 32 + 192 + 16 = 256 GB - в точности вписываемся!
# Нативный spill включён: при исчерпании Velox Pool → NVMe
"spark.gluten.sql.columnar.backend.velox.spillEnabled": "true",
"spark.gluten.sql.columnar.backend.velox.joinSpillEnabled": "true",
# HDFS вместо S3: нет PCIe-overhead, данные локально
"spark.hadoop.fs.defaultFS": "hdfs://namenode:9000",
}
Риски и митигация:
- Риск SIGSEGV: тест каждого нового Velox-релиза на staging-кластере (20 нод) перед деплоем в production.
- Риск неполного покрытия: запустить RAPIDS Qualification Tool на event logs (он работает и для Gluten-оценки) перед переходом.
- Риск сборки под ArenaData Hadoop: Velox нужно собирать с правильными версиями protobuf, folly, arrow под конкретный дистрибутив. Заложить 1–2 недели на это.
Кейс 2: Cloud-стартап AWS с командой аналитиков¶
Ситуация. FinTech-стартап, AWS-инфраструктура, 5 человек в data-команде (2 аналитика, 2 дата-инженера, 1 DevOps). Стек: dbt + Delta Lake + S3, Databricks уже используется для отдельных ETL. Задача: ускорить Gold-слой (тяжёлые агрегации клиентских KPI). Дедлайн - следующий квартал. Команда не хочет поддерживать нативные библиотеки.
Анализ по дереву решений:
- Databricks? Да - уже используется.
- Дальше идти по дереву не нужно.
Решение: Databricks Photon.
Обоснование: команда уже на Databricks, Delta Lake как основной формат - это идеальные условия для Photon. Включается одним чекбоксом. Никакой операционной нагрузки.
Конфигурация:
# В Databricks: через Cluster UI или через API
cluster_config = {
"spark_version": "14.3.x-photon-scala2.12",
"node_type_id": "i3.2xlarge",
"num_workers": 10,
"spark_conf": {
"spark.sql.adaptive.enabled": "true",
"spark.sql.adaptive.coalescePartitions.enabled": "true",
# Photon включается автоматически при версии "photon"
# Проверка: spark.databricks.photon.enabled = true (readonly)
}
}
# Проверка Photon в плане:
# df.explain() должен показывать PhotonHashAggregateExec, PhotonScanExec
Экономический расчёт:
Без Photon: 10 × i3.2xlarge × 6 DBU/час × $0.20/DBU × 2 часа = $24/запуск
С Photon: 10 × i3.2xlarge × 6 DBU/час × $0.25/DBU × 0.7 часа = $10.5/запуск
(Photon DBU дороже на 25%, но задача работает в 3x быстрее)
Экономия: $13.5/запуск × 365 запусков/год = $4,927/год
+ сэкономленное время команды на операционную поддержку: бесценно
Ограничение, которое нужно знать: если стартап захочет перейти на Snowflake, Trino или ванильный Spark через год - Photon не переедет. При переходе придётся либо выбирать Comet/Velox, либо мириться с потерей ускорения.
Кейс 3: Open-source стек на Kubernetes¶
Ситуация. Retail-компания, Kubernetes на bare-metal, данные в MinIO (Parquet + Iceberg). Инфраструктурная команда: 2 DevOps-инженера, специализирующихся на K8s. Данные инженеры - сильные в SQL/Python, но не знают C++. Задача: ускорить Silver-слой ETL (JOIN + GROUP BY) без рисков нестабильности.
Анализ по дереву решений:
- Databricks? Нет.
- GPU? Нет.
- On-premise enterprise? Нет - K8s, не bare-metal YARN.
- Данные IO-bound? Нет (Parquet, хорошие партиции).
- → Apache Comet.
Решение: Apache Comet.
Обоснование: нет C++ экспертизы → Gluten+Velox нецелесообразен. Нет GPU → RAPIDS нецелесообразен. Нет Databricks → Photon невозможен. Comet - единственный разумный выбор.
Конфигурация для K8s:
# spark-submit для Kubernetes с Comet
spark_config = {
"spark.plugins": "org.apache.spark.CometPlugin",
"spark.sql.extensions":
"org.apache.spark.sql.comet.CometSparkSessionExtensions",
"spark.comet.enabled": "true",
"spark.comet.exec.all.enabled": "true",
# Нативный shuffle (CometShuffleManager)
"spark.shuffle.manager":
"org.apache.spark.sql.comet.execution.shuffle.CometShuffleManager",
# Память: консервативно для K8s-пода на 16 GB
"spark.executor.memory": "8g",
"spark.memory.offHeap.enabled": "true",
"spark.memory.offHeap.size": "6g",
"spark.executor.memoryOverhead": "2g",
# Итого: 8 + 6 + 2 = 16 GB
# Kubernetes-специфичное
"spark.kubernetes.container.image": "myregistry/spark-comet:3.5",
"spark.kubernetes.namespace": "spark",
}
Ожидаемое ускорение: 2.5–4× на SQL-аналитических запросах. На запросах с оконными функциями (lag, lead) ускорение меньше или отсутствует - Comet деградирует в JVM. Это нужно принять как ограничение.
Как мониторить покрытие:
# После настройки: проверяем % нативных операторов
spark.conf.set("spark.comet.logFallbackReasons.enabled", "true")
df = spark.sql("""
SELECT region, SUM(revenue), COUNT(*), AVG(margin)
FROM gold.sales JOIN dim.products USING (product_id)
WHERE sale_date >= '2024-01-01'
GROUP BY region ORDER BY 2 DESC
""")
df.explain()
# Если в плане нет ни одного `Comet`-оператора - что-то не так
# Если есть GpuColumnarToRow/ColumnarToRow - fallback частичный
Кейс 4: ML Feature Engineering для рекомендательных нейросетей¶
Ситуация. E-commerce компания. Задача: ежедневно строить матрицу сходства товаров (item-item similarity) по поведенческим данным (1.5 миллиарда событий в сутки). Вычисление косинусных расстояний между векторами признаков - чисто compute-heavy операция. Кластер на AWS с доступными p4d.24xlarge (8 × NVIDIA A100 80GB). Данные - Parquet без мелких файлов.
Анализ по дереву решений:
- Databricks? Нет - Kubernetes on AWS.
- GPU доступны? Да.
- Нагрузка compute-heavy? Да (матрица сходства, агрегации по 1.5B строк).
- → NVIDIA RAPIDS.
Решение: NVIDIA RAPIDS + UCX.
Обоснование: задача идеальна для GPU. 1.5B строк × групповая агрегация × косинусное расстояние - это именно тот тип нагрузки, где GPU даёт 8–12× ускорение.
Конфигурация:
spark_gpu_config = {
# RAPIDS plugin
"spark.plugins": "com.nvidia.spark.SQLPlugin",
"spark.rapids.sql.enabled": "true",
# GPU ресурсы
"spark.executor.resource.gpu.amount": "1",
"spark.task.resource.gpu.amount": "1",
# Память на A100 80GB
"spark.executor.memory": "16g",
"spark.memory.offHeap.enabled": "true",
"spark.memory.offHeap.size": "8g",
"spark.executor.memoryOverhead": "8g",
"spark.rapids.memory.gpu.allocator": "ARENA",
"spark.rapids.memory.gpu.maxAllocFraction": "0.80",
# Размер батча: оптимально для 1.5B строк на 8 Executor
# 1.5B / 8 Executor / 200 партиций = 938K строк/батч
"spark.rapids.sql.reader.batchSizeRows": "1000000",
"spark.rapids.sql.reader.batchSizeBytes": str(2 * 1024**3),
# GPU Shuffle: UCX с GPUDirect если есть InfiniBand
"spark.shuffle.manager":
"com.nvidia.spark.rapids.spark35.RapidsShuffleManager",
# Нативный spill
"spark.rapids.memory.gpu.spillBatchSizeBytes": str(512 * 1024**2),
"spark.rapids.memory.host.spillStorageSize": str(16 * 1024**3),
}
# Проверка: используем RAPIDS Qualification Tool на event logs
# Qualification Score должен быть > 70 для данного профиля задачи
Ожидаемый экономический эффект:
Baseline CPU (8 × r6i.32xlarge: 128 vCPU, 1024 GB RAM):
$8.064/час × 8 нод × 4 часа = $258.05/запуск
RAPIDS GPU (8 × p4d.24xlarge: 8 × A100, 320 GB RAM):
$32.77/час × 4 нод × 0.5 часа = $65.54/запуск
(при ускорении 8×: 4 часа → 30 минут, 4 нода достаточно)
Экономия: $192.51/запуск × 365 = $70,266/год
Как применять матрицу: практический checklst¶
Шаг 1: Профилирование текущих задач¶
Прежде чем выбирать движок, нужно понять реальный профиль нагрузки:
def profile_spark_workload(spark, query: str) -> dict:
"""Анализ характеристик запроса для выбора движка."""
import re
# Выполняем с включённым explain
df = spark.sql(query)
plan_str = df._jdf.queryExecution().executedPlan().toString()
# Считаем типы операторов в плане
operators = {
"hash_agg": len(re.findall(r"HashAggregate", plan_str)),
"sort_merge_join": len(re.findall(r"SortMergeJoin", plan_str)),
"broadcast_join": len(re.findall(r"BroadcastHashJoin", plan_str)),
"filter": len(re.findall(r"Filter", plan_str)),
"exchange": len(re.findall(r"Exchange", plan_str)),
"python_udf": len(re.findall(r"PythonEval|BatchEval", plan_str)),
"window": len(re.findall(r"WindowExec", plan_str)),
}
# Детектируем IO vs compute
has_udf = operators["python_udf"] > 0
is_agg_heavy = operators["hash_agg"] > 2
is_join_heavy = operators["sort_merge_join"] + operators["broadcast_join"] > 1
is_shuffle_heavy = operators["exchange"] > 3
profile = {
"operators": operators,
"is_udf_heavy": has_udf,
"is_compute_heavy": is_agg_heavy or is_join_heavy,
"is_shuffle_heavy": is_shuffle_heavy,
"gpu_candidate": is_compute_heavy and not has_udf,
"recommendation": (
"NO ENGINE: fix Python UDF first" if has_udf
else "RAPIDS" if is_compute_heavy
else "COMET/VELOX" if is_agg_heavy or is_join_heavy
else "CPU likely sufficient"
)
}
print(f"Plan operators: {operators}")
print(f"Recommendation: {profile['recommendation']}")
return profile
Шаг 2: Квалификационный тест перед внедрением¶
Для RAPIDS используйте официальный Qualification Tool. Для Comet и Velox можно использовать простую метрику: запустить план с spark.rapids.sql.explain=ALL (для RAPIDS) или spark.comet.explainFallback.enabled=true (для Comet) и посчитать долю нативных операторов.
# RAPIDS Qualification Tool на event logs
java -jar rapids-4-spark-tools_2.12-24.02.0.jar qualification \
--output-directory /tmp/qual-report \
--event-log /var/spark-history/app-XXXXXXXX
# Интерпретация:
# GPU Opportunity Score > 70: RAPIDS имеет смысл
# GPU Opportunity Score 50–70: считайте TCO
# GPU Opportunity Score < 50: CPU-движок лучше
Шаг 3: Бенчмарк на реальных данных (не TPC-H!)¶
import time
def compare_engines_on_real_query(spark, query: str, runs: int = 3) -> dict:
"""Бенчмарк реального запроса с разными конфигурациями."""
results = {}
for engine_name, setup_config in [
("baseline", {}),
("comet", {
"spark.plugins": "org.apache.spark.CometPlugin",
"spark.comet.enabled": "true",
}),
]:
# Применяем конфигурацию
for k, v in setup_config.items():
spark.conf.set(k, v)
times = []
for _ in range(runs):
t0 = time.time()
spark.sql(query).count()
times.append(time.time() - t0)
median = sorted(times)[runs // 2]
results[engine_name] = median
print(f"{engine_name}: {median:.2f}s")
# Сброс конфигурации
spark.conf.set("spark.comet.enabled", "false")
return results
# Используем ПРОИЗВОДСТВЕННЫЙ запрос, а не TPC-H!
production_query = """
SELECT region, product_tier, DATE_TRUNC('month', sale_date) AS month,
SUM(revenue) AS total_revenue,
COUNT(DISTINCT customer_id) AS unique_customers,
AVG(basket_size) AS avg_basket
FROM silver.sales
JOIN dim.customers USING (customer_id)
JOIN dim.products USING (product_id)
WHERE sale_date >= '2023-01-01'
GROUP BY 1, 2, 3
ORDER BY total_revenue DESC
"""
results = compare_engines_on_real_query(spark, production_query)
speedup = results["baseline"] / results["comet"]
print(f"\nSpeedup: {speedup:.2f}x")
Типичные ошибки при выборе движка¶
Ошибка 1: Выбирать по TPC-H маркетингу.
TPC-H - идеальный бенчмарк для GPU и Velox: большие таблицы, нет Python UDF, данные в Parquet, чистые числовые типы. Реальный ETL-пайплайн с JSON-источниками, Python UDF и медленным JDBC даст совсем другие результаты.
Ошибка 2: Включить движок и считать задачу решённой.
Все четыре движка имеют fallback на JVM для неподдерживаемых операторов. Без проверки df.explain() нет уверенности, что нативное ускорение реально работает.
Ошибка 3: Не учитывать стоимость операционной поддержки.
Gluten+Velox даёт 5–8× ускорение, но требует 2 недели на начальный деплой и постоянного внимания при обновлениях Spark. Comet даёт 3–5×, но деплоится за 5 минут. Для команды из 3 человек разница критична.
Ошибка 4: GPU для IO-bound задачи.
Самая дорогостоящая ошибка: купить/арендовать GPU-кластер для задачи, где 80% времени - ожидание S3 или медленного JDBC. GPU не ускорит IO.
Финальное резюме: идеального движка не существует¶
Каждый из четырёх движков решает конкретную проблему конкретного контекста:
- Comet - правильный первый шаг для open-source команды. Низкий риск, легкий деплой, разумное ускорение.
- Gluten+Velox - максимум производительности для on-premise enterprise с зрелой платформенной командой.
- Photon - лучший выбор, если вы уже на Databricks. Нулевые операционные затраты, первоклассная Delta Lake-интеграция.
- RAPIDS - единственный правильный выбор для compute-heavy ML/AI задач с доступными GPU.
Задача инженера уровня Lead/Staff - не знать правильный ответ заранее, а уметь задать правильные вопросы, собрать данные (профиль нагрузки, метрики IO vs compute, бюджет, компетенции команды) и сделать обоснованный выбор. Именно это ценится в архитектурных ревью и ADR.
Домашнее задание: Architecture Decision Record¶
Условие:
Крупная технологическая компания (маркетплейс) предоставила следующее описание задачи:
Текущая инфраструктура:
- 80 нод × 32 vCPU, 128 GB RAM (bare-metal, без GPU)
- YARN как кластерный менеджер
- Данные: 2.5 PB в HDFS, Parquet + ORC
- Spark 3.4.2, Python 3.10
SLA пайплайнов:
- Ночной Gold-расчёт: должен завершаться за 90 минут (сейчас 6 часов)
- 8 ключевых запросов с SLA, 5 аналитических запросов без SLA
Примеры SQL-запросов (из топ-5 по CPU-времени):
1. JOIN трёх таблиц по 200 GB каждая + GROUP BY 8 ключей
2. Оконные функции (LAG, LEAD, RANK) по 500M строк
3. Простой фильтр + SUM по 800M строк (нет JOIN)
4. Python UDF для бизнес-правил (занимает 40% pipeline-времени)
5. COLLECT_LIST в агрегации для построения рекомендательных наборов
Бюджетные ограничения:
- Переход на облако не рассматривается (compliance)
- Бюджет на лицензии: $0
- Бюджет на инженерные ресурсы: 1 месяц × 2 инженера
- GPU-закупка: не предусмотрена
Команда:
- 4 Data Engineers (сильные в Spark/Python, нет C++ экспертизы)
- 2 Platform Engineers (K8s + Linux, нет Big Data опыта)
Напишите Architecture Decision Record (ADR) со следующей структурой:
Раздел 1: Анализ нагрузки (1 страница)
Для каждого из 5 запросов определите: compute-bound или IO-bound? Есть ли блокирующие операторы (UDF, COLLECT_LIST)? Оцените, какой процент времени пайплайна реально ускоряется нативным движком.
Раздел 2: Выбор и обоснование движка (0.5 страницы)
Выберите один движок (или обоснованную комбинацию). Пройдите по дереву решений из урока. Объясните, почему остальные три были отклонены для данного контекста.
Раздел 3: Конфигурация памяти (0.5 страницы)
Для выбранного движка рассчитайте:
spark.executor.memoryspark.memory.offHeap.size(если применимо)- Специфичный memory pool (Velox Pool или RMM или стандартный)
spark.executor.memoryOverhead
Сумма должна точно вписываться в 128 GB RAM ноды.
Раздел 4: Риски и митигация (0.5 страницы)
Для выбранного движка опишите:
- Что произойдёт с запросами 4 и 5 (Python UDF и COLLECT_LIST)?
- Сценарий, при котором задача упадёт в полный JVM-fallback?
- Как вы обнаружите, что ускорение реально работает (explain(), логи, метрики)?
Раздел 5: Ожидаемый результат (0.25 страницы)
На основе типичных ускорений движка: сколько времени займёт пайплайн после внедрения? Уложитесь ли в SLA 90 минут? Если нет - что нужно сделать дополнительно?
Ожидаемый объём: 3–4 страницы. Формат: markdown или PDF.