PostgreSQL 单表并行bulkload的extend file lock 冲突问题解决

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

标签

PostgreSQL , 并行加载 , 扩展数据文件 , extend lock , 消除


背景

PostgreSQL的数据文件是一个一个BLOCK extend的,当并行的会话插入同一个表,并且都遇到free space map反馈无BLOCK可插时,需要extend数据文件,有两种做法:

1、老的版本,只有一个会话可以EXTEND FILE,其他会话等它EXTEND结束后继续EXTEND。

《PostgreSQL bulk COPY load Bottleneck by extend lock waiting》

2、较新版本的PG,同样只有一个会话可以EXTEND FILE,但是如果其他会话也需要扩展同一个表的FILE时,会一次性扩展多个BLOCK,降低冲突。

《parallel blocking|waiting by slow BLOCK extend relation , ExclusiveLock on extension of relation》

由于EXTEND FILE改变了FILE的INODE信息,需要FSYNC操作。所以扩展数据块是比较耗时的操作。

前面两篇文章介绍了扩展数据块的冲突带来的性能问题。同时提到了内核层面的解决方法,在内核层面没有优化时,有什么方法呢?

预扩展数据块

当我们已知某个表将要导入大量数据时,可以先扩展好数据块,等高并发导入时,就不会有extend file的冲突了。

非预加载

1、创建预加载表

postgres=# create unlogged table tbl_pre(id int, info text, crt_time timestamp);  
CREATE TABLE  

2、创建数据源表

postgres=# create unlogged table tbl_content(id int, info text, crt_time timestamp);  
CREATE TABLE  

3、写入1000万记录

postgres=# insert into tbl_content select generate_series(1,10000000), repeat(md5(random()::text), 10), now();  
INSERT 0 10000000  

4、当不使用预加载时,写入1000万数据,耗时12秒。

postgres=# insert into tbl_pre select * from tbl_content ;  
INSERT 0 10000000  
Time: 11921.491 ms (00:11.921)  

预加载

1、往预加载表写入一条无效记录

postgres=# insert into tbl_pre values (-1);  
INSERT 0 1  
Time: 0.358 ms  

2、删除无效记录以外的其他记录。

postgres=# delete from tbl_pre where id<>-1;  
DELETE 10000000  
Time: 3417.401 ms (00:03.417)  

3、垃圾回收,由于无效记录在FILE的末尾BLOCK,所以这个表垃圾回收后,所有的BLOCK都会被保留,用于下次写入时,就不需要EXTEND BLOCK了。

postgres=# vacuum tbl_pre;  
VACUUM  
Time: 1221.166 ms (00:01.221)  

4、往预加载表写入数据,现在耗时5秒。降低了7秒。

postgres=# insert into tbl_pre select * from tbl_content ;  
INSERT 0 10000000  
Time: 4941.610 ms (00:04.942)  

如果是并行加载,效果会更加明显。

并行加载,非预分配模式

1、清除预分配表,清空到0个BLOCK,接下来的INSERT将需要EXTEND BLOCK。

postgres=# truncate tbl_pre ;  
TRUNCATE TABLE  

2、新建一个源数据表,写入100万记录

postgres=# create unlogged table tbl_content1(id int, info text, crt_time timestamp);  
CREATE TABLE  
postgres=# insert into tbl_content1 select generate_series(1,1000000), repeat(md5(random()::text), 10), now();  
INSERT 0 1000000  

3、新建一个脚本,往预分配表写入100万记录

vi test.sql  
  
insert into tbl_pre select * from tbl_content1;  

4、开启56个并发,每个并发往预分配表写入100万记录,一共写入5.6千万记录。耗时41.5秒。

pgbench -M prepared -n -r  -f ./test.sql -c 56 -j 56 -t 1  
transaction type: ./test.sql  
scaling factor: 1  
query mode: prepared  
number of clients: 56  
number of threads: 56  
number of transactions per client: 1  
number of transactions actually processed: 56/56  
latency average = 42696.543 ms  
tps = 1.311582 (including connections establishing)  
tps = 1.311711 (excluding connections establishing)  
statement latencies in milliseconds:  
     41534.443  insert into tbl_pre select * from tbl_content1;  

等待严重

iotop  
  
Total DISK READ :      15.43 K/s | Total DISK WRITE :     402.79 M/s  
Actual DISK READ:      15.43 K/s | Actual DISK WRITE:     590.21 M/s  
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND                                                                                                                                                                         
12881 be/4 root        0.00 B/s    0.00 B/s  0.00 % 49.34 % [kworker/u112:1]  
25518 be/4 digoal      3.86 K/s   10.88 M/s  0.00 %  8.22 % postgres: postgres postgres [local] INSERT waiting  
25485 be/4 digoal      3.86 K/s   15.46 M/s  0.00 %  4.72 % postgres: postgres postgres [local] INSERT waiting  
25557 be/4 digoal      3.86 K/s   15.46 M/s  0.00 %  0.67 % postgres: postgres postgres [local] INSERT waiting  
25553 be/4 digoal      3.86 K/s   12.69 M/s  0.00 %  0.57 % postgres: postgres postgres [local] INSERT waiting  
25467 be/4 digoal      0.00 B/s   15.46 M/s  0.00 %  0.23 % postgres: postgres postgres [local] INSERT waiting  
25548 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.22 % postgres: postgres postgres [local] INSERT waiting  
25526 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.22 % postgres: postgres postgres [local] INSERT waiting  
25556 be/4 digoal      0.00 B/s    6.48 M/s  0.00 %  0.21 % postgres: postgres postgres [local] INSERT waiting  
25521 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.20 % postgres: postgres postgres [local] INSERT waiting  
25523 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.20 % postgres: postgres postgres [local] INSERT waiting  
25568 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.20 % postgres: postgres postgres [local] INSERT waiting  
25543 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.20 % postgres: postgres postgres [local] INSERT waiting  
25561 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.20 % postgres: postgres postgres [local] INSERT waiting  
25559 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.20 % postgres: postgres postgres [local] INSERT waiting  
25478 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.20 % postgres: postgres postgres [local] INSERT waiting  
25533 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.20 % postgres: postgres postgres [local] INSERT waiting  
25512 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.19 % postgres: postgres postgres [local] INSERT waiting  
25550 be/4 digoal      0.00 B/s   10.28 M/s  0.00 %  0.19 % postgres: postgres postgres [local] INSERT waiting  
25569 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.19 % postgres: postgres postgres [local] INSERT waiting  
25540 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.18 % postgres: postgres postgres [local] INSERT waiting  
25566 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.18 % postgres: postgres postgres [local] INSERT waiting  
25536 be/4 digoal      0.00 B/s    0.00 B/s  0.00 %  0.18 % postgres: postgres postgres [local] INSERT waiting  

5、导入时,查看等待事件,几乎全部都是extend file的等待

postgres=# select backend_type,wait_event_type,wait_event,count(*) from pg_stat_activity group by 1,2,3;  
         backend_type         | wait_event_type |     wait_event      | count   
------------------------------+-----------------+---------------------+-------  
 logical replication launcher | Activity        | LogicalLauncherMain |     1  
 walwriter                    | Activity        | WalWriterMain       |     1  
 autovacuum launcher          | Activity        | AutoVacuumMain      |     1  
 background writer            | Activity        | BgWriterMain        |     1  
 client backend               | IO              | DataFileExtend      |     1  
 checkpointer                 | Activity        | CheckpointerMain    |     1  
 client backend               | Lock            | extend              |    53  
 client backend               |                 |                     |     3  
(8 rows)  

并行加载,预分配模式

1、往预加载表,末尾追加一条无效记录。

postgres=# insert into tbl_pre values (-1);  
INSERT 0 1  

2、删除无效记录以外的所有记录。

postgres=# delete from tbl_pre where id<>-1;  
DELETE 56000000  

3、垃圾回收,由于无效记录在FILE的末尾BLOCK,所以这个表垃圾回收后,所有的BLOCK都会被保留,用于下次写入时,就不需要EXTEND BLOCK了。

postgres=# vacuum tbl_pre;  

4、并行加载压测,现在耗时1.5秒。相比未预分配BLOCK时,节约了40秒时间。

pgbench -M prepared -n -r  -f ./test.sql -c 56 -j 56 -t 1  
transaction type: ./test.sql  
scaling factor: 1  
query mode: prepared  
number of clients: 56  
number of threads: 56  
number of transactions per client: 1  
number of transactions actually processed: 56/56  
latency average = 1613.094 ms  
tps = 34.715898 (including connections establishing)  
tps = 34.835685 (excluding connections establishing)  
statement latencies in milliseconds:  
      1529.876  insert into tbl_pre select * from tbl_content1;  
postgres=# select count(*) from tbl_pre;  
  count     
----------  
 56000001  
(1 row)  

小结

当需要大批量,高并发导入数据到单个表里面时,extend file的冲突会成为写入的瓶颈。

建议可取的方法如下:

1、像本文一样,提前预分配好空间,避免并行加载时,extend file。特别适合每天都有定时的导入任务的情况,因为可以计划。(单个并发导入1000万数据从12秒降低到了5秒。56个并发导入5.6千万数据,从41.5秒降低到了1.5秒)

2、拆成分区表,并行的往分区表写入,这样可以降低EXTEND FILE的冲突,因为有多个分区的话,就对应有多个FILE。

方法1解决了EXTEND FILE的锁冲突问题,但是当表上面有索引时,索引的LOCK问题依旧存在。而方法2,解决了两个问题,但是引入了另一个问题,如果要查询全局数据,在走索引扫描时,性能可能不如单表(因为多个分区,导致索引也是分区索引)。

参考

《PostgreSQL bulk COPY load Bottleneck by extend lock waiting》

《parallel blocking|waiting by slow BLOCK extend relation , ExclusiveLock on extension of relation》

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
5月前
|
消息中间件 存储 关系型数据库
PostgreSQL技术大讲堂 - 第33讲:并行查询管理
PostgreSQL从小白到专家,技术大讲堂 - 第33讲:并行查询管理
289 1
|
4月前
|
关系型数据库 MySQL 分布式数据库
PolarDB MySQL版并行查询技术探索与实践
PolarDB MySQL版并行查询技术探索与实践 PolarDB MySQL版在企业级查询加速特性上进行了深度技术探索,其中并行查询作为其重要组成部分,已经在线稳定运行多年,持续演进。本文将详细介绍并行查询的背景、挑战、方案、特性以及实践。
108 2
|
4月前
|
SQL 关系型数据库 分布式数据库
深度解析PolarDB数据库并行查询技术
深度解析PolarDB数据库并行查询技术:加速SQL执行的关键问题和核心技术 随着数据规模的不断扩大,用户SQL的执行时间越来越长,这不仅对数据库的优化能力提出更高的要求,并且对数据库的执行模式也提出了新的挑战。为了解决这个问题,许多数据库系统,包括Oracle、SQL Server等,都开始提供并行查询引擎的支持,以充分利用系统资源,达到加速SQL执行的效果。本文将深入探讨基于代价进行并行优化、并行执行的云数据库的并行查询引擎的关键问题和核心技术。
123 2
|
存储 SQL 人工智能
PolarDB 弹性并行查询(ePQ)功能使用白皮书
1 法律声明阿里云提醒您在阅读或使用本文档之前仔细阅读、充分理解本法律声明各条款的内容。如果您 阅读或使用本文档,您的阅读或使用行为将被视为对本声明全部内容的认可。您应当通过阿里云网站或阿里云提供的其他授权通道下载、获取本文档,且仅能用于自身的 合法合规的业务活动。本文档的内容视为阿里云的保密信息,您应当严格遵守保密义务; 未经 阿里云事先书面同意,您不得向任何第三方披露本手册内容或提供给任何第三
452 0
PolarDB 弹性并行查询(ePQ)功能使用白皮书
|
存储 SQL Cloud Native
基于 PolarDB for MySQL 实现并行创建索引赛题解析 | 学习笔记
快速学习基于 PolarDB for MySQL 实现并行创建索引赛题解析
174 0
基于 PolarDB for MySQL 实现并行创建索引赛题解析 | 学习笔记
|
存储 Cloud Native 关系型数据库
PolarDB MySQL 弹性多机并行深度剖析
背景并行查询(Parallel Query)是自PolarDB MySQL诞生伊始就致力于研发的企业级查询加速功能,这与PolarDB的产品定位密切相关,基于云原生的计算存储分离使底层数据量远突破单机容量的限制,而针对更海量数据的复杂分析、报表类业务也成为用户自然而然的需求,同时由于PolarDB是服务于在线业务(OLTP)的关系数据库系统,用户会希望分析业务能具有"在线"的能
823 0
PolarDB MySQL 弹性多机并行深度剖析
|
存储 SQL Cloud Native
PolarDB 并行查询的前世今生
本文会深入介绍PolarDB MySQL在并行查询这一企业级查询加速特性上做的技术探索、形态演进和相关组件的实现原理,所涉及功能随PolarDB MySQL 8.0.2版本上线。
969 2
PolarDB 并行查询的前世今生
|
存储 SQL Cloud Native
PolarDB 并行查询的前世今生
本文会深入介绍PolarDB MySQL在并行查询这一企业级查询加速特性上做的技术探索、形态演进和相关组件的实现原理,所涉及功能随PolarDB MySQL 8.0.2版本上线。
PolarDB 并行查询的前世今生
|
存储 SQL Cloud Native
PolarDB 并行查询深入剖析
本文会深入介绍PolarDB MySQL在并行查询这一企业级查询加速特性上做的技术探索、形态演进和相关组件的实现原理,所涉及功能随PolarDB MySQL 8.0.2版本上线。背景PolarDB云的兴起为古老而顽固的数据库市场带来了新的发展机遇,据Gartner预测,到 2022 年,所有数据库中将有 75% 部署或迁移到云平台,云原生数据库的诞生为各个数据库厂商、云服务供应商提供了弯道超车的绝
590 0
|
SQL 存储 算法
PostgreSQL并行HashJoin解读
PostgreSQL并行HashJoin解读
PostgreSQL并行HashJoin解读

相关产品

  • 云原生数据库 PolarDB