Learning Platform
Глоссарий Troubleshooting
Урок 11.01 · 30 мин
Продвинутый
TTLMergeTreedata lifecyclecolumn TTL

Row-level и Column-level TTL

TTL (Time-to-Live) — механизм автоматического управления жизненным циклом данных в ClickHouse. Вместо написания cron-задач и ручных DELETE-запросов вы декларативно описываете, когда данные должны исчезнуть или измениться, а ClickHouse применяет эти правила в фоновом режиме во время merge-операций.


Как работает TTL: связь с merge-циклом

TTL-правила применяются исключительно во время merge-операций — когда ClickHouse объединяет несколько part-файлов в один. Это означает, что данные с истёкшим TTL не удаляются мгновенно после INSERT.

Жизненный цикл TTL: от INSERT до удаления
INSERT → part созданINSERT: новые данные записываются на диск как отдельный part. TTL-метаданные сохраняются в part (поле TTL_info), но сами данные не проверяются на истечение. Строки доступны для SELECT сразу после вставки.
фоновый merge
Merge-операция: проверка TTLMerge-цикл: ClickHouse периодически объединяет мелкие parts в более крупные. Именно в этот момент проверяется TTL_info каждого part. Если TTL истёк — строки фильтруются или колонки заменяются на дефолтное значение.
Row TTL: строки удаленыRow TTL (delete): строки с истёкшим event_time + INTERVAL N DAY физически исключаются из результирующего part. После merge этих строк больше не существует на диске.
Column TTL: значение замененоColumn TTL (expiry): значение колонки заменяется на DEFAULT типа — 0 для числовых типов, пустая строка '' для String, 1970-01-01 для Date. Строка остаётся в таблице, но поле обнуляется.
WARNING

TTL применяется только при merge, не сразу после INSERT. Если вставить строки с уже истёкшим TTL, они будут видны в SELECT до следующего merge-цикла или принудительного MATERIALIZE TTL.


Row-level TTL: автоматическое удаление строк

Row-level TTL настраивается на уровне таблицы через предложение TTL. При истечении срока строка полностью удаляется из хранилища.

-- Создание таблицы с row-level TTL
CREATE TABLE events
(
    event_time DateTime,
    user_id    UInt64,
    payload    String
)
ENGINE = MergeTree
ORDER BY (user_id, event_time)
TTL event_time + INTERVAL 90 DAY;

Выражение event_time + INTERVAL 90 DAY вычисляется для каждой строки. Строки, для которых это выражение уже меньше текущего времени, удаляются при следующем merge.

Допустимые единицы интервала: SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR.

Изменение TTL на существующей таблице

-- Изменить TTL существующей таблицы (новое значение применяется при следующем merge)
ALTER TABLE events
    MODIFY TTL event_time + INTERVAL 180 DAY;

-- Полностью удалить TTL с таблицы
ALTER TABLE events
    REMOVE TTL;

Column-level TTL: замена значения на дефолт

Column-level TTL задаётся в определении колонки. По истечении срока значение колонки не удаляется — оно заменяется на дефолтное значение типа.

Тип колонкиЧто подставляется после TTL
UInt8, UInt32, Int64, Float640
String'' (пустая строка)
Date, Date321970-01-01
DateTime, DateTime641970-01-01 00:00:00
Array(T)[] (пустой массив)
-- Таблица с column-level TTL на sensitive-колонках
CREATE TABLE events_tiered
(
    event_time  DateTime,
    user_id     UInt64,
    raw_payload String TTL event_time + INTERVAL 7 DAY,   -- через 7 дней → ''
    ip_address  String TTL event_time + INTERVAL 30 DAY,  -- через 30 дней → ''
    summary     String                                     -- хранится бессрочно
)
ENGINE = MergeTree
ORDER BY (user_id, event_time);
WARNING

Column TTL заменяет значение на дефолт типа, а не на NULL. Даже если тип допускает Nullable, TTL подставит 0 или '', а не NULL. Для GDPR-сценариев, где важно различать “данные удалены” и “данные отсутствуют”, используйте Nullable тип и явную мутацию, либо row-level TTL.


Совмещение row-level и column-level TTL

Таблица может иметь одновременно оба типа TTL:

CREATE TABLE user_activity
(
    event_time    DateTime,
    user_id       UInt64,
    raw_data      String TTL event_time + INTERVAL 7 DAY,   -- колонка обнуляется через 7 дней
    processed_data String
)
ENGINE = MergeTree
ORDER BY (user_id, event_time)
-- Row TTL: вся строка удаляется через 365 дней
TTL event_time + INTERVAL 365 DAY;

Порядок применения: сначала срабатывает column TTL (значение заменяется), затем — row TTL (строка удаляется). Если row TTL короче column TTL, column TTL никогда не сработает для удалённых строк.


MATERIALIZE TTL: принудительное применение к существующим данным

Команда MATERIALIZE TTL запускает немедленную обработку всех существующих частей таблицы, применяя TTL без ожидания фонового merge.

-- Принудительно применить TTL ко всем существующим данным
ALTER TABLE events MATERIALIZE TTL;

-- Проверить прогресс через system.mutations
SELECT mutation_id, command, is_done, create_time
FROM system.mutations
WHERE table = 'events'
ORDER BY create_time DESC
LIMIT 5;

Альтернативный способ форсировать слияние (менее контролируемый):

-- Принудительный merge конкретной таблицы (не рекомендуется в production на больших таблицах)
OPTIMIZE TABLE events FINAL;

Проверка TTL через system.parts

-- Посмотреть TTL-метаданные на уровне партов
SELECT
    name,
    rows,
    min_ttl_expression,
    delete_ttl_expression,
    min_time,
    max_time
FROM system.parts
WHERE table = 'events'
  AND active
ORDER BY min_time;

Ключевые выводы

  1. TTL применяется только при merge — не сразу после INSERT. MATERIALIZE TTL или OPTIMIZE TABLE FINAL для немедленного применения.
  2. Row-level TTL полностью удаляет строку; column-level TTL заменяет значение на дефолт типа (не на NULL).
  3. Дефолт типа для String — пустая строка '', для чисел — 0, для Date — 1970-01-01.
  4. ALTER TABLE MODIFY TTL меняет правило для новых merge; MATERIALIZE TTL применяет его к существующим данным.
  5. Таблица может иметь одновременно row TTL и column TTL на разных колонках с разными интервалами.
VACUUM в PostgreSQL: free space, visibility map, FSM Data warehouse, data mart, data lake, lakehouse

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Разработчик создал таблицу с TTL event_time + INTERVAL 90 DAY. Через 95 дней данные всё ещё присутствуют в таблице при SELECT. Какова наиболее вероятная причина?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 8