Открыть сервис

Механизм выполнения Tungsten

Механизм выполнения Tungsten — это программный компонент системы управления базами данных Apache Spark, отвечающий за компиляцию, оптимизацию и исполнение запросов на языке структурированных запросов (SQL) и операций с датасетами (DataFrame/Dataset). Tungsten представляет собой набор низкоуровневых оптимизаций, направленных на повышение производительности за счёт эффективного использования аппаратных ресурсов: процессора, памяти и кэша. Разработка механизма началась в рамках проекта Spark SQL в версии 1.4 (2015 год) и впоследствии стала стандартным движком выполнения запросов в Apache Spark.

История

До внедрения Tungsten выполнение запросов в Spark основывалось на интерпретации логических планов через виртуальную машину Java (JVM). Это приводило к значительным накладным расходам: частым сборкам мусора, неэффективному использованию кэша процессора и избыточным операциям сериализации-десериализации данных. В 2014—2015 годах команда Databricks (основной разработчик Spark) приступила к созданию нового механизма, который бы минимизировал влияние JVM на производительность.

Первая версия Tungsten (Spark 1.4) включала три основные оптимизации: управление памятью в обход JVM (off-heap), кодогенерацию для циклов обработки данных и использование кэша процессора. В Spark 2.0 (2016 год) механизм был расширен поддержкой векторной обработки (whole-stage codegen), что позволило объединять несколько операторов в один скомпилированный цикл. Начиная с Spark 3.0 (2020 год), Tungsten стал частью нового движка выполнения — Adaptive Query Execution (AQE), который динамически корректирует план выполнения на основе статистики времени выполнения.

Архитектура и принцип работы

Механизм Tungsten функционирует на трёх уровнях оптимизации:

Управление памятью (Memory Management)

Традиционные приложения на JVM хранят объекты в куче (heap), что приводит к фрагментации памяти и накладным расходам на хранение заголовков объектов. Tungsten использует собственный менеджер памяти, который выделяет непрерывные блоки памяти (off-heap) через прямой доступ к операционной системе (с помощью sun.misc.Unsafe). Это позволяет:

  • Избежать сборки мусора JVM для рабочих данных.
  • Хранить данные в сыром бинарном формате (без заголовков объектов).
  • Использовать кэш процессора более эффективно за счёт последовательного доступа к памяти.

Кодогенерация (Code Generation)

Tungsten генерирует Java-байткод на этапе компиляции запроса. Вместо интерпретации каждого оператора (например, фильтрации, проекции, агрегации) отдельно, механизм создаёт единый цикл, который выполняет все операции над строками данных. Этот процесс называется whole-stage codegen. Пример: для запроса SELECT count(*) FROM t WHERE x > 10 генерируется один цикл, который читает строки, проверяет условие x > 10 и накапливает счётчик, без вызова виртуальных методов для каждого оператора.

Векторная обработка (Vectorization)

Для операций, которые сложно перевести в единый цикл (например, чтение с диска или десериализация сложных типов), Tungsten использует векторную обработку: данные обрабатываются пакетами (batch) по несколько тысяч строк за раз. Это уменьшает количество вызовов JVM и улучшает локальность кэша.

Ключевые оптимизации

1. Off-heap хранение данных

Tungsten хранит данные в бинарных столбцах (columnar format) вне кучи JVM. Это позволяет:

  • Уменьшить объём памяти на 50–70% по сравнению с объектным представлением.
  • Ускорить сериализацию/десериализацию за счёт пропуска этапа преобразования в объекты Java.
  • Обеспечить прямой доступ к данным из машинного кода.

2. Whole-stage codegen

Эта оптимизация объединяет несколько операторов (например, фильтр, проекцию, агрегацию) в один скомпилированный цикл. Результат — снижение накладных расходов на вызов методов и виртуальную диспетчеризацию. В тестах производительности Spark SQL ускорение достигало 2–5 раз по сравнению с интерпретируемым выполнением.

3. Hash-based агрегация и джойны

