本文作者蔡松露,是云猿生数据 CTO & 联结创始人,前阿里云数据库资深技术专家。目前负责云猿生数据产品研发工作,率领团队实现云原生数据库管理系统 KubeBlocks 的设计。在此文中,他对 PG on ECS(下文中以 ECS PG 代指)和 PG on K8s 两种计划做了性能比照,并提出了 PG on K8s 上的性能优化计划,以确保数据库在 K8s 上能满足用户对性能和稳定性的要求。
背景
近年来,很多企业的基础架构都有打算 all-in-K8s 的打算,心愿采纳基于 K8s 的数据库管控平台(如 KubeBlocks)作为自建 PostgreSQL 托管计划(下文以 KubeBlocks-PG 为例)。此外,数据库的容器化和 K8s 化是比拟新的话题,很多人对有状态利用上 K8s 抱有比拟大的狐疑态度,咱们心愿验证数据库在 K8s 上性能是否能满足生产要求。本文提供在私有云 ECS 上自建 PostgreSQL(下文中以 ECS PG 代指)和基于 K8s 的数据库管控平台作为自建 PostgreSQL 托管计划进行比照,并提出如何在 K8s 上优化 PG 性能的计划。
环境筹备
版本 | CPU | 内存 | 磁盘 | 网络 | 规格族 | 复制协定 | |
---|---|---|---|---|---|---|---|
ESC PG | 12.14 | 16C | 64G | ESSD PL1 500G | SLB | 独占 | 主备异步 |
ApeCloud PG | 12.14 | 16C | 64G | ESSD PL1 300G | SLB | 独占 | 主备异步 |
- 在云厂商托管的ACK服务上购买k8s集群并部署KubeBlocks,网络模式采纳Terway,Terway生产进去的Pod IP为VPC IP,保障一个VPC内的网络可达,简化了网络管理和利用开发的老本,node的规格为16C64G。
- 生产实例,一开始在独占的node上无奈生产出16C64G的规格,因为kubelet等agent还耗费局部资源,所以调低request和limit到14C56G后生产胜利。
应用kubectl edit编辑pg cluster的resource spec,去掉对request和limit的限度,保障压测过程中能够应用到16C CPU,buffers设置为16GB,创立 PG 实例。
kbcli cluster create --cluster-definition = postgresql
测试计划
Sysbench Read-intensive 测试:80% read + 20% write。
该测试场景读多写少,比拟靠近理论的生产场景。
第一轮压测:TPS跌0
从ECS压测机发动压测,通过VPC IP拜访PG。
Threads | Throughput | Latency(ms) | ||
---|---|---|---|---|
ApeCloud PG | ECS PG | ApeCloud PG | ECS PG | |
25 | 87264 | 91310 | 31.94 | 28.67 |
50 | 111063 | 140559 | 55.82 | 40.37 |
100 | 83032 | 159386 | 132.49 | 92.42 |
150 | 61865 | 140938 | 272.27 | 186.54 |
175 | 56487 | 134933 | 350.33 | 240.02 |
发现三个问题:
- CPU无奈打满:从ECS压测DB,DB所在node CPU无奈压满。
- 并发衰减快:随着压测并发数回升,ApeCloud PG性能衰减要比ECS PG快。
- TPS间歇性跌0:在压测的过程中经常出现间歇性的TPS跌0(307s开始)。
此时因为client和server端的CPU都无奈压满,所以狐疑是两头的网络链路有问题,尤其是狐疑SLB的规格是否达到下限,所以把SLB规格换成了slb.s3.large从新压测,ACK SLB的默认规格是 slb.s2.small。
换成slb.s3.large之后持续压测,问题仍然存在。
第二轮压测:网络链路排查
针对 SLB 提早设计测试case,应用sysbench select 1来模仿全链路网络提早,单纯的ping测试尽管也能反映局部网络提早,然而存在很多缺点,而且不能保障刺穿全链路,比方SLB设施对ping产生的ICMP报文会间接返回,导致SLB到Pod的后续链路无奈被探测到。
测试的发动端仍然是ECS,测试场景为:
ECS->Pod IP 应用VPC IP拜访,网络可中转
ECS->SLB IP->Pod IP 两头多了一层SLB
ECS-> ECS SLB IP ECS默认在PG前端内置了一层SLB
测试后果如下:
Threads | Throughput | Latency(ms) | ||||
---|---|---|---|---|---|---|
ApeCloud PG | ECS PG | ApeCloud PG | ECS PG | |||
Pod IP | SLB IP | SLB IP | Pod IP | SLB IP | SLB IP | |
25 | 107309 | 105298 | 92163 | 0.30 | 0.30 | 0.32 |
后果阐明ACK和SLB的网络都是失常的,性能稳定的概率不大,所以对SLB的狐疑根本能够排除。
第三轮压测:IO带宽调整
还是依照第一轮打算进行压测,这次从系统分析动手定性分析,查看云监控的ECS主机监控图。
发现两个景象:
- 磁盘读写带宽达到了对应规格的瓶颈,ESSD带宽和磁盘容量正相干,具体计算公式为: min{120+0.5*容量, 350}。300GB磁盘对应的带宽为270MB,从监控上看根本达到了瓶颈。
- 通过排查日志发现,在TPS 跌0的工夫点CPU使用率也有对应的上涨。
因为之前磁盘带宽达到了下限,所以针对IO带宽又加了一组测试,测试500GB磁盘的体现状况,500GB 磁盘对应的带宽为 min{120+0.5*500, 350} = 350MB,压测过程中发现在磁盘跑满的时候,CPU 仍然有锯齿状稳定,依据以往教训,这种抖动可能和 checkpoint 无关,然而也不至于到跌0的境地。
在一直减少磁盘带宽的过程中发现TPS跌0的景象失去缓解,因而针对这个发现一次性把磁盘带宽调到最高,换成ESSD PL2 1TB磁盘,对应带宽620MB,从图上看抖动仍然存在,但失去很大缓解,CPU使用率跌幅收窄。
再激进一点,间接降级到了ESSD PL3 2TB,磁盘带宽达到700MB。
TPS跌0根本缓解,然而仍然有比拟大的抖动,TPS从2400到1400,跌幅差不多40%,CPU抖动幅度收窄但仍然存在(@8183s)。
这一轮测试的论断就是IO带宽对CPU和TPS的影响很大,随着IO带宽的减少抖动幅度一直缩小,TPS跌0的问题隐没,然而即便IO带宽不做限度,TPS仍然有40%的上涨抖动,在排除了硬件的瓶颈束缚之后,这种抖动只可能和PG自身无关。
第四轮压测:Checkpoint与锁剖析
这次把眼光聚焦到Checkpoint上来,次要是把传导机制搞清楚,剖析IO限流是如何反馈到Checkpoint和事务的:
- PostgreSQL Checkpoint为何比其余数据库冲击要大?之前也测了一下MySQL,发现MySQL在做Checkpoint时抖动绝对要小很多。
- 即便IO限流,然而从监控看IO还是满的,事务不应该跌0,是不是此时带宽都被Checkpoint 占用了?为了更好地监控数据库和主机指标,关上KubeBlocks集成的Node Exporter监控。
再一次压测,发现跌0的时候有一次比拟大的内存回收,内存一次性被回收了10GB,这个量有点大,在不开Huge Page的时候,一个page frame 4KB,10GB大略是2.5MB的page数量,大量page的遍历和回收对os kernel page reclaim模块会有很大的压力,而且在那个工夫点上 os 卡了几十秒,导致下面的过程也都hang住,这种回收个别和 dirty_background_ratio 设置不合理无关,具体原理不再赘述。
执行 sysctl -a | grep dirty_background_ratio,发现 vm.dirty_background_ratio = 10。
调整background ratio为 5%:sysctl -w vm.dirty_background_ratio=5。
这个调整会让一些脏掉的page cache尽早刷下去,这个比例设置之所以要害,和PostgreSQL的实现有很大关系,PostgreSQL依赖os page cache,与Oracle、MySQL这些数据库的IO架构不同。MySQL应用DirectIO,不依赖零碎page cache,给内存治理模块带来的压力和反过来受到的影响会小很多,当然某些场景下DirectIO提早比写buffer cache会更大一些。
此时也开始关注PostgreSQL内核实现和日志,登录到Pod中,有如下发现:
一个WAL日志默认大小16MB。
root@postgres-cluster-postgresql-0:/home/postgres/pgdata/pgroot/data/pg_wal# du -sh 0000000A000001F300000077 16M 0000000A000001F300000077
压测过程中,PostgreSQL后盾过程会清理pg_wal目录下的WAL日志以腾出空间,通过strace发现最多一次删除了几百个文件,总计大小12GB(日志中的工夫都要 +8 个时区,所以 5:42 对应北京工夫 13:42):
2023-05-18 05:42:42.352 GMT,,,129,,64657f66.81,134,,2023-05-18 01:29:10 GMT,,0,LOG,00000,"checkpoint complete: wrote 680117 buffers (32.4%); 0 WAL file(s) added, 788 removed, 0 recycled; write=238.224 s, sync=35.28 6 s, total=276.989 s; sync files=312, longest=1.348 s, average=0.114 s; distance=18756500 kB, estimate=19166525 kB",,,,,,,,,"" 2023-05-18 05:42:42.362 GMT,,,129,,64657f66.81,135,,2023-05-18 01:29:10 GMT,,0,LOG,00000,"checkpoint starting: wal",,,,,,,,,"" 2023-05-18 05:42:44.336 GMT,"sysbenchrole","pgbenchtest",65143,"::1:43962",6465928f.fe77,1157,"SELECT",2023-05-18 02:50:55 GMT,36/46849938,0,LOG,00000,"duration: 1533.532 ms execute sbstmt1641749330-465186528: SEL ECT c FROM sbtest46 WHERE id=$1","parameters: $1 = '948136'",,,,,,,,"" 2023-05-18 05:42:44.336 GMT,"sysbenchrole","pgbenchtest",65196,"::1:44028",6465928f.feac,1137,"UPDATE",2023-05-18 02:50:55 GMT,57/43973954,949436561,LOG,00000,"duration: 1533.785 ms execute sbstmt493865735-6481814 15: UPDATE sbtest51 SET k=k+1 WHERE id=$1","parameters: $1 = '996782'",,,,,,,,""
能够看到,做Checkpoint的一瞬间,cpu idle就飙涨到了80%(对应TPS根本跌0)。
日志中局部事务的 duration 上涨到 1S+。
TPS跌0也在 13:44:20 这个工夫点完结。
2023-05-18 05:44:20.693 GMT,"sysbenchrole","pgbenchtest",65145,"::1:43964",6465928f.fe79,1178,"SELECT",2023-05-18 02:50:55 GMT,48/45617265,0,LOG,00000,"duration: 1942.633 ms execute sbstmt-1652152656-473838068: SE LECT c FROM sbtest37 WHERE id=$1","parameters: $1 = '1007844'",,,,,,,,""
13:45:41 开始做vacuum。
2023-05-18 05:45:41.512 GMT,,,87995,,646596d6.157bb,71,,2023-05-18 03:09:10 GMT,64/3879558,0,LOG,00000,"automatic aggressive vacuum of table ""pgbenchtest.public.sbtest45"": index scans: 1 pages: 0 removed, 66886 remain, 0 skipped due to pins, 2328 skipped frozen tuples: 14166 removed, 2005943 remain, 15904 are dead but not yet removable, oldest xmin: 944519757
13:47:04 checkpoint 真正实现。
2023-05-18 05:47:04.920 GMT,,,129,,64657f66.81,136,,2023-05-18 01:29:10 GMT,,0,LOG,00000,"checkpoint complete: wrote 680483 buffers (32.4%); 0 WAL file(s) added, 753 removed, 0 recycled; write=226.176 s, sync=32.53
整个过程的监控图:
发现CPU busy抖动和Checkpoint刷脏过程根本吻合。
全过程磁盘带宽始终打满:
跌0的时间段和checkpoint刷脏时间段基本一致:
通过看内存的稳定状况,发现内存回收导致的hang根本被打消,阐明之前dirty_background_ratio的参数调整无效。
此外还发现,在刷脏过程中,锁的数量始终比拟高,与非刷脏状态下的比照非常明显:
具体的锁有:
有时多个过程会抢同一把锁:
而且发现平时做IO的时候,磁盘带宽尽管会打满,然而事务之间很少抢锁,TPS也不会跌0,当锁竞争比拟显著的时候,就很容易跌0,而锁的竞争又和Checkpoint间接相干。
第五轮压测:PG内核代码剖析与trace
持续从Checkpoint实现动手剖析跌0起因,浏览了大量PostgreSQL Checkpoint和WAL局部的代码实现,并对PostgreSQL backend过程进行Trace,发现WAL日志创立存在问题,其中的duration 数据是通过脚本剖析日志计算得出的:
duration:550 ms 11:50:03.951036 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002EE000000E7.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 22
duration:674 ms 11:50:09.733902 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002EF00000003.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 22
duration:501 ms 11:50:25.263054 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002EF0000004B.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 23
duration:609 ms 11:50:47.875338 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002EF000000A8.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 25
duration:988 ms 11:50:53.596897 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002EF000000BD.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 29
duration:1119 ms 11:51:10.987796 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002EF000000F6.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 29
duration:1442 ms 11:51:42.425118 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F000000059.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 45
duration:1083 ms 11:51:52.186613 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F000000071.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 51
duration:503 ms 11:52:32.879828 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F0000000D8.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 75
duration:541 ms 11:52:43.078011 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F0000000EB.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 84
duration:1547 ms 11:52:56.286199 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F10000000C.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 84
duration:1773 ms 11:53:19.821761 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F10000003D.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 94
duration:2676 ms 11:53:30.398228 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F10000004F.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 101
duration:2666 ms 11:54:05.693044 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F100000090.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 122
duration:658 ms 11:54:55.267889 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F1000000E5.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 139
duration:933 ms 11:55:37.229660 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F200000025.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 163
duration:2681 ms 11:57:02.550339 openat(AT_FDCWD, "pg_wal/archive_status/00000010000002F200000093.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 197
这几个WAL日志文件从开始创立到ready须要500ms以上,有的甚至到了2.6S,这也是咱们观测到有些事务duration大于2S的起因,因为事务要挂起期待WAL文件ready能力持续写入。
WAL 创立的具体流程:
- stat(pg_wal/00000010000002F200000093)找不到文件
- 应用pg_wal/xlogtemp.129来创立
- 清零pg_wal/xlogtemp.129
- 建设软连贯link(“pg_wal/xlogtemp.129”, “pg_wal/00000010000002F200000093”)
- 关上pg_wal/00000010000002F200000093
- 在尾部写入元数据
- 加载并利用该WAL文件
查看PostgreSQL日志发现,那个时刻客户端有链接被重置,有的事务执行超过 10s。
2023-05-22 11:56:08.355 GMT,,,442907,"100.127.12.1:23928",646b5858.6c21b,1,"",2023-05-22 11:56:08 GMT,,0,LOG,08006,"could not receive data from client: Connection reset by peer",,,,,,,,,"" 2023-05-22 11:56:10.427 GMT,,,442925,"100.127.12.1:38942",646b585a.6c22d,1,"",2023-05-22 11:56:10 GMT,,0,LOG,08006,"could not receive data from client: Connection reset by peer",,,,,,,,,"" 2023-05-22 11:56:12.118 GMT,,,442932,"100.127.13.2:41985",646b585c.6c234,1,"",2023-05-22 11:56:12 GMT,,0,LOG,08006,"could not receive data from client: Connection reset by peer",,,,,,,,,"" 2023-05-22 11:56:13.401 GMT,"postgres","pgbenchtest",3549,"::1:45862",646ae5d3.ddd,3430,"UPDATE waiting",2023-05-22 03:47:31 GMT,15/95980531,1420084298,LOG,00000,"process 3549 still waiting for ShareLock on transac tion 1420065380 after 1000.051 ms","Process holding the lock: 3588. Wait queue: 3549.",,,,"while updating tuple (60702,39) in relation ""sbtest44""","UPDATE sbtest44 SET k=k+1 WHERE id=$1",,,""
通过比照日志发现每次WAL segment耗时较长时,客户端就会产生一批慢查问(>1s)日志
PG内核中清零的具体实现为:
/* do not use get_sync_bit() here --- want to fsync only at end of fill */
fd = BasicOpenFile(tmppath, open_flags);
if (fd < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m", tmppath)));
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
save_errno = 0;
if (wal_init_zero)
{
ssize_t rc;
/*
* Zero-fill the file. With this setting, we do this the hard way to
* ensure that all the file space has really been allocated. On
* platforms that allow "holes" in files, just seeking to the end
* doesn't allocate intermediate space. This way, we know that we
* have all the space and (after the fsync below) that all the
* indirect blocks are down on disk. Therefore, fdatasync(2) or
* O_DSYNC will be sufficient to sync future writes to the log file.
*/
rc = pg_pwrite_zeros(fd, wal_segment_size, 0); // buffer write
if (rc < 0)
save_errno = errno;
}
else
{
/*
* Otherwise, seeking to the end and writing a solitary byte is
* enough.
*/
errno = 0;
if (pg_pwrite(fd, "\0", 1, wal_segment_size - 1) != 1)
{
/* if write didn't set errno, assume no disk space */
save_errno = errno ? errno : ENOSPC;
}
}
pgstat_report_wait_end();
if (save_errno)
{
/*
* If we fail to make the file, delete it to release disk space
*/
unlink(tmppath);
close(fd);
errno = save_errno;
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write to file \"%s\": %m", tmppath)));
}
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_SYNC);
if (pg_fsync(fd) != 0) // fsync data to disk
{
save_errno = errno;
close(fd);
errno = save_errno;
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not fsync file \"%s\": %m", tmppath)));
}
pgstat_report_wait_end();
从代码中能够看出WAL清零操作是先做异步写,每次写一个page block,直到循环写完,而后再一次性做fsync,异步写个别很快,当零碎负载很低的时候,异步写8KB的数据响应工夫是us级别,当零碎负载比拟重的时候,一个异步IO提早甚至能达到30ms+,异步写时延变长和os kernel的io path有很大关系,当内存压力大时,异步写可能会被os转成同步写,而且IO过程和page reclaim 的slowpath交错在一起,所以实践上就有可能耗时很久,在理论trace中也的确如此。上面是监测到的紧邻的两次WAL清零IO操作,能够看到两次异步IO操作的距离达到了30ms+。
11:56:57.238340 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192 11:56:57.271551 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
过后的磁盘带宽:
咱们能够测算一下,对于一个16MB的WAL segment,须要2K次清零操作,如果每次操作耗时1ms,那么须要至多2s来能实现整体清零。
以某个正在执行的事务为例子:
#trace一个正在执行事务的 PostgreSQL Backend 过程,两头等锁耗时 1.5s
02:27:52.868356 recvfrom(10, "*\0c\304$Es\200\332\2130}\32S\250l\36\202H\261\243duD\344\321p\335\344\241\312/"..., 92, 0, NULL, NULL) = 92
02:27:52.868409 getrusage(RUSAGE_SELF, {ru_utime={tv_sec=232, tv_usec=765624}, ru_stime={tv_sec=59, tv_usec=963504}, ...}) = 0
02:27:52.868508 futex(0x7f55bebf9e38, FUTEX_WAIT_BITSET|FUTEX_CLOCK_REALTIME, 0, NULL, FUTEX_BITSET_MATCH_ANY) = 0
02:27:54.211960 futex(0x7f55bebfa238, FUTEX_WAKE, 1) = 1
02:27:54.215049 write(2, "\0\0\36\1\377\334\23\0T2023-05-23 02:27:54.215"..., 295) = 295
02:27:54.215462 getrusage(RUSAGE_SELF, {ru_utime={tv_sec=232, tv_usec=765773}, ru_stime={tv_sec=59, tv_usec=963504}, ...}) = 0
对应的 SQL 是:
2023-05-23 02:27:54.215 GMT,"postgres","pgbenchtest",1301759,"::1:56066",646c1ef3.13dcff,58,"SELECT",2023-05-23 02:03:31 GMT,43/198458539,0,LOG,00000,"duration: 1346.558 ms execute sbstmt-13047857631771152290: SEL ECT c FROM sbtest39 WHERE id=$1","parameters: $1 = '1001713'",,,,,,,,""
至此根本能够确定Checkpoint时TPS跌0、CPU抖动和WAL清零无关,具体传导机制是:
WAL 创立->WAL 清零->刷脏和清零操作IO争抢->事务期待变长->持有锁工夫变长->被梗塞的事务过程越来越多->事务大面积超时。
清零的最大问题是会产生大量IO,并且须要所有事务挂起期待清零数据sync实现,直到新的WAL文件ready,在这个过程中所有事务都要期待WALWrite和wal_insert锁,这是抖动的最大本源。不过问题的实质还是IO争抢,如果IO负载很低,清零速度比拟快,观测到的抖动也不显著,问题也不会裸露,目前观测到的激烈抖动也只呈现在压测过程中,所以后面几轮测试中放大IO带宽也有助于缓解TPS跌0和CPU抖动。
因为在创立新的WAL文件的时候须要加锁,所以通过调整WAL文件大小来升高加锁的频率也是优化方向之一。
第六轮压测:敞开wal_init_zero
问题定位后,解决方案也就比拟好找了,WAL日志清零和判断WAL日志槽是否失常无关,实质上是一种不良好但比拟省力的实现,最好的解决方案应该是WAL日志能自解释,不依赖清零来保障正确性,这种计划须要批改PG内核,所以不大事实;还有一种计划是尽管还须要清零,然而能够由文件系统来实现,不须要PG内核显式调用,当然这须要文件系统反对该清零个性。
[ZFS和XFS正好具备这个个性](
https://www.reddit.com/r/bcachefs/comments/fhws6h/the_state_o… “ZFS和XFS正好具备这个个性”) 。咱们以后测试应用的 EXT4 并不具备这个个性,所以咱们先尝试把文件系统改为ZFS。
然而在测试ZFS的过程中,发现了好几次文件系统挂起的状况:
root@pgclusterzfs-postgresql-0:~# cat /proc/4328/stack
[<0>] zil_commit_impl+0x105/0x650 [zfs]
[<0>] zfs_fsync+0x71/0xf0 [zfs]
[<0>] zpl_fsync+0x63/0x90 [zfs]
[<0>] do_fsync+0x38/0x60
[<0>] __x64_sys_fsync+0x10/0x20
[<0>] do_syscall_64+0x5b/0x1d0
[<0>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[<0>] 0xffffffffffffffff
因而基于稳定性的思考,ZFS被临时搁置,转而采纳 XFS,并 set wal_init_zero = OFF,同时为了升高WAL日志文件创建的频率,咱们把 wal_segment_size 从 16MB调整到了1GB,这样加锁频率也会升高。
通过测试,跌0和CPU抖动缓解很显著:
尽管打消清零操作和升高加锁频率能解决局部抖动问题,然而因为Checkpoint时刷脏和事务写WAL日志仍然会抢带宽、抢锁,所以在Checkpoint时抖动仍然存在,只是和之前相比有了很大的缓解,所以如果再持续优化,只能从升高单个事务的IO量上动手。
为了数据安全思考,之前的压测都开启了full_page_write,该个性用来保障断电时page block数据损坏场景下的数据恢复,具体原理能够参考《PG.个性剖析.full page write 机制》http://mysql.taobao.org/monthly/2015/11/05/,如果存储能保障原子写(不会呈现局部胜利、局部失败的状况)或PG能从某个备份集中复原(正确的全量数据+增量WAL回放),那么在不影响数据安全的前提下能够尝试敞开full_page_write。
第七轮压测:敞开full_page_write
敞开full_page_write前后CPU和IO带宽比照都非常明显:
能够看出IO争抢对PG的影响很大,而且在敞开full_page_write之后即便有Checkpoint,CPU也简直没有抖动。
又加测了三种场景:
- 开启full_page_write+16MB WAL segment size;
- 开启full_page_write+1GB WAL segment size;
- 敞开full_page_write+1GB WAL segment size。
能够看出,在开启full_page_write时1GB segment比16MB segment体现要略好,也印证了通过减少segment size升高加锁频率的计划可行;敞开full_page_write后PG体现十分顺滑。
所以最终抉择了一组 (wal_init_zero off + XFS) + (full_page_write off) + (wal_segment_size 1GB) 的组合测试,成果如下:
能够看到在Checkpoint时抖动隐没,零碎十分顺滑,PG也从IO-Bound变成了CPU-Bound,此时的瓶颈应该在PG的外部锁机制上。
第八轮压测:最终性能比照
不过依据以往的教训,PG因为是过程模型,一个会话对应一个过程,当并发数比拟高的时候,页表和过程上下文切换的代价会比拟高,所以又引入了pgBouncer;用户自建ECS PG为了解决并发问题,开启了Huge Page,ApeCloud PG因为部署在ACK上,所以没有开启Huge Page。
比照时为了偏心,ApeCloud在上面的测试中开启了full_page_write。
能够看出在引入pgBouncer之后,PG可能承载更多的链接数而不会引起性能进化,ApeCloud PG比 PG在性能上相差不大,在并发数比拟低的时候性能上略好一些,整体稳定性上会更好一些。
论断
- WAL清零对PG的性能和稳定性都有比拟大的影响,如果文件系统反对清零个性,能够敞开wal_init_zero选项,可无效升高CPU和TPS抖动。
- full_page_write 对PG的性能和稳定性也有比拟大的影响,如果能从存储或备份上能保证数据的安全性,能够思考敞开,可无效升高CPU和TPS抖动。
- 减少WAL segment size大小,可升高日志轮转过程中加锁的频率,也能够升高CPU和TPS抖动,只是成果没那么显著。
- PG是多过程模型,引入pgBouncer可反对更大的并发链接数,并大幅晋升稳定性,如果条件容许,能够开启Huge Page,尽管原理不同,但成果和pgBouncer相似。
- PG在默认参数下,属于IO-Bound,在通过上述优化后转化为CPU-Bound。
- ACK和SLB网络实现比拟强壮,性能和稳定性上都满足要求。
- 在K8s上对文件系统、PG参数等选项的调整十分不便,能够疾速无效进行不同的组合测试,而且数据库跑在K8s上不会带来性能上的损耗,在做过通用调优之后能够达到很好的成果,对用户来说限度更少,有更强的自主性。