(五)时序表分区
应用程序在大多数场景下,都需要查询最近的信息,同时归档旧信息。为了处理这种工作负载,单节点PostgreSQL数据库通常会使用表分区将一个大表根据时间顺序数据分解为多个继承的表,每个表包含不同时间范围的数据。
表分区的意义
在一个大表中,删除行的代价是扫描查找要删除的行,然后清空已清空的空间。而Drop表分区,是一项与数据大小无关的快速操作,相当于简单地删除磁盘上包含数据的文件。
对表进行分区还可以使索引在每个时间范围内更小、更快的被创建出来。因此,对最近数据进行操作的查询很可能只需操作在内存中合适的热索引上,加快了读取速度。
同样,INSERT要更新的索引更小,也就更快。基于时间的分区在以下情况下最有意义:
① 大多数查询访问最近数据的一个非常小的子集;
② 旧数据定期删除;
在Citus上扩展时序数据
我们可以将单节点表分区技术与Citus的分布式分片技术混合使用,在Postgres的声明式表分区之上创建一个可伸缩的时间序列数据库。
1. 创建特定分区
以保存GitHub历史事件数据的表为例,结合Citus进行分片和分区。
创建表并按时间划分,表中的每条记录都表示一个在GitHub中创建的事件,以及关于该事件的关键信息,如事件类型、创建日期和创建该事件的用户:
-- the separate schema will be useful later
CREATE SCHEMA github;
-- declaratively partitioned table
CREATE TABLE github.events (
event_id bigint,
event_type text,
event_public boolean,
repo_id bigint,
payload jsonb,
repo jsonb, actor jsonb,
org jsonb,
created_at timestamp
) PARTITION BY RANGE (created_at);
在创建特定分区之前,我们先设置好分片规则:通过repo_id进行切分,事件将被聚集到每个存储库的分片中。
SELECT create_distributed_table('github.events', 'repo_id');
此时,Citus已经为这个表跨数据节点创建了分片,每个分片都有一个名为github的表。同时,Citus设置分区信息,每个分屏都声明了分区键,即,RANGE (created_at)。
分区表不能直接包含数据,可以理解为跨分区的视图。因此,这些分片还没有准备好保存数据,我们需要手动创建分区并指定它们的时间范围,然后插入与时间范围匹配的数据。
-- manually make a partition for 2016 events
CREATE TABLE github.events_2016 PARTITION OF github.events
FOR VALUES FROM ('2016-01-01') TO ('2016-12-31');
协调节点上有github.events表和github.events_2016表,Citus将把分区创建创建规则设置到所有分片上,为每个切分创建一个分区。
2. 自动创建分区
我们已经手动创建了github.events表的分区,但如果实际场景需要使用保存不到一年的时间范围或者更窄的分区时,每次都手动创建似乎不太现实。
Citus作为PostgreSQL的扩展插件,那么PostgreSQL的一些核心扩展都能与Citus一起开箱即用。这里,我们将用到pg_pathman。
加载pg_partman扩展,并告诉partman我们想要建立分区,作为示例,我们每个分区保存一个小时的数据,这将创建初始的每小时分区:
CREATE SCHEMA partman;
CREATE EXTENSION pg_partman WITH SCHEMA partman;
-- Partition the table into hourly ranges of "created_at"
SELECT partman.create_parent('github.events', 'created_at', 'native', 'hourly');
UPDATE partman.part_config SET infinite_time_partitions = true;
默认情况下,create_parent根据系统时间创建过去的四个分区,未来的四个分区,以及当前的一个分区。如果需要回填旧数据,可以在create_parent调用中指定p_start_partition参数,或者p_premake来为将来创建分区。
随着时间的推移,pg_partman将需要进行一些维护,以创建新分区并删除旧分区,触发维护:
-- disabling analyze is recommended for native partitioning due to aggressive locks
SELECT partman.run_maintenance(p_analyze := false);
由于pg_partman支持在后台工作进程的情况下构建,我们可以结合pg_cron设置一个定期作业来运行维护功能:
SELECT cron.schedule('@hourly', $$
SELECT partman.run_maintenance(p_analyze := false);
$$);
如果要配置pg_partman以删除旧分区,可以更新partman:
UPDATE partman.part_config
SET retention_keep_table = false,
retention = '1 month'
WHERE parent_table = 'github.events';
注意:PostgreSQL目前的分区机制还存在一些不足之处,如,不能直接在分区表上创建索引,需要通过pg_partman创建一个模板表来定义新分区的索引,分区表上的维护操作还将获得主动锁,这可能会导致查询暂时停顿等。