暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostgreSQL rotate table 自动清理调度 - 约束,触发器

digoal 2018-03-11
343

作者

digoal

日期

2018-03-11

标签

PostgreSQL , rotate table , 自动清理 , 触发器 , insert into on conflict


背景

时序场景,这个需求很常见:

1、自动清理过期的历史数据,并且要保证清理速度够快,不产生WAL日志。

要么DROP,要么truncate。

2、我们知道PG支持分区表,如果是通过分区的方法来实现,最好不过了,但是需要不停的创建分区,这个目前pg_pathman能够满足自动创建分区。但是自动删除分区还是不够自动。

所以怎么做呢?

一个做法是这样的:

1、固定一个周期的所有分区表(类似list分区),比如以小时为结尾的表,只需要24个。以周为单位的表,只需要7个。以分钟为单位的表,需要60个。

2、自动根据数据插入的时间,触发,并清理(truncate)旧的数据。

这种方法,写入代码是固定的不需要变更。

数据表是一次性创建的,不需要后续再增加。

例子

1、创建周期内的所有明细分区表

以10分钟为单位,创建6个表,代表每个10分钟。

create table t1_0 (id int, info text, crt_time timestamp, check (substring(to_char(crt_time,'yyyymmddhh24miss'), 11, 1)='0')); create table t1_1 (id int, info text, crt_time timestamp, check (substring(to_char(crt_time,'yyyymmddhh24miss'), 11, 1)='1')); create table t1_2 (id int, info text, crt_time timestamp, check (substring(to_char(crt_time,'yyyymmddhh24miss'), 11, 1)='2')); create table t1_3 (id int, info text, crt_time timestamp, check (substring(to_char(crt_time,'yyyymmddhh24miss'), 11, 1)='3')); create table t1_4 (id int, info text, crt_time timestamp, check (substring(to_char(crt_time,'yyyymmddhh24miss'), 11, 1)='4')); create table t1_5 (id int, info text, crt_time timestamp, check (substring(to_char(crt_time,'yyyymmddhh24miss'), 11, 1)='5'));

2、创建约束表,或者说调度表(因为我们不能每条记录都触发一个TRUNCATE事件)

以10分钟为周期,清除10分钟前的数据。

create table t_const(crt_time timestamp primary key);

3、创建分区表的触发器,将数据按周期截断后,写入约束表。

```
create or replace function tg() returns trigger as $$
declare
begin
insert into t_const values (to_timestamp(substring(to_char(NEW.crt_time,'yyyymmddhh24miss'), 1, 11)||'000','yyyymmddhh24miss')) on conflict (crt_time) do nothing;
return null;
end;
$$ language plpgsql strict;

create trigger tg1 after insert on t1_0 for each row execute procedure tg();
create trigger tg1 after insert on t1_1 for each row execute procedure tg();
create trigger tg1 after insert on t1_2 for each row execute procedure tg();
create trigger tg1 after insert on t1_3 for each row execute procedure tg();
create trigger tg1 after insert on t1_4 for each row execute procedure tg();
create trigger tg1 after insert on t1_5 for each row execute procedure tg();
```

4、创建约束表触发器,触发清除明细表分区的规则。

```
create or replace function tg_truncate() returns trigger as $$
declare
suffix int := substring(to_char(NEW.crt_time,'yyyymmddhh24miss'), 11, 1)::int;
begin
set lock_timeout = '1s';

for i in 0..5 loop
if i=suffix then
continue;
end if;

if suffix=0 and i=5 then  
  continue;  
end if;

if i=suffix-1 then  
  continue;  
end if;

execute 'truncate t1_'||i;

raise notice 'truncated %', 't1_'||i;

end loop;

return null;
end;
$$ language plpgsql strict;
```

create trigger tg1 after insert on t_const for each row execute procedure tg_truncate();

试一试

自动清除

postgres=# insert into t1_0 values (1,now(),'2018-01-02 10:00:40'); NOTICE: truncated t1_1 NOTICE: truncated t1_2 NOTICE: truncated t1_3 NOTICE: truncated t1_4 INSERT 0 1

如果后面再写入同一个区,不会触发自动清除,符合要求(仅第一条触发)。

postgres=# insert into t1_0 values (1,now(),'2018-01-02 10:00:40'); INSERT 0 1 postgres=# insert into t1_0 values (1,now(),'2018-01-02 10:01:50'); INSERT 0 1

小结

使用分区,实现了数据的分区存放。(目前如果使用原生分区表的话,写入会对主表和所有子分区加共享锁,导致无法truncate。建议方法:使用pg_pathman分区,或者直接写子分区。)

《分区表锁粒度差异 - pg_pathman VS native partition table》

使用TRUNCATE,使得清理数据时不会产生WAL日志。

使用触发器,实现了自动的数据清理。

参考

《PostgreSQL 数据rotate用法介绍 - 按时间覆盖历史数据》

《PostgreSQL APP海量FEED LOG实时质量统计CASE(含percentile_disc) - 含rotate 分区表》

PostgreSQL 许愿链接

您的愿望将传达给PG kernel hacker、数据库厂商等, 帮助提高数据库产品质量和功能, 说不定下一个PG版本就有您提出的功能点. 针对非常好的提议,奖励限量版PG文化衫、纪念品、贴纸、PG热门书籍等,奖品丰富,快来许愿。开不开森.

9.9元购买3个月阿里云RDS PostgreSQL实例

PostgreSQL 解决方案集合

德哥 / digoal's github - 公益是一辈子的事.

digoal's wechat

文章转载自digoal,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论