本文介绍了在PolarDB MySQL版中过期数据清理(TTL)功能的实现原理。
问题现象
Time to Live (TTL) 功能常见于NoSQL数据库和基于LSM Tree的存储引擎中,提供了键(KEY)和行(Row)级别的生命周期控制策略。这一功能在某些场景下可以有效节省存储空间。在键值存储中,可以借助后台线程定期扫描过期的键并将其删除,而在基于LSM Tree的数据库中,可借助压缩(Compact)线程,定期清理过期的数据行。
示例
在传统的关系型数据库中(如 MySQL、Oracle和PostgreSQL)原生数据库并没有支持TTL功能,通常依赖于触发器或存储过程来实现类似的功能。
-- 1. 首先创建一个包含过期时间的表,表结构如下:
CREATE TABLE messages(
message_id VARCHAR(36) PRIMARY KEY,
sender_id VARCHAR(36),
receiver_id VARCHAR(36),
message_text TEXT,
sent_at TIMESTAMP
);
-- 2. 创建一个定时任务,每隔5min(可自定义)运行一次任务,根据sent_at字段,删除那些已经过期的数据行
SET GLOBAL event_scheduler = ON;
CREATE EVENT delete_old_messages
ON SCHEDULE
EVERY 5 MINUTE
STARTS CURRENT_TIMESTAMP
DO
DELETE FROM messages
WHERE sent_at < DATE_SUB(NOW(), INTERVAL 5 MINUTE);
但是,这种做法可能会引发其他问题,例如:
如果删除不及时,仍可能查询到已经过期的数据行。
如果需要删除的数据量较大,可能会导致数据库性能波动。
因此,如果我们需要更精细的TTL控制,仍然需要数据库原生支持TTL功能。
过期清理数据技术方案
PolarDB增加一张系统表ttl_job_history
,用于记录TTL任务执行的情况。同时,关于表的TTL信息,我们将其存储在表的元数据(Data Dictionary)信息中。当打开表时,这些TTL信息将被加载到dict_table
结构中。其中表结构如下:
struct dict_table_t {
...
/** TTL info's column, this column must be secondary index.*/
const char *ttl_col_name{nullptr};
/** TTL info's expire time, the unit is seconds, default is 0.*/
ulint ttl_interval{0};
...
}
CREATE TABLE IF NOT EXISTS ttl_job_history (
job_id BIGINT unsigned NOT NULL COMMENT 'clean job id',
table_name VARCHAR(255) NOT NULL COMMENT 'clean ttl table name',
state VARCHAR(255) NOT NULL COMMENT 'clean job state',
start_time BIGINT unsigned NOT NULL COMMENT 'job start time, unix timestamp',
finished_time BIGINT unsigned COMMENT 'job finished time, unix timestamp',
expire_time BIGINT unsigned COMMENT 'record expired time, unix timestamp',
scan_cost INT unsigned COMMENT 'scan expired record cost, milliseconds',
purge_cost INT unsigned COMMENT 'purge expired record cost, milliseconds',
purge_rows INT unsigned COMMENT 'purge expired record rows',
PRIMARY KEY job_id (job_id)
) ENGINE=INNODB STATS_PERSISTENT=0 CHARACTER SET utf8 COLLATE utf8_bin comment='ttl clean job history' TABLESPACE=mysql;
TTL表信息的更新
TTL信息的添加和修改只能通过创建表和ALTER TABLE
的DDL操作进行管理。
在创建表时,如果检测到存在TTL信息,经过相关检查后,我们将TTL信息存储到
dd_table->se_private_data().set(dd_table_key_strings[DD_TABLE_TTL_INFO]);
中。在执行
ALTER TABLE
操作时,我们将TTL信息更新到dd_table->se_private_data().set(dd_table_key_strings[DD_TABLE_TTL_INFO]);
中。
TTL表的查询
由于我们将TTL表的信息已经全部存储在dict_table_t
结构中;TTL持久化的信息将存储到dd_table->se_private_data().set(dd_table_key_strings[DD_TABLE_TTL_INFO]);
中。这样,在集群启动时,当创建一个表的dict_table_t
信息时,会从dd_table
中加载相关的TTL信息。
当对表进行DDL操作以修改TTL信息时,相关数据将被存储到
dd_table->se_private_data()
中,然后表将被重新打开,此时将把TTL信息更新到dict_table_t
结构中。在更新
dd_table->se_private_data()
时,TTL信息会被写入Redo Log,并通过物理复制同步到只读节点(RO 节点)上,因此在RO节点的dict_table_t
结构中也包含了TTL信息。最终,只要引擎层成功获取到一个表的
dict_table_t
对象,那么也就获取到了相关的TTL信息。
TTL表数据的删除
需要一个coordinator线程和一组worker线程,以及一个TTL任务队列;
为了降低TTL表数据删除对集群的影响,这些线程的优先级应设置为较低。
coordinator线程:
定期从
dd_table
中获取需要执行TTL任务的表的信息。如果某个表需要执行TTL任务,将清理任务封装并放入TTL任务队列中。
同时,将此TTL任务的信息插入到
ttl_job_history
系统表中,任务的状态初始为“挂起”,并将expire_time
更新为当前时间。定期检查
ttl_job_history
中的数据,删除finished_time
超过90天的记录。
worker线程:
定期从任务队列中获取TTL任务。
当成功获取到一个TTL任务时,更新该任务在
ttl_job_history
表中的信息,将状态更改为“开始”,并记录开始时间。根据
innodb_ttl_cluster_index_purge_batch_size
和innodb_ttl_index_purge_batch_size
参数,系统将扫描过期数据。对于每个TTL任务,如果TTL列上有索引,则会扫描innodb_ttl_index_purge_batch_size
次;如果TTL列没有索引,则会扫描innodb_ttl_cluster_index_purge_batch_size
次。任务结束时,将状态更新为“完成”,并记录结束时间、删除的数据条数以及总的数据条数。
任务队列(crash safe):
TTL仅在数据删除时涉及数据的修改,因此唯一的可能性是在TTL任务执行期间集群发生崩溃。重启后,检查ttl_job_history
中是否有未执行完成的任务。如果存在未完成的任务,可以选择重新下发这些任务或直接删除它们。因为在下次对该TTL表执行TTL任务时,系统仍会清理之前未删除的过期数据。
TTL表可观测性
由于TTL的工作线程在每一步都更新ttl_job_history
系统表,因此我们可以通过查询ttl_job_history
来观察TTL任务执行的一些状态信息。
如果发现某个TTL任务的
finished_time
始终未更新或者finished_time
与start_time
之间的间隔过大,可以适当调整innodb_ttl_cluster_index_purge_batch_size
或者innodb_ttl_index_purge_batch_size
参数,达到合适的数据删除速度。如果发现一直有TTL任务处于“挂起”状态,说明TTL的工作线程可能不足。可以适当增加
innodb_ttl_threads
的数量,以改善处理能力。如果发现TTL任务的
scan_time
占总执行时间的比重较大,则表明扫描过期数据所花费的时间过长。此时,可以考虑对timestamp_col
添加索引,以有效缩短扫描所需时间。
版本限制
适用的数据库引擎版本为MySQL 8.0.2,且小版本需为8.0.2.2.27及以上版本。如何查看内核版本,请参见内核版本说明。
- 本页导读 (1)
- 问题现象
- 示例
- 过期清理数据技术方案
- TTL表信息的更新
- TTL表的查询
- TTL表数据的删除
- TTL表可观测性
- 版本限制