Механизм выполнения 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 против Tungsten | 2–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 →