УДК 004.428.4

Интеграция кода на Rust в ClickHouse

Болонин Денис Константинович – студент Факультета компьютерных наук Национального исследовательского университета «Высшая школа экономики».

Аннотация: В современном мире существует огромное число языков программирования, каждый из которых предназначен для различных областей применения, имеет свои преимущества и недостатки. В данный момент большая часть кода системы управления базами данных (СУБД) ClickHouse [4] написана на языке C++ – он обеспечивает высокую производительность и, в отличие от языка C, является языком более высокого уровня. В последние годы набирает популярность язык Rust, сравнимый с C++ по быстродействию, и обладающий преимуществами в более надежной работе с памятью. С использованием языка Rust разработано большое количество библиотек, некоторые из которых можно было бы применить в ClickHouse, но для этого необходимо обеспечить возможность работы кода на обоих языках в одном проекте. В этой работе на примере библиотеки хеш-функции BLAKE3 будет рассмотрена возможность интеграции кода на языке Rust в ClickHouse.

Ключевые слова: СУБД, ClickHouse, C++, Rust, система сборки, хеш-функция.

1. Введение

Многие библиотеки, разрабатываемые в наше время, предоставляют удобные интерфейсы для большинства популярных языков программирования, что позволяет использовать их преимущества в различных проектах, имеющих разнообразные наборы технологий и подходов к разработке. В тех случаях, когда для удобной и быстрой библиотеки разработчики не предоставляют интерфейс для взаимодействия с другими языками, проекты вынуждены обращаться к более старым, менее производительным альтернативам.

В данный момент кодовая база СУБД ClickHouse состоит из нескольких языков программирования, однако основным языком для самой СУБД является C++. В связи с этим, возможности интеграции библиотек на других компилируемых языках достаточно ограничены. Одним из таких языков является схожий по производительности с C/C++ язык Rust. Существует достаточно большое количество полезных библиотек, написанных на нем, поэтому следует рассмотреть возможность их использования в ClickHouse и предоставить её разработчикам.

 В рамках данного проекта была поставлена следующая цель – обеспечить возможность интеграции кода на Rust в ClickHouse. Были выделены следующие задачи:

  1. Ознакомиться с возможностями совместного использования языков C++ и Rust в одном проекте. Существуют различные способы использования библиотек на Rust вместе с другими языками, поэтому требуется подобрать наиболее удобный и подходящий для C++.
  2. Модифицировать систему сборки ClickHouse таким образом, чтобы библиотеки на Rust могли полноценно собираться вместе с остальной кодовой базой ClickHouse. ClickHouse использует для конфигурации сборочной системы утилиту CMake, поэтому требуется переписать файлы CMake так, что они будут правильно воспринимать код на Rust как часть бинарного файла при сборке.
  3. В качестве примера интеграции библиотеки на Rust, добавить библиотеку хеш-функции BLAKE3 в ClickHouse. Это позволит не только предоставить пользователям более производительную и надежную хеш-функцию, но и даст в дальнейшем протестировать качество интеграции Rust с кодом ClickHouse.
  4. Протестировать возможность сборки ClickHouse вместе с добавленной библиотекой. После внедрения библиотеки всегда необходимо проверять, не изменились ли какие-либо зависимости и не появились ли конфликты при сборке. Кроме того, не все архитектуры могут поддерживать сборку той или иной библиотеки, что также требует тестирования.
  5. Протестировать работоспособность и производительность функции BLAKE3 в ClickHouse, произвести сравнительный анализ с существующим функционалом. Измерить время работы, затрачиваемое на обеспечение совместимости Rust и C++. Поскольку многие методы могут потребовать преобразований типов, то время, затрачиваемое на преобразования, следует минимизировать.
  6. Подготовить подробную документацию для последующей разработки и пользования по двум направлениям: интеграции новых библиотек на Rust и работе с BLAKE

2. Существующие решения

В этом разделе разберем предусмотренные разработчиками Rust решения для интеграции с другими языками, а также другие криптографические хеш-функции, аналогичные BLAKE3, уже использующиеся в ClickHouse.

