跳到主要内容
最后于 更新

行级 TTL

实验性功能

行级 TTL 是实验性功能,自 Apache Doris 5.0.0 起支持。采用前请先在测试环境中验证。

TTL 必须在建表时开启:不能为已有普通表补开,不能关闭,也不能切换 TTL 模式。部分列更新、Sequence 列和物化视图等存在下文所列限制。数据过期不保证磁盘空间立即释放。

概述​

行级 TTL(Time to Live)使单行数据在过期后对查询不可见,并允许后台 Compaction 回收过期行。它适用于同一张表或同一个分区内各行需要不同过期时间的场景。目前支持 Doris 内表的 Duplicate Key 和 Unique Key 模型,包括写时合并(MOW)和读时合并(MOR),不支持 Aggregate Key 模型。

选择 TTL 模式​

建表时,根据数据的保留需求选择以下两种互斥模式之一:

适用场景模式配置方式过期时间
每条事件从其时间戳起保留相同的时长源列模式开启 TTL,同时设置 function_column.ttl_col 和 function_column.ttl源列时间 + 表级保留时长
应用为每行指定绝对过期时间直接过期时间模式开启 TTL,不设置上述两个属性写入 __DORIS_TTL_COL__ 的逐行绝对过期时间

过期规则​

  • 源列值或直接过期时间为 NULL 时,该行不会因 TTL 过期。
  • 过期时间小于或等于查询使用的当前时间时,该行即为过期。
  • 一次查询在整个扫描过程中使用统一的当前时间值。因此,即使没有新写入,后续查询也可能返回更少的行。

快速开始​

前置条件​

  • 使用 Apache Doris 5.0.0 或更高版本,并在测试环境中验证此实验性功能。
  • 使用有建表、写入和查询权限的账号连接测试数据库。
  • 使用受支持的表模型:Doris 内表的 Duplicate Key 或 Unique Key(MOW/MOR)。

建表并验证过期行为​

以下示例使每条事件从 event_time 起保留一天。单副本配置仅用于演示,不是生产环境的副本数建议。

SET time_zone = '+00:00';
DROP TABLE IF EXISTS row_ttl_events;
CREATE TABLE row_ttl_events (
id INT,
event_time DATETIME(6) NULL,
message STRING
)
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1",
"function_column.enable_row_ttl" = "true",
"function_column.ttl_col" = "event_time",
"function_column.ttl" = "1 day",
"function_column.ttl_time_zone" = "+00:00"
);

INSERT INTO row_ttl_events VALUES
(1, NOW(6) - INTERVAL 2 DAY, 'expired'),
(2, NULL, 'persistent'),
(3, NOW(6), 'live');

SELECT id, message FROM row_ttl_events ORDER BY id;

写入后立即查询,结果为:

id  message
2 persistent
3 live

旧事件无需等待 Compaction 就已不可见。事件时间为 NULL 的行不会过期。TTL 依据传入的事件时间计算,并不会自动以导入时间为起点,因此迟到的事件可能在导入时就已经过期。

参数​

下列 function_column.* 属性均以字符串形式设置为表属性。

属性默认值 / 必填条件含义及修改规则
function_column.enable_row_ttl默认 false;两种模式均需设为 true布尔字符串,仅能在建表时设置。
function_column.ttl_col默认不设置;源列模式下必须与 function_column.ttl 一起设置已存在的非生成 Value 列名,类型须为 DATE/DATEV2、DATETIME/DATETIMEV2 或 TIMESTAMPTZ。不支持 Key 列和 TIMESTAMP_NS。建表后不可修改。
function_column.ttl默认不设置;须与 function_column.ttl_col 一起设置非负整数时长。建表后仅允许设置为相同或更短的时长。0 表示在源列时间到达时过期,不表示关闭 TTL。直接过期时间模式不支持此属性。
function_column.ttl_time_zoneDATE/DATETIME 系列源列必须显式设置;TIMESTAMPTZ 默认 +00:00解释源列时间所用的时区。接受 UTC、Z,或 -12:00 至 +14:00 范围内的固定偏移,格式为 +HH:MM / -HH:MM。TIMESTAMPTZ 只接受零偏移。建表后不可修改,直接过期时间模式不能设置。

