Выбор движка: Comet vs Velox vs RAPIDS vs Photon - матрица по кейсам

Выбор движка: Comet vs Velox vs RAPIDS vs Photon - матрица по кейсам

optimization

Эра пост-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.24xlarge AWS с 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.memory
  • spark.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.