Для начала стоит рассказать о существующих подходах к интеграции Rust с C++. Для того, чтобы функции и методы внутри проекта на C++ имели возможность вызывать методы из кода на Rust, необходимо предоставить сборочной системе проекта заголовочные файлы C/C++, а также непосредственно саму скомпилированную Rust-библиотеку. Разумным подходом к решению первой проблемы является выбор наиболее подходящей библиотеки для автогенерации заголовочных файлов. Изначально удалось найти два репозитория одной библиотеки: rusty-cheddar - оригинальный репозиторий и moz-cheddar – форк компании Mozilla. От них сразу пришлось отказаться, поскольку обе библиотеки очень давно не обновлялись и помещены авторами в архив. Продолжив поиски, я нашел в документации Rust [5] упоминания библиотек bindgen и cbindgen [3], первая из которых позволяет использовать функции C/C++ в Rust, а вторая генерирует заголовочные файлы C/C++ из кода на Rust. В итоге выбор однозначно пал на cbindgen, поскольку эта библиотека все еще обновляется и позволяет делать именно то, что необходимо для интеграции Rust в код на C++. Компиляция кода на Rust будет производиться обычным образом с использованием сборочной системы Rust – cargo.

Библиотека BLAKE3 [1] в рамках данного проекта была выбрана в качестве примера для интеграции кода на Rust в ClickHouse. В ClickHouse уже есть большое количество интегрированных хеш-функций – SHA1, различные версии SHA2, MD5 и другие хеш-функции. Основными причинами для внедрения BLAKE3 являются:

  • скорость работы BLAKE3 - разработчики приводят результаты тестирований, в которых производительность их хеш-функции превышает многие аналоги до 6 раз. А поскольку ClickHouse стремится быть быстрой СУБД, то поддержка использования настолько быстрой хеш-функции была бы полезной для пользователей. Обратимся к результатам тестирований библиотеки разработчиками (рис 1).

1

Рисунок 1. Графики производительности различных хеш-функций в сравнении с BLAKE3 [2].

Как можно заметить, BLAKE3 увеличивает свое преимущество по времени с ростом объема данных, подаваемых ей на вход. Разрыв в 6 раз по сравнению с SHA256 появляется уже для входов объемом в 4КБ и в дальнейшем нарастает. В то же время, стоит отметить, что в ClickHouse зачастую будут хешироваться строки меньших размеров, вход, имеющий объем 1КБ уже считается достаточно крупным. Поэтому, исходя из данных разработчиков BLAKE3, после интеграции от BLAKE3 можно ожидать в 2-3 раза большей производительности по сравнению с SHA256 и аналогичными функциями.

  • разработчики BLAKE3 указывают на её более высокую надежность по сравнению с MD5, SHA1 и устойчивость к атаке удлинением сообщения, в отличие от SHA

Таким образом, внедрение библиотеки BLAKE3 позволит получить хорошую альтернативу некоторым уже имеющимся в ClickHouse хеш-функциям.

3. Метрики для тестирования

3.1. Метрика интеграции Rust с ClickHouse

В целом, проверить насколько качественно работает интеграция Rust с C++ напрямую довольно непростая задача. Пожалуй, единственная метрика – сам факт интеграции кода и его успешной работы. Получить результат по данной метрике можно только путем непосредственного тестирования интегрированной функции.

3.2. Метрики производительности хеш-функции BLAKE3

Поскольку в ClickHouse уже существовала система для тестирования производительности, в том числе и хеш-функций, применим её для тестирования BLAKE3. В ней для каждой хеш-функции используются 3 набора строковых данных. Из них два набора – короткие строки. В них входят пустая строка и небольшая строка, генерирующаяся из случайного десятизначного числа. Третий набор состоит из длинной 1025-символьной строки. В каждом запросе вычисление происходит не один раз, а многократно, что позволяет получить лучшую статистическую выборку и усреднённые данные по каждому набору.

3.3. Оценка затрат на обеспечение совместимости C++ и Rust

Помимо обычных тестирований производительности для оценки затрат на обеспечение совместимости можно провести измерения при помощи программного обеспечения perf, которые покажут, на какие вызовы затрачивается время при выполнении операций. Это даст достаточно хорошую процентную оценку для затрат на совместимость.

4. Реализация

Первым шагом реализации поставленной цели работы стала перенастройка системы сборки ClickHouse таким образом, чтобы она адекватно воспринимала библиотеки, написанные на Rust. Для этого в виде вызываемой самописной CMake-функции была добавлена возможность использования cargo - системы сборки Rust, для компиляции библиотек. Позднее, от самописной функции пришлось отказаться в пользу библиотеки Corrosion-rs, предоставляющей более удобный интерфейс в CMake, а также имеющей более широкие возможности настройки сборки.

