PostgreSQL 传统 hash 分区方法和性能

标签

PostgreSQL , hash , list, range , hashtext , 哈希函数 , 取模 , 传统分区方法 , trigger , rule , pg_pathman , 内置分区 , general 分区


背景

除了传统的基于trigger和rule的分区,PostgreSQL 10开始已经内置了分区功能(目前仅支持list和range),使用pg_pathman则支持hash分区。

从性能角度,目前最好的还是pg_pathman分区。

但是,传统的分区手段,依旧是最灵活的,在其他方法都不奏效时,可以考虑传统方法。

如何创建传统的hash分区

1、创建父表

create table tbl (id int, info text, crt_time timestamp);

2、创建分区表,增加约束

do language plpgsql $$
declare
  parts int := 4;
begin
  for i in 0..parts-1 loop
    execute format('create table tbl%s (like tbl including all) inherits (tbl)', i);
    execute format('alter table tbl%s add constraint ck check(mod(id,%s)=%s)', i, parts, i);
  end loop;
end;
$$;

3、创建触发器函数,内容为数据路由,路由后返回NULL(即不写本地父表)

create or replace function ins_tbl() returns trigger as $$
declare
begin
  case abs(mod(NEW.id,4))
    when 0 then
      insert into tbl0 values (NEW.*);
    when 1 then
      insert into tbl1 values (NEW.*);
    when 2 then
      insert into tbl2 values (NEW.*);
    when 3 then
      insert into tbl3 values (NEW.*);
    else
      return NEW;  -- 如果是NULL则写本地父表
    end case;
    return null;
end;
$$ language plpgsql strict;

4、创建before触发器

create trigger tg1 before insert on tbl for each row when (NEW.id is not null) execute procedure ins_tbl();

5、验证

postgres=# insert into tbl values (1);
INSERT 0 0
postgres=# insert into tbl values (null);
INSERT 0 1
postgres=# insert into tbl values (0);
INSERT 0 0
postgres=# insert into tbl values (1);
INSERT 0 0
postgres=# insert into tbl values (2);
INSERT 0 0
postgres=# insert into tbl values (3);
INSERT 0 0
postgres=# insert into tbl values (4);
INSERT 0 0  

postgres=# select  tableoid::regclass, * from tbl;
 tableoid | id | info | crt_time
----------+----+------+----------
 tbl      |    |      |
 tbl0     |  0 |      |
 tbl0     |  4 |      |
 tbl1     |  1 |      |
 tbl1     |  1 |      |
 tbl2     |  2 |      |
 tbl3     |  3 |      |
(7 rows)

6、查询时,只要提供了约束条件,会自动过滤到子表,不会扫描不符合约束条件的其他子表。

postgres=# explain select * from tbl where abs(mod(id,4)) = abs(mod(1,4)) and id=1;
                                QUERY PLAN
--------------------------------------------------------------------------
 Append  (cost=0.00..979127.84 rows=3 width=45)
   ->  Seq Scan on tbl  (cost=0.00..840377.67 rows=2 width=45)
         Filter: ((id = 1) AND (abs(mod(id, 4)) = 1))
   ->  Seq Scan on tbl1  (cost=0.00..138750.17 rows=1 width=45)
         Filter: ((id = 1) AND (abs(mod(id, 4)) = 1))
(5 rows)

传统分区性能 对比 非分区表

传统分区表性能

性能相比没有分区有一定下降。(CPU开销略有提升)

1、创建压测脚本

vi test.sql
\set id random(1,100000)
insert into tbl values (:id);

2、压测

pgbench -M prepared -n -r -P 1 -f ./test.sql -c 56 -j 56 -T 120  

transaction type: ./test.sql
scaling factor: 1
query mode: prepared
number of clients: 56
number of threads: 56
duration: 120 s
number of transactions actually processed: 21277635
latency average = 0.316 ms
latency stddev = 0.170 ms
tps = 177290.033472 (including connections establishing)
tps = 177306.915203 (excluding connections establishing)
script statistics:
 - statement latencies in milliseconds:
         0.002  \set id random(1,100000)
         0.315  insert into tbl values (:id);

3、资源开销

last pid: 36817;  load avg:  32.9,  15.7,  7.27;       up 15+00:46:36                                                                                                                                                               17:59:17
63 processes: 34 running, 29 sleeping
CPU states: 42.3% user,  0.0% nice, 20.4% system, 37.1% idle,  0.2% iowait
Memory: 192G used, 29G free, 116M buffers, 186G cached
DB activity: 168654 tps,  0 rollbs/s, 928 buffer r/s, 99 hit%,    176 row r/s, 168649 row w/
DB I/O:     0 reads/s,     0 KB/s,     0 writes/s,     0 KB/s
DB disk: 1455.4 GB total, 425.2 GB free (70% used)
Swap:

未分区表性能

postgres=# drop trigger tg1 on tbl ;

1、TPS

transaction type: ./test.sql
scaling factor: 1
query mode: prepared
number of clients: 56
number of threads: 56
duration: 120 s
number of transactions actually processed: 31188395
latency average = 0.215 ms
latency stddev = 0.261 ms
tps = 259884.798007 (including connections establishing)
tps = 259896.495810 (excluding connections establishing)
script statistics:
 - statement latencies in milliseconds:
         0.002  \set id random(1,100000)
         0.214  insert into tbl values (:id);

2、资源开销

last pid: 36964;  load avg:  31.7,  18.7,  8.89;       up 15+00:47:41                                                                                                                                                               18:00:22
63 processes: 45 running, 18 sleeping
CPU states: 33.3% user,  0.0% nice, 26.8% system, 39.8% idle,  0.1% iowait
Memory: 194G used, 26G free, 118M buffers, 188G cached
DB activity: 256543 tps,  0 rollbs/s, 1006 buffer r/s, 99 hit%,    176 row r/s, 256538 row w
DB I/O:     0 reads/s,     0 KB/s,     0 writes/s,     0 KB/s
DB disk: 1455.4 GB total, 424.8 GB free (70% used)
Swap:

非整型字段,如何实现哈希分区

1、PostgreSQL内部提供了类型转换的哈希函数,可以将任意类型转换为整型。

                                   List of functions
   Schema   |      Name      | Result data type |     Argument data types     |  Type
------------+----------------+------------------+-----------------------------+--------
 pg_catalog | hash_aclitem   | integer          | aclitem                     | normal
 pg_catalog | hash_array     | integer          | anyarray                    | normal
 pg_catalog | hash_numeric   | integer          | numeric                     | normal
 pg_catalog | hash_range     | integer          | anyrange                    | normal
 pg_catalog | hashbpchar     | integer          | character                   | normal
 pg_catalog | hashchar       | integer          | "char"                      | normal
 pg_catalog | hashenum       | integer          | anyenum                     | normal
 pg_catalog | hashfloat4     | integer          | real                        | normal
 pg_catalog | hashfloat8     | integer          | double precision            | normal
 pg_catalog | hashinet       | integer          | inet                        | normal
 pg_catalog | hashint2       | integer          | smallint                    | normal
 pg_catalog | hashint4       | integer          | integer                     | normal
 pg_catalog | hashint8       | integer          | bigint                      | normal
 pg_catalog | hashmacaddr    | integer          | macaddr                     | normal
 pg_catalog | hashmacaddr8   | integer          | macaddr8                    | normal
 pg_catalog | hashname       | integer          | name                        | normal
 pg_catalog | hashoid        | integer          | oid                         | normal
 pg_catalog | hashoidvector  | integer          | oidvector                   | normal
 pg_catalog | hashtext       | integer          | text                        | normal
 pg_catalog | hashvarlena    | integer          | internal                    | normal
 pg_catalog | interval_hash  | integer          | interval                    | normal
 pg_catalog | jsonb_hash     | integer          | jsonb                       | normal
 pg_catalog | pg_lsn_hash    | integer          | pg_lsn                      | normal
 pg_catalog | time_hash      | integer          | time without time zone      | normal
 pg_catalog | timestamp_hash | integer          | timestamp without time zone | normal
 pg_catalog | timetz_hash    | integer          | time with time zone         | normal
 pg_catalog | uuid_hash      | integer          | uuid                        | normal

2、其他字段类型的哈希表方法如下

如 hashtext

drop table tbl;  

create table tbl (id text, info text, crt_time timestamp);  

do language plpgsql $$
declare
  parts int := 4;
begin
  for i in 0..parts-1 loop
    execute format('create table tbl%s (like tbl including all) inherits (tbl)', i);
    execute format('alter table tbl%s add constraint ck check(abs(mod(hashtext(id),%s))=%s)', i, parts, i);
  end loop;
end;
$$;  

create or replace function ins_tbl() returns trigger as $$
declare
begin
  case abs(mod(hashtext(NEW.id),4))
    when 0 then
      insert into tbl0 values (NEW.*);
    when 1 then
      insert into tbl1 values (NEW.*);
    when 2 then
      insert into tbl2 values (NEW.*);
    when 3 then
      insert into tbl3 values (NEW.*);
    else
      return NEW;
    end case;
    return null;
end;
$$ language plpgsql strict;  

create trigger tg1 before insert on tbl for each row when (NEW.id is not null) execute procedure ins_tbl();

性能与整型一样。

传统分区性能 对比 非分区表 - 性能结果

1、性能

模式 insert N 行/s
基于trigger的hash分区 17.7 万
未分区 26 万

2、CPU资源开销

模式 user system idle
基于trigger的hash分区 42.3% 20.4% 37.1%
未分区 33.3% 26.8% 39.8%

小结

除了传统的基于trigger和rule的分区,PostgreSQL 10开始已经内置了分区功能(目前仅支持list和range),使用pg_pathman则支持hash分区。

从性能角度,目前最好的还是pg_pathman分区。

《PostgreSQL 10 内置分区 vs pg_pathman perf profiling》

《PostgreSQL 10.0 preview 功能增强 - 内置分区表》

《PostgreSQL 9.5+ 高效分区表实现 - pg_pathman》

但是,传统的分区手段,依旧是最灵活的,在其他方法都不奏效时,可以考虑传统方法。

传统手段中,最懒散的做法(当然是以牺牲性能为前提),例子:

《PostgreSQL general public partition table trigger》

时间: 2024-08-03 21:12:27

PostgreSQL 传统 hash 分区方法和性能的相关文章

使用facebook linkbench 测试PostgreSQL社交关系图谱场景性能

facebook linkbench 测试PostgreSQL社交关系图谱场景性能 作者 digoal 日期 2016-09-11 标签 PostgreSQL , facebook , linkbench 背景 Linkbench是facebook提供的一款数据库性能测试开源软件,它的思路是围绕社交关系图谱构建一套测试数据,然后在此基础之上对数据进行关系查询,新增关系,断开关系等操作. Linkbench的介绍可参考https://www.facebook.com/notes/facebook-

PostgreSQL OLTP高并发请求性能优化

在多核系统中,一般TPS会随并发数的增加而提升,但是当并发数超过一定的数值(如CPU核数的2到3倍以后),性能开始下降,并发数越高,下降越严重. 例子: 更新500万记录表中的1条随机记录.开8000个并发. create table test_8000 (id int primary key, cnt int default 0); insert into test_8000 select generate_series(1,5000000); vi t.sql \setrandom id 1

PostgreSQL雕虫小技,分组TOP性能提升44倍

业务背景 按分组取出TOP值,是非常常见的业务需求.比如提取每位歌手的下载量TOP 10的曲目.提取每个城市纳税前10的人或企业. 传统方法 传统的方法是使用窗口查询,PostgreSQL是支持窗口查询的.例子测试表和测试数据,生成10000个分组,1000万条记录. postgres=# create table tbl(c1 int, c2 int, c3 int); CREATE TABLE postgres=# create index idx1 on tbl(c1,c2); CREAT

PostgreSQL on XFS vs EXT4 性能

内核版本 3.18.24 系统内存256G. 测试使用了3块PCI-E SSD. # pvcreate /dev/dfa # pvcreate /dev/dfb # pvcreate /dev/dfc # vgcreate vgdata01 /dev/dfa /dev/dfb /dev/dfc # lvcreate -i 3 -I 8 -L 4T -n lv01 aliflash # lvcreate -i 3 -I 8 -L 2G -n lv02 aliflash 系统刷脏页内核参数,如下,尽

PostgreSQL 用 CTE语法 + 继承 实现平滑拆分大表

标签 PostgreSQL , 拆分大表 , 继承 , cte 背景 业务设计初期可能不会考虑到表将来会有多大,或者由于数据日积月累,单表会变得越来越大. 后面在考虑分区的话,应该怎么将单表切换成分区表呢? 这里可以用到PostgreSQL的CTE语法,以及继承功能,还有内置的分区表功能. 例子 具体步骤 1.创建分区表 2.创建继承关系,分区表继承自需要拆分的表 3.用cte转移数据 4.全部转移完成后,在事务中切换表名 例子,将tbl_big切换成哈希分区 1.创建被迁移的大表 create

PostgreSQL 9.5+ 高效分区表实现 - pg_pathman

PostgreSQL 9.5+ 高效分区表实现 - pg_pathman 作者 digoal 日期 2016-10-24 标签 PostgreSQL , 分区表 , pg_pathman , custom scan api 背景 目前PostgreSQL社区版本的分区表功能比较弱,需要通过继承和触发器或RULE来实现分区表的功能,由于查询和更新涉及约束的检查.插入则涉及触发器或规则重写,导致分区功能性能较差. 商业版本EDB,以及数据仓库Greenplum都有比较好的分区支持. 去年GP开源后,

PostgreSQL 主机性能测试方法 - 单机单实例

背景 业界有一些通用的数据库性能测试模型,可以用来测试硬件在不同测试模型下的性能表现. 参考http://www.tpc.org/https://github.com/oltpbenchmark/oltpbenchhttp://oltpbenchmark.com/ 本文主要以PostgreSQL为例,向大家介绍一下,如何使用PostgreSQL来测试硬件的性能. PostgreSQL 的功能非常的强大,所以可以适用于几乎所有的测试模型,同时用户还可以根据自己的应用场景设计测试模型. 前面已经介绍

PostgreSQL 主机性能测试方法 - 单机多实例

背景 业界有一些通用的数据库性能测试模型,可以用来测试硬件在不同测试模型下的性能表现. 参考http://www.tpc.org/https://github.com/oltpbenchmark/oltpbenchhttp://oltpbenchmark.com/ 本文主要以PostgreSQL为例,向大家介绍一下,如何使用PostgreSQL来测试硬件的性能. PostgreSQL 的功能非常的强大,所以可以适用于几乎所有的测试模型,同时用户还可以根据自己的应用场景设计测试模型. 一.机器部署

PostgreSQL vs Greenplum Hash outer join (hash表的选择)

标签 PostgreSQL , Greenplum , hash outer join , hash table 背景 数据分析.大表JOIN.多表JOIN时,哈希JOIN是比较好的提速手段. hash join会首先扫描其中的一张表(包括需要输出的字段),根据JOIN列生成哈希表.然后扫描另一张表. hash join介绍 https://www.postgresql.org/docs/10/static/planner-optimizer.html the right relation is