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

本文涉及的产品
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介:

标签

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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
消息中间件 存储 关系型数据库
PostgreSQL技术大讲堂 - 第33讲:并行查询管理
PostgreSQL从小白到专家,技术大讲堂 - 第33讲:并行查询管理
463 1
|
关系型数据库 大数据 PostgreSQL
PostgreSQL16-新特性-并行聚合
PostgreSQL16-新特性-并行聚合
142 0
|
SQL 存储 算法
PostgreSQL并行HashJoin解读
PostgreSQL并行HashJoin解读
PostgreSQL并行HashJoin解读
|
SQL 分布式计算 并行计算
PostgreSQL 并行计算解说 之10 - parallel 自定义并行函数(UDF)
标签 PostgreSQL , cpu 并行 , smp 并行 , 并行计算 , gpu 并行 , 并行过程支持 背景 PostgreSQL 11 优化器已经支持了非常多场合的并行。简单估计,已支持27余种场景的并行计算。 parallel seq scan parallel index scan
1689 0
|
SQL 分布式计算 并行计算
PostgreSQL 并行计算解说 之9 - parallel 自定义并行聚合
标签 PostgreSQL , cpu 并行 , smp 并行 , 并行计算 , gpu 并行 , 并行过程支持 背景 PostgreSQL 11 优化器已经支持了非常多场合的并行。简单估计,已支持27余种场景的并行计算。 parallel seq scan parallel index scan
608 0
|
SQL 分布式计算 并行计算
PostgreSQL 并行计算解说 之25 - parallel FDW scan (并行访问多个外部表) with parallel append (FDW must with IsForeignScanParallelSafe)
标签 PostgreSQL , cpu 并行 , smp 并行 , 并行计算 , gpu 并行 , 并行过程支持 背景 PostgreSQL 11 优化器已经支持了非常多场合的并行。简单估计,已支持27余种场景的并行计算。 parallel seq scan
346 0
|
并行计算 算法 关系型数据库
PostgreSQL 11 并行计算算法,参数,强制并行度设置
标签 PostgreSQL , 并行计算 背景 PostgreSQL 并行计算原理、应用参考: 《PostgreSQL 多场景 沙箱实验》 优化器并行计算的并行度计算方法 1、总worker进程数 postgres=# show ; max_worker_processes ---------------------- 128 (1 row) 2、所有会话,在同一时刻的QUERY,并行计算最大允许开启的WORKER数。
4896 0
|
存储 SQL 关系型数据库
PostgreSQL 多维、图像 欧式距离、向量距离、向量相似 查询优化 - cube,imgsmlr - 压缩、分段、异步并行
标签 PostgreSQL , 多维 , 图像 , cube , imgsmlr , 压缩 , 分段 , 异步并行 , 欧式距离 背景 向量相似搜索越来越流行,PostgreSQL imgsmlr,cube插件分别应用于向量类型的存储,以及向量距离排序输出搜索。
2817 0
|
Web App开发 SQL 关系型数据库
Locking issue with concurrent DELETE / INSERT in PostgreSQL - 解法 advisory lock
标签 PostgreSQL , 原子性 , 并行锁同行 , advisory lock 背景 两个会话,同时对同一个ID值的记录执行删除后插入,有一个会话会出现删除0,插入失败的现象。 现象 CREATE TABLE test (id INT PRIMARY KEY); INSER...
876 0
|
SQL 关系型数据库 数据库
Postgresql lock锁等待检查
查看锁等待sql with t_wait as ( select a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,a.
1874 0

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版