Вторым шагом стало добавление C++-совместимых методов непосредственно в части кода, связанные с хешированием. BLAKE3 была добавлена и зарегистрирована как функция для SQL запросов путём создания метода apply, применяющего BLAKE3 к байтовым данным. Было опробовано несколько вариаций метода apply, отличавшихся своей структурой и способом связки Rust-методов с методами C++. В первой вариации каждый отдельный метод BLAKE3, требуемый в apply, получил свой метод-прослойку, обеспечивший совместимость с C++. Основной причиной для их внедрения стало то, что не все структуры, используемые в Rust и BLAKE3 возможно корректно представить в заголовочном файле C/C++. Прослойки принимают данные в совместимых с C/C++ типах данных, а далее трансформируют их в данные, понятные функциям и методам библиотеки BLAKE3. Полученный результат с небольшими исправлениями был полностью работоспособным, однако не обеспечивал производительности, схожей с тестами разработчиков BLAKE3. В частности, из-за специфики работы с памятью между языками программирования приходилось конвертировать объекты из C-совместимых форматов в привычные для Rust форматы и проводить обратные операции при возврате результатов.

На текущий момент времени все методы-прослойки объединены в один, сразу же вызываемый из apply и дающий в ответ готовый хеш. Это позволило производить конвертации объектов только один раз. Из-за снижения количества вызовов промежуточных функций для конвертации C- и Rust-совместимых форматов работа BLAKE3 в ClickHouse ускорилась. Таким образом, мы добились корректной работы кода на Rust в ClickHouse и приемлемого быстродействия библиотеки BLAKE3. Все базовые наборы тестов ClickHouse были успешно пройдены, а также был добавлен новый тест, отдельно проверяющий правильность работы хеш-функции.

2

Рисунок 2. Пример SQL запроса в ClickHouse, применяющего BLAKE3 к данным.

Кроме того, к реализации подготовлена детальная документация, описывающая процесс интеграции библиотек на Rust в ClickHouse. Перечислены все шаги интеграции, описаны задействованные при ней функции CMake, необходимые действия для написания сборочного скрипта новой библиотеки, а также указаны возможные решения возникающих при интеграции проблем. Также внесены изменения в документацию для хеш-функций, добавлена и описана новая функция BLAKE3.

Возникшие проблемы

Заметной проблемой стала работа MemorySanitizer для методов-прослоек. При проверке памяти на стороне языка Rust для санитайзера было неочевидно происхождение данных, лежащих во входных переменных методов. Зачастую, любые операции с ними приводили к ложному срабатыванию, указывавшему на обращение к неинициализированной памяти. Были испробованы несколько стандартных для ситуации ложного срабатывания MemorySanitizer вариантов решения проблемы, как на стороне Rust, так и на стороне C++, но они не устранили неполадку полностью. После обсуждения этой проблемы с руководителем проекта, было решено вставить особый метод, который бы явно инициализировал память на стороне Rust. Этот метод более медленный, чем обычный, но его компиляция и использование происходят только в сборках с MemorySanitizer. Такой шаг позволил не отключать тесты памяти для библиотеки BLAKE3, и соответственно, для всех других тестов, которые в будущем могут зависеть от неё.

Кроме того, много времени ушло на работу со сборкой под нестандартные архитектуры. Для каждой из них пришлось проработать требуемые для сборки Rust библиотеки, а также скорректировать сборочную систему для использования корректных тулчейнов в каждом отдельном случае.

С кодом, разработанным в рамках данной работы можно ознакомиться в следующих пул-реквестах:

  • https://github.com/ClickHouse/ClickHouse/pull/33435;
  • https://github.com/ClickHouse/ClickHouse/pull/42073;
  • https://github.com/ClickHouse/ClickHouse/pull/36462;
  • https://github.com/ClickHouse/ClickHouse/pull/41913;
  • https://github.com/ClickHouse/ClickHouse/pull/36999.

Все перечисленные пул-реквесты в настоящее время находятся в продакшене, начиная с релиза ClickHouse release 22.10, 2022-10-25.

5. Результаты тестирования

Основная задача по интеграции библиотеки завершена, по результатам базового набора тестов никаких ошибок не появляется. В то же время интересно разобрать, насколько эффективно работает интегрированная библиотека и как хорошо её производительность в ClickHouse соответствует оценкам её разработчиков.

Данные тестов получены на обычном ПК, с использованием стандартных распространенных комплектующих. Тесты проводились на собранном локально бинарном файле ClickHouse, при сборке не указывались никакие дополнительные флаги.

Напомним, что в качестве теста на производительность, библиотека будет вызываться через SQL запросы для многократной обработки трех наборов данных – пустой строки, короткой 10-символьной и длинной 1025-символьной строк.

