Skip to main content
Развертывания ClickHouse для обсервабилити неизбежно связаны с большими объёмами данных, которыми необходимо управлять. ClickHouse предлагает ряд возможностей для управления данными.
ClickStack поставляется с оптимизированной схемой по умолчаниюClickStack предоставляет готовые схемы для журналов, трассировки и метрик, которые используют новейшие возможности ClickHouse (текстовые индексы для полнотекстового поиска и поиска по ключам в Map, материализованные столбцы и ALIAS-массивы для фильтрации при прямом чтении, поиск строк по номеру блока) и были протестированы бенчмарками, чтобы обеспечивать высокую производительность из коробки для нагрузок журналирования и трассировки. Используйте их как отправную точку для собственного проектирования.

Партиции

Партиционирование в ClickHouse позволяет логически разделять данные на диске по столбцу или SQL-выражению. Благодаря такому логическому разделению с каждой партицией можно работать независимо, например удалять её. Это позволяет эффективно перемещать партиции, а значит и подмножества данных, между уровнями хранения по времени или удалять устаревшие данные/эффективно удалять данные из кластера. Партиционирование задаётся для таблицы при её первоначальном определении с помощью условия PARTITION BY. Это условие может содержать SQL-выражение для любого столбца или набора столбцов, результат которого определяет, в какую партицию будет направлена строка. Части данных логически связаны (через общий префикс имени папки) с каждой партицией на диске и могут запрашиваться изолированно. В примере ниже схема otel_logs по умолчанию использует партиционирование по дням с выражением toDate(Timestamp). По мере вставки строк в ClickHouse это выражение вычисляется для каждой строки, и данные направляются в соответствующую партицию, если она уже существует (если строка для этого дня первая, партиция будет создана).
С партициями можно выполнять ряд операций, включая резервные копии, операции со столбцами, мутации для изменения/удаления данных по строкам) и очистку индексов (например, вторичных индексов). Например, предположим, что таблица otel_logs разбита на партиции по дням. Если она заполнена структурированным набором журнальных данных, она будет содержать данные за несколько дней:
Текущие партиции можно посмотреть с помощью простого запроса к системной таблице:
У нас может быть и другая таблица — otel_logs_archive, которую мы используем для хранения более старых данных. Данные можно эффективно перемещать в эту таблицу по партициям (это лишь изменение метаданных).
В отличие от других методов, для которых потребовались бы INSERT INTO SELECT и перезапись данных в новую целевую таблицу.
Перемещение партицийДля перемещения партиций между таблицами необходимо соблюдение нескольких условий; в частности, таблицы должны иметь одинаковые структуру, ключ партиционирования, первичный ключ и индексы/проекции. Подробные сведения о том, как указывать партиции в ALTER DDL, можно найти здесь.
Кроме того, данные можно эффективно удалять на уровне партиций. Это значительно менее затратно по ресурсам, чем альтернативные методы (мутации или легковесные удаления), поэтому этому варианту следует отдавать предпочтение.
Эта возможность задействуется механизмом TTL при использовании настройки ttl_only_drop_parts=1. Подробнее см. в разделе Управление данными с помощью TTL.

Применение

Выше показано, как данные можно эффективно перемещать и обрабатывать по партициям. На практике в сценариях обсервабилити операции с партициями чаще всего используются в двух случаях:
  • Многоуровневые архитектуры - перемещение данных между уровнями хранения (см. Уровни хранения), что позволяет строить архитектуру «горячего» и «холодного» хранения.
  • Эффективное удаление - когда данные достигают заданного TTL (см. Управление данными с помощью TTL)
Ниже мы подробно рассмотрим оба случая.

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

Хотя партиции могут помочь повысить производительность запросов, это сильно зависит от характера доступа к данным. Если запросы затрагивают только несколько партиций (в идеале — одну), производительность может улучшиться. Обычно это имеет смысл только в том случае, если ключ партиционирования не входит в первичный ключ и фильтрация выполняется по нему. Однако запросы, которым нужно охватить много партиций, могут работать хуже, чем без партиционирования (поскольку частей может оказаться больше). Преимущество обращения к одной партиции будет еще менее заметным или вовсе исчезнет, если ключ партиционирования уже находится в начале первичного ключа. Партиционирование также можно использовать, чтобы оптимизировать запросы GROUP BY, если значения в каждой партиции уникальны. Однако в общем случае следует убедиться, что первичный ключ оптимизирован, и рассматривать партиционирование как метод оптимизации запросов только в исключительных случаях, когда характер доступа предполагает обращение к определенному предсказуемому подмножеству данных, например партиционирование по дням, когда большинство запросов выполняется по данным за последний день. Здесь приведен пример такого поведения.

Управление данными с помощью TTL (Time-to-live)

Time-to-Live (TTL) — крайне важная возможность в решениях для обсервабилити на базе ClickHouse, обеспечивающая эффективное хранение и управление данными, особенно с учетом того, что постоянно генерируются огромные объемы данных. Использование TTL в ClickHouse позволяет автоматически удалять устаревшие данные по истечении заданного срока, обеспечивая оптимальное использование хранилища и поддерживая производительность без ручного вмешательства. Эта возможность необходима для того, чтобы база данных оставалась компактной, затраты на хранение снижались, а запросы выполнялись быстро и эффективно за счет работы с наиболее актуальными и свежими данными. Кроме того, TTL помогает соблюдать политики хранения данных благодаря системному управлению их жизненным циклом, что повышает общую устойчивость и масштабируемость решения для обсервабилити. TTL можно задавать в ClickHouse как на уровне таблицы, так и на уровне столбца.

TTL на уровне таблицы

Схема по умолчанию и для журналов, и для трассировок включает TTL, чтобы данные удалялись по истечении заданного срока. Это указывается в экспортере ClickHouse с помощью ключа ttl, например.
Этот синтаксис в настоящее время поддерживает синтаксис Golang Duration. Мы рекомендуем использовать h и следить за тем, чтобы значение соответствовало периоду партиционирования. Например, если таблица партиционируется по дням, убедитесь, что значение кратно количеству дней, например 24h, 48h, 72h. Это автоматически обеспечит добавление в таблицу предложения TTL, например при ttl: 96h.
По умолчанию данные с истекшим TTL удаляются, когда ClickHouse объединяет части данных. Когда ClickHouse обнаруживает, что срок TTL данных истек, он выполняет внеплановое слияние.
TTL по расписаниюTTL применяется не сразу, а по расписанию, как указано выше. Настройка таблицы MergeTree merge_with_ttl_timeout задает минимальную задержку в секундах перед повторным выполнением слияния с delete TTL. Значение по умолчанию — 14400 секунд (4 часа). Но это лишь минимальная задержка: до запуска TTL-слияния может пройти больше времени. Если значение слишком мало, будет выполняться много внеплановых слияний, которые могут потреблять значительные ресурсы. Принудительно применить TTL можно командой ALTER TABLE my_table MATERIALIZE TTL.
**Важно: мы рекомендуем использовать настройку ttl_only_drop_parts=1 ** (она применяется в схеме по умолчанию). Когда эта настройка включена, ClickHouse удаляет часть целиком, если в ней истекли все строки. Удаление частей целиком вместо частичной очистки строк с истекшим TTL (которая выполняется с помощью ресурсоемких мутаций при ttl_only_drop_parts=0) позволяет использовать меньшие значения merge_with_ttl_timeout и снижает влияние на производительность системы. Если данные партиционированы по той же единице, по которой выполняется истечение TTL, например по дням, части естественным образом будут содержать данные только из заданного интервала. Это гарантирует, что ttl_only_drop_parts=1 можно будет применять эффективно.

TTL на уровне столбца

В приведённом выше примере срок хранения данных задаётся на уровне таблицы. Вы также можете настроить истечение срока хранения данных на уровне столбца. По мере старения данных это можно использовать для удаления столбцов, чья ценность при расследовании не оправдывает затраты ресурсов на их хранение. Например, мы рекомендуем сохранять столбец Body на случай, если будут добавлены новые динамические метаданные, которые не были извлечены во время вставки, например новая метка Kubernetes. Через некоторое время, например через 1 месяц, может стать очевидно, что эти дополнительные метаданные не приносят пользы, — и тогда хранение столбца Body теряет смысл. Ниже показано, как удалить столбец Body через 30 дней.
Чтобы задать TTL на уровне столбца, пользователям нужно определить собственную схему. Это нельзя указать в OTel collector.

Повторное сжатие данных

Хотя для наборов данных обсервабилити мы обычно рекомендуем ZSTD(1), вы можете поэкспериментировать с другими алгоритмами сжатия или более высокими уровнями сжатия, например ZSTD(3). Помимо возможности указать это при создании схемы, сжатие можно настроить так, чтобы оно менялось по истечении определённого времени. Это может быть уместно, если кодек или алгоритм сжатия обеспечивает лучшее сжатие, но снижает производительность запросов. Такой компромисс может быть приемлем для старых данных, к которым обращаются реже, но не для свежих данных, которые чаще используются при расследованиях. Пример показан ниже: вместо удаления данных через 4 дня мы начинаем сжимать их с помощью ZSTD(3).
Оцените производительностьМы рекомендуем всегда оценивать, как разные уровни сжатия и алгоритмы влияют на производительность вставки и запросов. Например, дельта-кодеки могут быть полезны для сжатия временных меток. Однако если они входят в состав первичного ключа, производительность фильтрации может снизиться.
Дополнительные сведения и примеры по настройке TTL можно найти здесь. Примеры того, как TTL можно добавлять и изменять для таблиц и столбцов, можно найти здесь. О том, как TTL позволяют создавать иерархии хранения, например архитектуры hot-warm, см. раздел Уровни хранения.