Tungsten использует хеш-таблицы, реализованные в off-heap памяти, с бинарными ключами и значениями. Это позволяет выполнять агрегации и соединения (join) без создания промежуточных объектов Java. Операции сортировки также оптимизированы за счёт использования сортировки слиянием (merge sort) на бинарных данных.

4. Поддержка SIMD-инструкций

Начиная с Spark 3.0, Tungsten может использовать SIMD-инструкции (Single Instruction, Multiple Data) процессора для параллельной обработки нескольких элементов данных за одну команду. Это ускоряет операции, такие как фильтрация, сравнение и хеширование.

Применение

Механизм Tungsten является основным исполнителем запросов в Apache Spark. Он используется в следующих сценариях:

  • Аналитические запросы SQL — выполнение сложных запросов с агрегациями, соединениями и подзапросами.
  • Обработка датасетов — операции с DataFrame и Dataset через Spark SQL.
  • Машинное обучение — обучение моделей на больших объёмах данных (например, в библиотеке MLlib).
  • Потоковая обработка — выполнение микропакетных (micro-batch) запросов в Spark Streaming.

Tungsten не предназначен для прямого использования разработчиками; он работает как внутренний компонент Spark, автоматически активируемый при выполнении запросов.

Влияние на производительность

Внедрение Tungsten привело к значительному улучшению производительности Spark SQL. По данным Databricks, в тестах TPC-DS (набор бенчмарков для систем обработки данных) ускорение составило от 2 до 10 раз по сравнению с версиями Spark до 1.4. Основные факторы:

  • Снижение нагрузки на сборщик мусора JVM (на 80–90% в некоторых сценариях).
  • Уменьшение времени сериализации данных (до 5 раз).
  • Улучшение использования кэша процессора (коэффициент промахов кэша снижается на 30–50%).

Критика и ограничения

Несмотря на преимущества, механизм Tungsten имеет ряд ограничений:

  • Сложность отладки — сгенерированный байткод трудно анализировать, что усложняет диагностику ошибок.
  • Зависимость от платформы — оптимизации, основанные на sun.misc.Unsafe, работают только на JVM от Oracle/OpenJDK и могут быть недоступны на других реализациях (например, IBM J9).
  • Потребление памяти — off-heap память не контролируется JVM, что может привести к утечкам, если не настроены лимиты (параметр spark.memory.offHeap.size).
  • Ограниченная поддержка сложных типов — для пользовательских типов данных (UDF) кодогенерация может быть неэффективной или отключаться.

Сравнение с другими механизмами

ХарактеристикаTungsten (Spark)Photon (Databricks)Velox (Meta)
ТипКодогенерация + off-heapВекторное выполнение на C++Векторное выполнение на C++
ПлатформаJVMНативная (C++)Нативная (C++)
Скорость2–5x против интерпретации3–10x против Tungsten2–4x против интерпретации
ИнтеграцияВстроен в SparkВнешний движокВнешний движок

Tungsten остаётся стандартным механизмом для Spark, но в 2023 году Databricks представила Photon — нативный движок на C++, который превосходит Tungsten по производительности за счёт полного отказа от JVM. Тем не менее, Tungsten продолжает использоваться в открытой версии Spark.

Интересные факты

  • Название «Tungsten» (вольфрам) выбрано из-за высокой температуры плавления этого металла (3422 °C), что символизирует устойчивость к нагрузкам.
  • Механизм был разработан Рейнольдом Сингом (Reynold Xin) и его командой в Databricks.
  • В Spark 3.4 (2023 год) добавлена поддержка автоматического выбора между off-heap и on-heap памятью на основе профиля нагрузки.

Источники

  • Apache Spark Documentation: «Tungsten Execution Engine» (2015–2024).
  • Xin, R., et al. «Spark SQL: Relational Data Processing in Spark» (2015).
  • Databricks Engineering Blog: «Project Tungsten: Bringing Apache Spark Closer to Bare Metal» (2015).
  • Armbrust, M., et al. «Spark SQL: Relational Data Processing in Spark» (2016).
  • Apache Spark JIRA: SPARK-12795, SPARK-14079, SPARK-27376.

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →