AirJD 焦点
AirJD

没有录音文件
00:00/00:00
加收藏

PostgreSQL表分区实践 by 许中清@华为

发布者 PostgreSQL
发布于 1430807068888  浏览 8661 关键词 PostgreSQL, 数据库 
分享到

第1页

PostgreSQL 表分区实践分享

许中清 @Huawei jonathan.shv@gmail.com



第2页

• 表分区是RDBMS最重要的特性之一 • PG用继承表实现“表分区” • PG实现原生表分区:三种方案 • 实践中的关键问题



第3页

表分区

分而治之:表分区就是把一张大表的数据根据一定策略拆分成若干个子表



CREATE TABLE op_log(



log_no



bigint,



creation_time date,



created_by text,



content



text)



PARTITION BY RANGE(creation_time)



( PARTITION p1 VALUES LESS THAN (DATE'2014-07-01'),



PARTITION p2 VALUES LESS THAN (DATE'2015-01-01'),



PARTITION p3 VALUES LESS THAN (DATE'2015-07-01'),



PARTITION p4 VALUES LESS THAN (MAXVALUE)



);



第4页

分而治之:解决复杂问题的方法论

• 提高数据聚集度(按分区键) • 利于多核能力,并行处理,提

升分析类业务性能

性能



易管理

• 表的维护从操作一个大表变 成操作多个子表

• 冷热数据差异化管理操作。



可用性

• 表的维护与读写产生冲突

• 表分区缩短维护时间窗,提供 可用性



第5页

重要:表分区特性广泛应用

• 在数据仓库领域,表分区是最常使用的特性之一



第6页

• 表分区是RDBMS最重要的特性之一 • PG用继承表实现“表分区” • PG实现原生表分区:三种方案 • 实践中的关键问题



第7页

PG如何处理继承表



INSERT:子表不继承



CREATE TABLE join_table(c1 int,c2 int); CREATE TABLE parent (c1 int,c2 int); CREATE TABLE child1(c3 int) inherits (parent); CREATE TABLE child2(c4 int) inherits (parent);



INSERT INTO parent VALUES(10,10);



SELECT:子表在底层展开

SELECT * FROM parent p, join_table j WHERE p.c2=j.c2;



第8页

PG如何处理继承表(2)

UPDATE/DELETE: 执行计划从顶层展开所有子表

UPDATE parent p SET c1=j.c1 FROM join_table j WHERE p.c2=j.c2;



第9页

PG使用表继承可实现表分区



op_log



创建表分区四步法



log_no creation_time created_by content



① 创建主表:CREATE TABLE op_log(…);



op_log_2012

log_no creation_time created_by content



op_log_2013



< DATE'2013-01-01'



log_no creation_time created_by content



DATE'2013-01-01'



< DATE'2014-01-01'



op_log_2014

log_no creation_time created_by content



DATE'2014-01-01'



< DATE'2015-01-01'



②创建子表:

CREATE TABLE op_log_2013() INHERITS(op_log);

③增加子表约束:

ALTER TABLE op_log_2013 ADD CONSTRAINT log_range CHECK (creation_time>DATE’2013-01-01’ AND creation_time<DATE’2014-01-01’);



指定路由机制: (Trigger/Rule) CREATE TRIGGER router_to_2013 BEFORE INSERT ON log_range FOR EACH ROW EXECUTE PROCEDURE …;



partition_range.sql



第10页

PG“表分区”性能(INSERT)



在批量插入的场景下,trigger对性能影响很大



ms 25000 20000 15000 10000

5000 0



20987 9875

循环插入



203 批量插入



通过主表插入(Trigger) 直接插入子表



测试环境:5个子表,插入10K条数据,插入的所有数据都在一个分区内 每个tuple的大小16 byte



第11页

PG“表分区”性能(SELECT)



SELECT生成执行计划的时间 与 子表数量成线性关系



1000m0 s 9000 8000 7000 6000 5000 4000 3000 2000 1000 0 010460768



1828 940700 875



3750 1875



5422 2687









全表扫描 范围查询 点查找







子表数量 全表扫描 范围查询 点查询



5 16 16



50 100 500



1k



2k



3k



5k



46 78 900 1,828 3,750 5,422 9,547



47 78 500 937 1,900 2,750 5,203



470 875 1,875 2,687 4,797



原因:



对主表的查询会展开成N+1条类似的查询语句。(N为主表的子表数量) 相当于要把同一条SQL语句(主表替换成子表)优化N+1遍



第12页

PG“表分区”:管理不易

1. 创建分区表: (四步法)

1. 步骤复杂(全手工) 2. 易出错

2. 创建索引:

3. 创建N+1个索引

3. 分区表的管理:

4. 增加子表,三步。并需要修改主表路 由机制



第13页

• 表分区是RDBMS最重要的特性之一 • PG用继承表实现“表分区” • PG实现原生表分区:三种方案 • 实践中的关键问题



第14页

改造PG:原生支持表分区



性能

1. 最大分区数量 2. CURD的性能 3. 复杂查询的时延 4. 批量业务的性能



设计原则

满足业务需求

1. 表分区策略 2. 性能基线 3. 分区管理功能



内核侵入最小

1. 保持PG原生架 构,风险可控

2. 侵入内核过多会 导致稳定周期长



第15页

改造PG-方案1:在上包一层



1. 父表是一张没有物理属性的虚拟表



pg_index



pg_attribute



2. 增加pg_partition保存分区信息



3. 每个子表也是pg_class中一张表

op_log



pg_trigger



op_log_2012



pg_class



op_log_2013



pg_constraint



op_log_2014



pg_partition



pg_statistic



优点: 1.

2. 缺点:

3. 4. 5.



普通表、父表、子表id在同一个id空间。元数据缓存空间、存 储引擎接口无需改动。 改动小,实现相对容易

每一个子表都有一份元数据,冗余度高,系统资源占用高 表模式修改(DDL)需要更新所有子表元数据,失效消息数量剧增 因为以上原因导致对分区数的限制



pg_toast



第16页

改造PG-方案2:在下加一层分区表的逻辑/物理元数据解耦:

1. 所有子表的逻辑元数据继承父表



pg_index



pg_attribut方e 案2为用当方p前g案_采toast



2. 每个子表独立保存物理元数据



op_log



pg_trigger



op_log_2012



pg_class



op_log_2013



pg_constraint



op_log_2014



pg_partition



pg_statistic



优点: 1. 元数据缓存大大减小:所有子表的逻辑元数据只需要存一份 2. DDL语句修改更高效 3. 子表数量支持的更多

缺点: (复杂度、工作量、内核侵入程度较高) 4. 普通表与子表的id不在一个id空间 5. 元数据缓存机制复杂:子表需要单独的缓存空间 6. 存储引擎接口必须要区分普通表与分区表,并新增处理分支



第17页

改造PG-方案3:向下拉一层

pg_attribute



所有表的逻辑/物理元数据解耦: 1. 所有表的元数据分逻辑/物理两层,对

应将pg_class拆分成pg_class_logic 和pg_class_physical两层 2. 普通表是只有一个子表的特殊分区表

缺点: 1. 对PG的程序架构和代码要



pg_class_logic

逻辑元数据层 物理元数据层 pg_class_physical



pg_constraint pg_statistic



伤筋动骨的修改



优点: 1. 从逻辑上统一了分区表和普通表。所有的表id都在同一个空间 2. 元数据缓存和失效机制不需要再区分分区表和普通表 3. 存储引擎接口不需要区分分区表和普通表 4. 垃圾回收/统计采样等等都不再需要区分分区表和普通表



pg_trigger pg_toast



第18页

• 表分区是RDBMS最重要的特性之一 • PG用继承表实现“表分区” • PG实现原生表分区:三种方案 • 实践中的关键问题



第19页

关键问题1:元数据与缓存失效机制



PostgreSQL元数据缓存同步策略



分区表的关键问题



Table



1:1 RelationData



Session1



Session2



Session3



RelationData

PData

元数据表 缓存

进程1



RelationData

PData

元数据表 缓存

进程2

失效消息队列



RelationData

PData

元数据表 缓存

进程3



1. 一个RelationData包含一张表的所有表模式信息 2. 一个Session中同一张表的RelationData实例最多只有一个 3. RelationData是从优化器、执行器、到存储引擎等共用的数据

结构 4. 收到实效消息后RelationData会重建



Parent Table Partition



1:1

1:m 1:1



RelationData PData



1. 子表的数据结构(PData)如何作为 参数在系统中传递(改动最小)?难 道要改所有的函数接口吗?

2. 接收到表模式失效消息时,如何重 建主表和子表的信息?



第20页

关键问题2:分区表的统计信息

PostgreSQL统计信息:

create table sta_test(c1 int,c2 int); insert into sta_test values(generate_series(1,10),generate_series(100,200)); insert into sta_test values(generate_series(30,40),generate_series(300,400)); insert into sta_test values(generate_series(60,70),generate_series(600,700));

特征值统计信息(c1):MCV(Most Common Values)

直方图统计信息(c2):

分区表统计信息的关键问题: 1. 采集时,生成主表的统计信息还是每个子表的统计信息? 2. 如果使用主表的统计信息,那么Pruning之后的,据此计算的cost偏差 很大(特别是分区键上的cost) 3. 如果使用子表的统计信息,那么如何累加成主表的统计信息?子表太多 时,优化器效率如何保证?



第21页

关键问题3:表级锁与并发



PG表级锁冲突矩阵(简化)



分区表的关键问题



Requested Lock Mode

ACCESS SHARE (SELECT) ROW EXCLUSIVE (I/U/D/)



Current Lock Mode



ACCESS ROW



ACCESS



SHARE EXCLUSIVE EXCLUSIVE



(SELECT) (I/U/D/) (DDL)



Add/Drop Partition 时父表上加什么锁?



session1



session2



SELECT >350



Parent



DROP P2



X Pruning:0011

P1 P2 P3 P4

DROPED

100 200 300 400



X ERROR: P4?



ACCESS EXCLUSIVE (DDL)



X



X



X



注:pg有8个级别的表级锁,常见的3个



INSERT INTO Interval分区表如何避免为同一 个子表创建两个物理文件?

(Interval分区表:向某个子表插入第一条记录 时才会为这个子表创建物理文件)



第22页

关键问题4:子表剪枝算法



分区键是单列



分区 表:p_table(100,200,300,400,500) 查询: SELECT * FROM ptable WHERE c>150 and (c<250 or c>450)

AND



C>150 01111



OR 11101



C<250 11100



C>450 00001



分区键是多列 分区键: (c1,c2) 值域 P1: (100, 100) {(-∞,100], (-∞,+∞)} P2: (100, 200) {100, [100,200)} P3: (100, 300) {100, [200,300)} P4: (200, 200) {[100,200], (-∞,+∞)} P5: (200, 300) {200, [200,300)} P6: (200, 500) {200, [300,500)} P7: (300, 200) {[200,300], (-∞,+∞)} P8: (300, 400) {[300, [200,400]} 查询: SELECT * FROM ptable

?WHERE c2>350

Pruning结果:P1,P4,P6,P7,P8



第23页

① Scan(P1)



关键问题5:算子



Append



Iterator (P1,P2,P3)



Scan(P2)



Scan(P3)



Scan(Pi)



② Iterator

(P1,P2,P3)



Join P,Q分区策略完 全一样

Iterator (Q1,Q2,Q3)



Iterator (J1,J2,J3)

Join (P1,P2,P3) (Q1,Q2,Q3)



Scan(Pi)



Scan(Qi)



Scan(Pi)



Scan(Qi)



第24页

时延的代价



测试环境: 数据量 : 测试目的:



存储:SAS(15000rps)*2 计算:2P12C 100个子表,每个子表10w条数据,共1000w条数据 主要针对OLTP类场景,验证表分区之后的性能代价



s200



INSERT性能对比



160 142 140





100 80



75 65





55 50









50w(1并发)



200w(50并发)



PG继承表 分区表 PG普通表



SELECT(Index)性能对比



继承表场景下时间太长,中途中止



14 12



12 11.35







6 4



4 3.68





0 10w次/并发50 并发10w次/并发200 并发



PG继承表 分区表 PG普通表



UPDATE性能对比

继承表场景下时间太长,中途中止



40 35.374 35 30.519 30







15 10 6.185 5.554



分区表 PG普通表





1tuple/1sql100sql/1tx50 1并tu发ple/共1s更ql1新sq1l/01wtx条1 并发 共更新10w条



DELETE性能对比



30 25 20



继承表场景下时间太长,大约1200s 24.585

19.098





10 5 3.33 2.95



分区表 PG普通表





1tuple/1sql100sql/1tx501tu并ple发/1s共ql1删sq除l/1t0xw1条并发 共删除10w条



第25页

谢谢



支持文件格式:*.pdf
上传最后阶段需要进行在线转换,可能需要1~2分钟,请耐心等待。