Уровни хранения

В ClickHouse можно создавать уровни хранения на разных дисках, например хранить горячие/недавние данные на SSD, а более старые — в S3. Такая архитектура позволяет использовать для старых данных более дешёвое хранилище, поскольку из-за их редкого использования при расследованиях к запросам к ним предъявляются менее строгие SLA.
Не относится к ClickHouse CloudClickHouse Cloud использует одну копию данных, размещённую в S3, с кэшами узлов на SSD. Поэтому уровни хранения в ClickHouse Cloud не требуются.
Чтобы создать уровни хранения, сначала нужно создать диски, а затем на их основе определить политики хранения с томами, которые можно указать при создании таблицы. Данные могут автоматически перемещаться между дисками в зависимости от уровня заполнения, размера частей и приоритетов томов. Более подробную информацию можно найти здесь. Хотя данные можно вручную перемещать между дисками с помощью команды ALTER TABLE MOVE PARTITION, перемещением данных между томами также можно управлять с помощью TTL. Полный пример можно найти здесь.

Управление изменениями схемы

Схемы Log и трассировок неизбежно будут меняться на протяжении жизненного цикла системы — например, когда пользователи начинают отслеживать новые системы с другими метаданными или метками подов. Если формировать данные в соответствии со схемой OTel и сохранять исходные данные событий в структурированном формате, схемы ClickHouse будут устойчивы к таким изменениям. Однако по мере появления новых метаданных и изменения шаблонов доступа к данным в запросах вам потребуется обновлять схемы, чтобы отразить эти изменения. Чтобы избежать простоя при изменении схемы, у пользователей есть несколько вариантов, которые мы рассмотрим ниже.

Использование значений по умолчанию

Столбцы можно добавлять в схему с помощью значений DEFAULT. Указанное значение по умолчанию будет использоваться, если оно не задано при INSERT. Изменения в схему можно внести до изменения логики преобразования в materialized view или конфигурации OTel collector, из-за которых начнётся отправка новых столбцов. После изменения схемы можно перенастроить OTel collectors. Если пользователи применяют рекомендуемый процесс, описанный в “Извлечение структуры с помощью SQL”, при котором OTel collectors отправляют данные в движок таблицы Null, а materialized view отвечает за извлечение целевой схемы и отправку результатов в целевую таблицу для хранения, представление можно изменить с помощью синтаксиса ALTER TABLE ... MODIFY QUERY. Предположим, у нас есть приведённая ниже целевая таблица и соответствующее ей materialized view (аналогичное тому, что используется в “Извлечение структуры с помощью SQL”) для извлечения целевой схемы из структурированных журналов OTel:
Предположим, мы хотим извлечь новый столбец Size из LogAttributes. Мы можем добавить его в схему с помощью ALTER TABLE, указав значение по умолчанию:
В приведённом выше примере мы указываем значение по умолчанию как ключ size в LogAttributes (если его нет, будет 0). Это означает, что запросы, обращающиеся к этому столбцу для строк, в которые это значение не было вставлено, должны обращаться к Map и, следовательно, будут выполняться медленнее. Мы также могли бы легко указать здесь константу, например 0, снизив стоимость последующих запросов к строкам, в которых это значение отсутствует. Запрос к этой таблице показывает, что значение заполняется из Map, как и ожидалось:
Чтобы это значение добавлялось ко всем будущим данным, мы можем изменить нашу materialized view с помощью синтаксиса ALTER TABLE, как показано ниже:
В последующих строках столбец Size будет заполняться при вставке.

Создание новых таблиц

В качестве альтернативы описанному выше процессу можно просто создать новую целевую таблицу с новой схемой. Затем любые materialized view можно настроить на использование новой таблицы с помощью приведённой выше команды ALTER TABLE MODIFY QUERY. При таком подходе можно версионировать таблицы, например otel_logs_v3. При таком подходе пользователям придётся выполнять запросы к нескольким таблицам. Чтобы выполнять запросы сразу к нескольким таблицам, можно использовать функцию merge, которая поддерживает шаблоны с подстановочными знаками в имени таблицы. Ниже это показано на примере запроса к версиям v2 и v3 таблицы otel_logs:
Если вы хотите избежать использования функции merge и предоставить конечным пользователям таблицу, объединяющую несколько таблиц, можно использовать движок таблицы Merge. Ниже мы покажем, как это сделать:
Это можно обновлять каждый раз при добавлении новой таблицы с помощью синтаксиса EXCHANGE. Например, чтобы добавить таблицу v4, можно создать новую таблицу и атомарно поменять её местами с предыдущей версией.
Последнее изменение 3 июля 2026 г.