5.1. Тестирование производительности

Сравним производительность хеш-функции BLAKE3 с другими аналогичными хеш-функциями, применяемыми в ClickHouse: SHA256, SHA224 и MD5. Для удобства сведем все результаты тестирования в единую таблицу 1.

Таблица 1. Результаты тестов производительности хеш-функций.

Хеш-функция

Данные

Время работы, с

BLAKE3

Строка, 0 символов, 10^6 запусков

0,0871

Строка, 10 символов, 10^6 запусков

0,1024

Строка, 1025 символов, 10^5 запусков

0,1444

SHA224

Строка, 0 символов, 10^6 запусков

0,1999

Строка, 10 символов, 10^6 запусков

0,2061

Строка, 1025 символов, 10^5 запусков

0,3390

SHA256

Строка, 0 символов, 10^6 запусков

0,1977

Строка, 10 символов, 10^6 запусков

0,2048

Строка, 1025 символов, 10^5 запусков

0,3352

MD5

Строка, 0 символов, 10^6 запусков

0,1106

Строка, 10 символов, 10^6 запусков

0,1149

Строка, 1025 символов, 10^5 запусков

0,1841

Как можно заметить, BLAKE3 в итоге производительнее всех аналогичных хеш-функций, имеющихся на данный момент в ClickHouse. В случае с различными версиями SHA2 разрыв составляет более 2 раз, в то же время MD5 отстаёт от BLAKE3 не так сильно, но при этом имеет характерные недостатки по своей надежности.

В завершение раздела, представим данные тестов в более наглядном формате – гистограмме (рис. 3).

3

Рисунок 3. Гистограмма результатов тестов производительности хеш-функций.

5.2. Оценка затрат на совместимость

Будем производить оценку оверхеда при помощи perf top – приложение покажет, какой процент времени занимает обработка тех или иных вызовов функций. В качестве теста был запущен запрос, аналогичный запросу для 1025-символьной строки, рассчитанный на 10^7 запусков функции. По результатам теста данные были переведены в графическое представление Flamegraph (рис. 4).

4

Рисунок 4. График Flamegraph для SQL-запроса, вычисляющего BLAKE3.

Из полученного графика можно выделить три типа операций, происходящих во время исполнения запроса – операции самой СУБД, генерация хеша библиотекой BLAKE3, а также паразитные методы, которые затрачивают время только на конвертации типов данных C/C++ и Rust. В нашем случае СУБД выполняет некие операции в начале и конце запроса, при этом основное время паразитных методов попадает на момент после работы BLAKE3 и перед финальными операциями самой СУБД. Паразитные методы суммарно отнимают 1,15% всего времени запроса и занимаются записью готового хеша в корректный формат.

По итогам тестирования можно заключить, что несмотря на оверхед, имеющийся из-за необходимости обеспечения совместимости C++ и Rust BLAKE3 сохраняет высокую производительность и работает значительно быстрее аналогичных функций, реализованных в ClickHouse.

6. Заключение

В результате выполнения проекта в ClickHouse реализована возможность интеграции библиотек на языке Rust, а также в качестве примера добавлена библиотека хеш-функции BLAKE3. Это показывает, что даже в проект, существующий и разрабатываемый на протяжении долгого времени, возможно встраивать новые, современные языки программирования и библиотеки, написанные на них, позволяя использовать в проекте новое ПО и алгоритмы, при этом не изменяя старый код и его зависимости.

Список литературы

  1. BLAKE3 docs [Электронный ресурс] / BLAKE3team. Режим доступа: https://docs.rs/blake3/1.3.0/blake3/index.html, свободный. (дата обращения: 03.05.2022).
  2. BLAKE3 paper [Электронный ресурс] / BLAKE3team. Режим доступа: https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf, свободный. (дата обращения: 06.05.2022).
  3. cbindgen User Guide [Электронный ресурс] / eqrion. Режим доступа: https://github.com/eqrion/cbindgen/blob/master/docs.md, свободный. (дата обращения: 25.04.2022).
  4. ClickHouse Documentation [Электронный ресурс] / ClickHouse, Inc. Режим доступа: https://clickhouse.tech/docs/en, свободный. (дата обращения: 05.05.2022).
  5. The Embedded Rust Book [Электронный ресурс] / Embedded WG. Режим доступа: https://docs.rust-embedded.org/book/intro/index.html, свободный. (дата обращения: 27.12.2021).

Интересная статья? Поделись ей с другими: