Использование flameGraph

Для глубокого анализа производительности и отладки запросов ADQM/ClickHouse предоставляет системную таблицу system.trace_log, в которую сохраняются трассировки стека, собираемые профилировщиком запросов. Например, с помощью данных этой таблицы можно понять почему запрос выполняется слишком медленно или не так, как ожидалось — изучить последовательность операций в трассировке и найти конкретные функции запроса, которые вызывают проблемы (требуют больше всего времени или ресурсов на выполнение). Эта информация может быть полезна для оптимизации запроса и повышения его производительности.

Для визуального представления трассировок стека в виде flame-графика можно использовать агрегатную функцию flameGraph. Эта функция возвращает массив строк по данным таблицы system.trace_log, который затем можно передать в утилиту flamegraph.pl (или аналогичный инструмент) для построения flame-графика.

Синтаксис функции flameGraph

Синтаксис функции flameGraph в общем виде:

flameGraph(<trace>, [<size>], [<ptr>])

где:

  • <trace> — трассировка стека;

  • <size> — количество выделенной памяти при профилировании по памяти (тип трассировки Memory, MemorySample или MemoryPeak), по умолчанию — 1;

  • <ptr> — адрес выделенной памяти, по умолчанию — 0.

Построение flame-графика

Чтобы построить профиль запроса в виде flame-графика на основе информации из таблицы system.trace_log, следуйте приведенным ниже инструкциям.

Предварительно на хостах ADQM необходимо настроить профилировщик запросов ADQM и подготовить инструменты для отрисовки flame-графиков:

  1. Включите профилировщик запросов и настройте его с помощью параметров query_profiler_* и memory_profiler_* (см. Session Settings в документации ClickHouse). Эти параметры можно устанавливать на уровне пользователя ADQM, сессии clickhouse-client или конкретного запроса, который будет профилироваться (подробнее в статье Управление настройками пользователя). Например, рекомендуется назначить параметрам query_profiler_real_time_period_ns и/или query_profiler_cpu_time_period_ns значение 10000000 для трассировки выполняемых функций C++.

  2. Установите пакет adqm-clickhouse-debuginfo. Убедитесь, что настройка allow_introspection_functions активирована на уровне пользователя, который будет подключаться к ADQM и использовать функцию flameGraph. Эту опцию также можно включать на уровне сессии clickhouse-client:

    • в пакетном режиме:

      $ clickhouse-client --allow_introspection_functions=1 -q "..." ...
    • в интерактивном режиме:

      SET allow_introspection_functions = 1;
  3. Скачайте скрипт flamegraph.pl на хосты ADQM и дайте разрешение на его выполнение (команда chmod +x). Предварительно проверьте, что на хостах установлен Perl.

Теперь можно использовать функцию flameGraph и утилиту flamegraph.pl, чтобы получить профиль конкретного запроса в виде flame-графика. Например, следующая команда создает flame-график (интерактивный SVG-файл с именем <flame_cpu>), визуализирующий трассировки стека по времени CPU для запроса с идентификатором <query_id> (<path_to> в данном примере — путь к скрипту flamegraph.pl на хосте ADQM):

$ clickhouse-client \
    -q "SELECT arrayJoin(flameGraph(arrayReverse(trace))) \
        FROM system.trace_log \
        WHERE trace_type = 'CPU' AND query_id = '<query_id>'" \
    | <path_to>/flamegraph.pl  > <flame_cpu>.svg

Пример

Данный пример показывает, как с помощью flame-графика можно посмотреть, какие функции и в каком порядке вызываются при выполнении запроса.

  1. Создайте таблицу без первичного ключа с двумя столбцами — данные в первом столбце будут сжиматься по алгоритму LZ4, второй столбец будет хранить данные без сжатия:

    CREATE TABLE test_table (a Int64 CODEC(LZ4), b Int64 CODEC(NONE)) ENGINE = MergeTree ORDER BY tuple();
  2. Вставьте в таблицу 100 миллионов строк с тестовыми значениями:

    INSERT INTO test_table SELECT 1, 1 FROM numbers(100000000);

    С помощью следующего запроса можно посмотреть размер хранимых данных и увидеть разницу между столбцами:

    SELECT
        name,
        formatReadableSize(data_uncompressed_bytes) AS uncompressed_size,
        formatReadableSize(data_compressed_bytes) AS compressed_size,
        round(data_uncompressed_bytes / data_compressed_bytes, 2) AS ratio
    FROM system.columns
    WHERE table = 'test_table';
       ┌─name─┬─uncompressed_size─┬─compressed_size─┬─ratio─┐
    1. │ a    │ 762.94 MiB        │ 3.45 MiB        │ 221.4 │
    2. │ b    │ 762.94 MiB        │ 763.23 MiB      │     1 │
       └──────┴───────────────────┴─────────────────┴───────┘
  3. Выполните два отдельных запроса для вычисления суммы значений в каждом столбце:

    SELECT sum(a) FROM test_table;
    Query id: adde6caf-60c5-4820-8bb3-5f75a8edb98e
    
       ┌────sum(a)─┐
    1. │ 100000000 │ -- 100.00 million
       └───────────┘
    
    1 row in set. Elapsed: 0.072 sec. Processed 100.00 million rows, 800.00 MB (1.39 billion rows/s., 11.14 GB/s.)
    Peak memory usage: 7.92 MiB.
    SELECT sum(b) FROM test_table;
    Query id: 8491a039-be0d-4a17-9130-ec286dfee039
    
       ┌────sum(b)─┐
    1. │ 100000000 │ -- 100.00 million
       └───────────┘
    
    1 row in set. Elapsed: 0.363 sec. Processed 100.00 million rows, 800.00 MB (275.73 million rows/s., 2.21 GB/s.)
    Peak memory usage: 14.51 MiB.
  4. Постройте flame-график по загрузке CPU для первого запроса:

    $ clickhouse-client \
        -q "SELECT arrayJoin(flameGraph(arrayReverse(trace))) \
            FROM system.trace_log \
            WHERE trace_type = 'CPU' AND query_id = 'adde6caf-60c5-4820-8bb3-5f75a8edb98e'" \
        | flamegraph.pl  > flamegraph_cpu_1.svg
    Flame-график загрузки CPU при вычислении суммы значений в столбце, для которого применяется кодек сжатия данных
    Flame-график загрузки CPU при вычислении суммы значений в столбце, для которого применяется кодек сжатия данных

    На flame-графике показан полный стек вызываемых функций C++. Например, можно увидеть, что в процессе чтения данных выполняется декомпрессия (вызываются функции decompress).

  5. Постройте flame-график для второго запроса:

    $ clickhouse-client \
        -q "SELECT arrayJoin(flameGraph(arrayReverse(trace))) \
            FROM system.trace_log \
            WHERE trace_type = 'CPU' AND query_id = '8491a039-be0d-4a17-9130-ec286dfee039'" \
        | flamegraph.pl  > flamegraph_cpu_2.svg

    По графику видно, что в данном случае также используется класс CompressedReadBufferBase, но он уже не вызывает функции декомпрессии (так как данные считываются из столбца, где не применяется сжатие).

    Flame-график загрузки CPU для запроса чтения данных из столбца без декомпрессии
    Flame-график загрузки CPU для запроса чтения данных из столбца без декомпрессии
Нашли ошибку? Выделите текст и нажмите Ctrl+Enter чтобы сообщить о ней