时长格式​

时长不带单位时按秒解释。单位不区分大小写:

单位支持的写法
周week、weeks
天day、days、d
小时hour、hours、h
分钟minute、minutes、m
秒second、seconds、s

例如,86400、1 day 和 24h 表示相同时长。一天固定为 24 小时。

  • 不支持小数、负数、1 day 2 hours 等组合时长、月和年。
  • 时长转换为微秒后,必须在有符号 64 位整数范围内。
  • 源列时间与时长相加得到的纪元微秒数也必须在有符号 64 位整数范围内,否则计算过期时间时会报错。

时区​

源列模式不支持 Asia/Shanghai 等区域时区及夏令时规则。DATE 按所配置偏移下的当天零点解释;TIMESTAMPTZ 表示绝对时间。修改查询会话时区不会改变已存储行的 TTL 策略。

隐藏 TTL 列​

Doris 自动创建可空隐藏列 __DORIS_TTL_COL__,不要在表结构中手动声明此列。

在源列模式下,Doris 将源列值复制到隐藏列。应写入或更新源列,不要通过隐藏列提供独立的过期时间。

在直接过期时间模式下,隐藏列类型为 BIGINT,保存 Unix 纪元以来的微秒数,不是秒或毫秒。各类输入的处理方式如下:

输入直接过期时间模式下的行为
完整行写入时传入 NULL、DEFAULT 或省略此列得到 NULL 过期时间,该行不会因 TTL 过期。
BIGINT直接作为纪元微秒数使用。在当前日期查询时,0 和负值已经过期。
DATE/DATETIME 表达式使用 SQL 会话的 time_zone 或导入任务的 timezone 转换为纪元微秒数。
TIMESTAMPTZ 表达式转换为纪元微秒数;该值本身已表示绝对时间。
文本日期应显式转换为支持的时间类型。
TIMESTAMP_NS不支持。

需要查看隐藏列时,开启 show_hidden_columns 并显式查询该列。此设置不会绕过 TTL 过滤,也不能用来查看已过期行。

示例​

为每行指定过期时间​

应用需要为每行指定不同的过期时间时,使用直接过期时间模式:

SET time_zone = '+00:00';
DROP TABLE IF EXISTS row_ttl_expirations;
CREATE TABLE row_ttl_expirations (id INT, message STRING)
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1",
"function_column.enable_row_ttl" = "true"
);

INSERT INTO row_ttl_expirations(id, message, __DORIS_TTL_COL__) VALUES
(1, 'expired', 0),
(2, 'persistent', NULL),
(3, 'live', NOW(6) + INTERVAL 1 DAY);
INSERT INTO row_ttl_expirations VALUES (4, 'omitted');

SELECT id, message FROM row_ttl_expirations ORDER BY id;

立即查询,结果为:

id  message
2 persistent
3 live
4 omitted

INSERT ... SELECT 也支持向隐藏列写入时间类型表达式,转换规则相同。

通过 Stream Load 导入过期时间​

使用上例的 row_ttl_expirations 表。替换连接参数占位符,并使用有导入权限的账号。输入中包含过期时间字符串,因此在列映射中显式转换类型,同时指定导入时区:

curl --location-trusted -u '<user>:<password>' \
-H 'label:row_ttl_expirations_example_1' \
-H 'column_separator:,' \
-H 'timezone:+00:00' \
-H 'columns:id,message,expiration,__DORIS_TTL_COL__=cast(expiration as datetime(6))' \
-T - 'http://<fe_host>:<http_port>/api/<database>/row_ttl_expirations/_stream_load' <<'DATA'
10,stream-live,2099-01-01 00:00:00.000000
11,stream-expired,2000-01-01 00:00:00.000000
DATA

重复导入时请使用新的 label。成功响应中的 Status 为 Success;过期本身不会使其他方面合法的输入行成为导入错误。在 2099 年之前执行以下查询,仅返回第 10 行:

SELECT id, message FROM row_ttl_expirations WHERE id >= 10 ORDER BY id;
id  message
10 stream-live

