概述

更新时间:2025-01-14 05:52:09

本文介绍了在PolarDB MySQL中过期数据清理(TTL)功能的实现原理。

问题现象

Time to Live (TTL) 功能常见于NoSQL数据库和基于LSM Tree的存储引擎中,提供了键(KEY)和行(Row)级别的生命周期控制策略。这一功能在某些场景下可以有效节省存储空间。在键值存储中,可以借助后台线程定期扫描过期的键并将其删除,而在基于LSM Tree的数据库中,可借助压缩(Compact)线程,定期清理过期的数据行。

示例

在传统的关系型数据库中(如 MySQL、OraclePostgreSQL)原生数据库并没有支持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 TABLEDDL操作进行管理。

  • 在创建表时,如果检测到存在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_sizeinnodb_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任务执行的一些状态信息。

  1. 如果发现某个TTL任务的finished_time始终未更新或者finished_timestart_time之间的间隔过大,可以适当调整innodb_ttl_cluster_index_purge_batch_size或者innodb_ttl_index_purge_batch_size参数,达到合适的数据删除速度。

  2. 如果发现一直有TTL任务处于“挂起”状态,说明TTL的工作线程可能不足。可以适当增加innodb_ttl_threads的数量,以改善处理能力。

  3. 如果发现TTL任务的scan_time占总执行时间的比重较大,则表明扫描过期数据所花费的时间过长。此时,可以考虑对timestamp_col添加索引,以有效缩短扫描所需时间。

版本限制

适用的数据库引擎版本为MySQL 8.0.2,且小版本需为8.0.2.2.27及以上版本。如何查看内核版本,请参见内核版本说明

  • 本页导读 (1)
  • 问题现象
  • 示例
  • 过期清理数据技术方案
  • TTL表信息的更新
  • TTL表的查询
  • TTL表数据的删除
  • TTL表可观测性
  • 版本限制