Перейти к основному содержанию

TTL в ClickStack

Time-to-Live (TTL) — важная возможность ClickStack для эффективного хранения данных и управления ими, особенно с учетом того, что постоянно генерируются огромные объемы данных. TTL позволяет автоматически удалять устаревшие данные, обеспечивая оптимальное использование хранилища и поддержание производительности без ручного вмешательства. Эта возможность важна для того, чтобы база данных оставалась компактной, затраты на хранилище снижались, а запросы оставались быстрыми и эффективными, поскольку они выполняются по наиболее релевантным и свежим данным. Кроме того, она помогает соблюдать политики хранения данных за счет систематического управления жизненным циклом данных, тем самым повышая общую устойчивость и масштабируемость решения для обсервабилити. По умолчанию ClickStack хранит данные 3 дня. Чтобы изменить это, см. “Изменение TTL”. В ClickHouse TTL управляется на уровне таблицы. Например, ниже показана схема по умолчанию для журналов; ${TABLES_TTL} подставляется в виде настроенного срока хранения (3 дня, если значение не менялось), когда коллектор создает таблицу:
CREATE TABLE IF NOT EXISTS ${DATABASE}.otel_logs
(
  `Timestamp` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
  `TraceId` String CODEC(ZSTD(1)),
  `SpanId` String CODEC(ZSTD(1)),
  `TraceFlags` UInt8,
  `SeverityText` LowCardinality(String) CODEC(ZSTD(1)),
  `SeverityNumber` UInt8,
  `ServiceName` LowCardinality(String) CODEC(ZSTD(1)),
  `Body` String CODEC(ZSTD(1)),
  `ResourceSchemaUrl` LowCardinality(String) CODEC(ZSTD(1)),
  `ResourceAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
  `ScopeSchemaUrl` LowCardinality(String) CODEC(ZSTD(1)),
  `ScopeName` String CODEC(ZSTD(1)),
  `ScopeVersion` LowCardinality(String) CODEC(ZSTD(1)),
  `ScopeAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
  `LogAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
  `EventName` String CODEC(ZSTD(1)),
  `__hdx_materialized_k8s.cluster.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.cluster.name'] CODEC(ZSTD(1)),
  `__hdx_materialized_k8s.container.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.container.name'] CODEC(ZSTD(1)),
  `__hdx_materialized_k8s.deployment.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.deployment.name'] CODEC(ZSTD(1)),
  `__hdx_materialized_k8s.namespace.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.namespace.name'] CODEC(ZSTD(1)),
  `__hdx_materialized_k8s.node.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.node.name'] CODEC(ZSTD(1)),
  `__hdx_materialized_k8s.pod.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.pod.name'] CODEC(ZSTD(1)),
  `__hdx_materialized_k8s.pod.uid` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.pod.uid'] CODEC(ZSTD(1)),
  `__hdx_materialized_deployment.environment.name` LowCardinality(String) MATERIALIZED ResourceAttributes['deployment.environment.name'] CODEC(ZSTD(1)),
  `ResourceAttributeItems` Array(String) ALIAS arrayMap((arr) -> concat(arr.1, '=', arr.2), ResourceAttributes::Array(Tuple(String, String))),
  `ScopeAttributeItems` Array(String) ALIAS arrayMap((arr) -> concat(arr.1, '=', arr.2), ScopeAttributes::Array(Tuple(String, String))),
  `LogAttributeItems` Array(String) ALIAS arrayMap((arr) -> concat(arr.1, '=', arr.2), LogAttributes::Array(Tuple(String, String))),
  INDEX idx_trace_id TraceId TYPE text(tokenizer = 'array'),
  INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE text(tokenizer = 'array'),
  INDEX idx_res_attr_items ResourceAttributeItems TYPE text(tokenizer = 'array'),
  INDEX idx_scope_attr_key mapKeys(ScopeAttributes) TYPE text(tokenizer = 'array'),
  INDEX idx_scope_attr_items ScopeAttributeItems TYPE text(tokenizer = 'array'),
  INDEX idx_log_attr_key mapKeys(LogAttributes) TYPE text(tokenizer = 'array'),
  INDEX idx_log_attr_items LogAttributeItems TYPE text(tokenizer = 'array'),
  INDEX idx_lower_body lower(Body) TYPE text(tokenizer = 'splitByNonAlpha')
)
ENGINE = MergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp)
TTL toDateTime(Timestamp) + ${TABLES_TTL}
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, enable_block_number_column = 1, enable_block_offset_column = 1;
Партиционирование в ClickHouse позволяет логически разделять данные на диске по столбцу или SQL-выражению. Благодаря такому логическому разделению с каждой партицией можно работать независимо, например удалять ее, когда истекает срок хранения согласно политике TTL. Как показано в примере выше, партиционирование задается для таблицы при ее первоначальном определении с помощью выражения PARTITION BY. Это выражение может содержать SQL-выражение для любого столбца или набора столбцов, результат которого определяет, в какую партицию будет отправлена строка. В результате данные логически связываются (через общий префикс имени папки) с каждой партицией на диске, после чего их можно запрашивать изолированно. В приведенном выше примере схема otel_logs по умолчанию разбивает данные на партиции по дням с помощью выражения toDate(Timestamp). По мере вставки строк в ClickHouse это выражение вычисляется для каждой строки, и строка направляется в соответствующую партицию, если она существует (если строка для данного дня первая, партиция будет создана). Подробнее о партиционировании и других его применениях см. в разделе “Партиции таблиц”. Схема таблицы также включает TTL toDateTime(Timestamp) + ${TABLES_TTL} и настройку ttl_only_drop_parts = 1. Первое выражение гарантирует, что данные будут удаляться, когда их возраст превысит настроенный TTL (по умолчанию 3 дня). Настройка ttl_only_drop_parts = 1 предписывает удалять только те части данных, у которых истек срок хранения целиком (вместо попыток частично удалять строки). Поскольку партиционирование гарантирует, что данные за разные дни никогда не будут «сливаться», их можно удалять эффективно.
ttl_only_drop_partsМы рекомендуем всегда использовать настройку ttl_only_drop_parts=1. Когда эта настройка включена, ClickHouse удаляет часть целиком, если срок хранения истек для всех строк в ней. Удаление частей целиком вместо частичной очистки строк по TTL (которая достигается за счет ресурсоемких мутаций, когда ttl_only_drop_parts=0) позволяет использовать меньшие значения merge_with_ttl_timeout и снижает нагрузку на систему. Если данные разбиты на партиции по той же единице, по которой выполняется истечение TTL, например по дням, части естественным образом будут содержать данные только из заданного интервала. Это обеспечивает эффективное применение ttl_only_drop_parts=1.
По умолчанию данные с истекшим TTL удаляются, когда ClickHouse выполняет слияние частей данных. Когда ClickHouse обнаруживает, что срок хранения данных истек, он выполняет внеплановое слияние.
Расписание TTLTTL применяются не сразу, а по расписанию, как отмечалось выше. Настройка таблицы MergeTree merge_with_ttl_timeout задает минимальную задержку в секундах перед повторным выполнением слияния с delete TTL. Значение по умолчанию — 14400 секунд (4 часа). Но это лишь минимальная задержка; до запуска TTL-слияния может пройти больше времени. Если значение слишком низкое, будет выполняться много внеплановых слияний, которые могут потреблять много ресурсов. Принудительно применить TTL можно командой ALTER TABLE my_table MATERIALIZE TTL.

Изменение TTL

Чтобы изменить TTL, можно сделать одно из следующего:
  1. Изменить схемы таблиц (рекомендуется). Для этого нужно подключиться к экземпляру ClickHouse, например с помощью clickhouse-client или Cloud SQL Console. Например, TTL таблицы otel_logs можно изменить с помощью следующего DDL:
ALTER TABLE default.otel_logs
MODIFY TTL TimestampTime + toIntervalDay(7);
  1. Измените OTel collector. Коллектор ClickStack OpenTelemetry создает таблицы в ClickHouse, если они отсутствуют. Это достигается с помощью экспортера ClickHouse, который, в свою очередь, предоставляет параметр ttl, позволяющий задавать выражение TTL по умолчанию, например.
exporters:
 clickhouse:
   endpoint: tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1
   ttl: 72h

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

В приведенных выше примерах срок хранения данных задается на уровне таблицы. Вы также можете задавать срок хранения данных на уровне столбца. По мере устаревания данных это можно использовать для удаления столбцов, ценность которых при расследовании инцидентов не оправдывает затраты ресурсов на их хранение. Например, мы рекомендуем сохранять столбец Body на случай, если будут добавлены новые динамические метаданные, которые не были извлечены во время вставки, например новая метка Kubernetes. Спустя некоторое время, например 1 месяц, может стать очевидно, что эти дополнительные метаданные не несут пользы, а значит, хранить столбец Body уже не имеет особого смысла. Ниже показано, как удалить столбец Body через 30 дней.
CREATE TABLE otel_logs_v2
(
        `Body` String TTL Timestamp + INTERVAL 30 DAY,
        `Timestamp` DateTime,
 ...
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)
Чтобы указать TTL на уровне столбца, необходимо задать собственную схему. Это нельзя настроить в OTel collector.
Последнее изменение 25 июня 2026 г.