如果输入已经是纪元微秒数,应使用 columns:id,message,__DORIS_TTL_COL__ 直接映射,不要再转换为 DATETIME。

更新 Unique Key 行​

支持普通 SQL UPDATE。对于可见行,修改无关列会保留 TTL 值;修改源列时间或直接过期时间会改变该行的到期时间。这与不支持的部分列更新导入模式不同。

DROP TABLE IF EXISTS row_ttl_accounts;
CREATE TABLE row_ttl_accounts (id INT, message STRING)
UNIQUE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1",
"enable_unique_key_merge_on_write" = "true",
"function_column.enable_row_ttl" = "true"
);
INSERT INTO row_ttl_accounts(id, message, __DORIS_TTL_COL__) VALUES
(1, 'original', 9223372036854775807);
UPDATE row_ttl_accounts SET message = 'updated' WHERE id = 1;

SET show_hidden_columns = true;
SELECT id, message, __DORIS_TTL_COL__ FROM row_ttl_accounts ORDER BY id;
SET show_hidden_columns = false;
id  message  __DORIS_TTL_COL__
1 updated 9223372036854775807

完整行 UPSERT 会替换整行。因此,直接过期时间模式下省略过期时间会清除旧的过期时间:

INSERT INTO row_ttl_accounts(id, message) VALUES (1, 'replaced');
SET show_hidden_columns = true;
SELECT id, message, __DORIS_TTL_COL__ FROM row_ttl_accounts ORDER BY id;
SET show_hidden_columns = false;
id  message   __DORIS_TTL_COL__
1 replaced NULL

更新时还需注意以下规则:

  • 源列模式下,完整行写入省略源列时使用该列正常的默认值/NULL 规则,而不是上一版本的时间戳。
  • UPDATE 无法通过普通查询找到已经过期的行;如需写入新版本,应使用包含所需值的完整行 INSERT。
  • 对于 Unique Key 表,最新版本过期不会让同一 Key 的旧版本重新可见。

缩短保留时长​

使用快速开始中的源列模式表,并使用有 ALTER 权限的账号:

ALTER TABLE row_ttl_events SET ("function_column.ttl" = "12 hours");
SHOW CREATE TABLE row_ttl_events;

新的保留时长既作用于已有源列值,也作用于后续写入。SHOW CREATE TABLE 会显示更新后的 function_column.ttl 属性;若显示的是不带单位的整数,则单位为秒。

按新策略已经过期的行对使用该策略的查询不可见,并可被回收。允许再次设置相同的时长;不允许增大,因为先前过期的数据可能已经被回收。

ALTER 报错不代表策略已回滚

更短的查询策略会先持久化,再下发回收配置。因此,即使 ALTER 返回超时、配置版本未获确认或表发生并发变更等错误,更短的保留时长也可能已经生效。

请检查 SHOW CREATE TABLE,排除报错原因后,以相同的时长重试以同步策略。不要尝试恢复原来更长的时长。

限制​

开启 TTL 前,请检查现有表功能和工作负载是否满足以下限制:

方面当前行为 / 限制
表模型仅支持 Doris 内表的 Duplicate Key 和 Unique Key(MOW/MOR);拒绝 Aggregate Key。
开启及模式修改仅能在 CREATE TABLE 时开启。ALTER 不能开启/关闭 TTL、改变源列或时区,也不能切换模式。不支持 ENABLE FEATURE "ROW_TTL"。只有源列模式的保留时长可以缩短。
Sequence 列不能与行级 TTL 共存,包括建表后再开启 SEQUENCE_LOAD。
表结构变更不能删除或修改 TTL 源列、隐藏列,不能重命名源列。无关列的变更仍遵循普通 Schema Change 规则。
部分列更新两种 TTL 模式均不支持。包括开启 enable_unique_key_partial_update = true 的 INSERT、Stream Load 的 partial_columns = true、UPDATE_FIXED_COLUMNS 和 UPDATE_FLEXIBLE_COLUMNS,即使请求中带有 TTL 值也不支持。完整行 UPSERT、普通 SQL UPDATE 和 DELETE 仍可按各自的表模型规则使用。
物化视图不允许在 TTL 表上通过 CREATE MATERIALIZED VIEW 创建同步物化视图;异步物化视图不能以 TTL 表作为基表。这与普通 ALTER TABLE ... ADD ROLLUP 不同,后者受支持,TTL 隐藏列由 Doris 维护。
历史及增量访问TTL 表不支持 FOR VERSION AS OF、FOR TIME AS OF、@incr(...) 和 CREATE STREAM ... ON TABLE。
查询 Hint拒绝 PREAGGOPEN。MOR 扫描会关闭存储层预聚合,以保证版本合并和过期语义正确。
查询性能TTL 扫描会关闭 SQL Cache、Query Cache、短路点查以及部分 TopN 下推优化。点查、聚合、关联和 ORDER BY/LIMIT 仍可使用,但不能假定其执行计划和性能与普通表相同。
一致性检查自动校验和一致性检查会跳过 TTL 表;对 TTL Tablet 显式发起的校验和一致性检查会被拒绝。这不表示关闭普通副本复制。

物理回收边界​

查询时检查 TTL 可见性;磁盘回收则依赖适用的后台 Compaction 和后续旧文件清理。表长期没有合并、Compaction 延迟或旧文件仍被保留,都可能导致过期数据继续占用磁盘,没有固定的物理删除时限。

回收行为取决于表模型和 Compaction 路径:

表模型 / 数据路径回收行为
Duplicate Key可在支持的 Segment、Cumulative、Base 和 Full Compaction 路径中回收过期行。
Unique Key MOW可在 Cumulative、Base 和 Full Compaction 中回收已发布 Rowset 的过期行。发布前的 Segment Compaction 会保留过期 Key,用于写入冲突处理。
Unique Key MOR只有在 Full Compaction,或覆盖从版本 0 开始的历史版本的 Base Compaction 中才会回收过期行,避免删除后露出同一 Key 的旧版本。
冷数据 Compaction不执行行级 TTL 回收。
Changelog Tablet不会通过行级 TTL 删除日志条目。原始 Changelog 中可能仍保留基表已过期行的写入记录,其保留策略独立于行级 TTL。

行级 TTL 不保证到期时所有数据副本或变更记录都已被擦除。

常见问题​

遇到配置报错或存储空间未释放时,可按下表排查:

报错 / 现象处理方式
function_column.ttl_col and function_column.ttl must be set together源列模式同时设置这两个属性;直接过期时间模式则都不设置。
function_column.ttl_time_zone is required为 DATE/DATETIME 源列显式设置固定时区偏移。
row ttl column must be a value column使用受支持的时间类型 Value 列,不要使用 Key 列。
Partial update is not supported on tables with row TTL使用完整行 UPSERT,并确认省略列的默认值符合预期,或使用普通 SQL UPDATE;不要在未检查省略列取值的情况下直接移除部分更新设置。
Increasing function_column.ttl is not supported保持或缩短时长。如需延长,规划新表并从仍保留的源数据重新导入;TTL 无法恢复已回收数据。
TTL properties can only be specified when creating a table不要通过 ALTER 修改开关、源列或时区;仅源列 TTL 表的 function_column.ttl 可修改。
查询中行已消失,但存储空间未减少检查 Compaction 进度和上述回收边界。过期与物理删除是两个阶段。

最佳实践​

  • 事件发生后保留统一时长时,选择源列模式;应用需要逐行指定绝对截止时间时,选择直接过期时间模式。导入前检查迟到时间戳和 NULL 值。
  • 数据天然按整个时间分区保留时,优先考虑动态分区。行级 TTL 适合更细粒度的管理,但增加逐行过滤,并限制部分查询优化。
  • 采用前验证实际的导入、更新、查询与 Compaction 工作负载,尤其检查对部分列更新、Sequence 列、物化视图、历史查询和缓存的依赖。
  • 缩短保留时长前确认预期的过期边界,并在此 TTL 策略之外保留必须可恢复的数据。用普通查询验证可见性,不要把磁盘占用或隐藏列查询当作是否过期、是否彻底擦除的依据。