AWS Aurora IO:XactSync 不是 PostgreSQL 等待事件
在 AWS RDS 中,您可以运行两种类型的 PostgreSQL 托管服务:真正的 PostgreSQL 引擎,从社区来源编译,并在由数据库 EC2 实例挂载的 EBS 存储上运行,以及 Aurora,它是专有的且仅限 AWS 云,其中上层取自社区 PostgreSQL。 Aurora 中的存储层完全不同。
在PostgreSQL中,与大多数 RDBMS 一样,除了独占快速加载操作外,用户会话后端进程写入共享内存缓冲区。会话进程或后台写入进程对磁盘的写入是异步的。因为缓冲区写入通常是随机的,所以如果立即同步,它们会增加延迟,从而减慢响应时间。通过异步执行它们,用户不会等待它们。并且定期执行它们(检查点),它们可以重新排序以减少查找时间并避免多次写入相同的缓冲区。由于您不想丢失已提交的更改或在实例失败的情况下查看未提交的更改,因此内存缓冲区更改受 WAL(预写日志记录)保护。即使会话进程正在执行写入,也无需将其同步到存储,直到事务结束。最终用户必须等待物理写入同步的唯一地方是提交,因为我们必须确保 WAL 在确认提交之前进入持久存储。但是这些写入是顺序的,这个 log-at-commit 应该很快。一个设计良好的应用程序每次用户交互不应该有超过一次提交,然后几毫秒是不可察觉的。这就是想法:只写入内存,没有高延迟的系统调用(通过等待事件可见),并且只有在提交时我们才能等待物理写入的延迟,通常在本地存储上,因为目标是防止仅内存丢失和快速磁盘,因为容量受控制(仅需要两个检查点之间的 WAL)。
极光完全不同。出于高可用性的原因,存储不是本地存储,而是分布到多个数据中心(数据库集群区域中的 3 个可用区)。并且为了即使在整个 AZ 发生故障的情况下也能保持在 HA 中,块会在每个 AZ 中进行镜像。这意味着每次缓冲区写入实际上是写入远程存储上的6 副本。这会使后端和编写者忙于完成检查点。出于这个原因,AWS 决定将这项工作交给 Aurora 存储实例。数据库实例仅提供 WAL(重做日志)。他们在本地应用它来维护他们的共享缓冲区,但它保留在内存中。所有检查点和恢复都由存储实例完成(其中包含一些 PostgreSQL 代码)。除了高可用性之外,出于性能原因(横向扩展读取),Aurora 还可以将存储暴露给一些只读副本。并且为了减少恢复时间,即使仍有一些重做应用以恢复到故障点,也可以立即打开一个实例。由于 Aurora 存储层对此是自治的,因此在读取块时将重做应用于流。
对于我的 Oracle 读者,以下是我曾经总结过的内容:
https://twitter.com/FranckPachot/status/1230916566386126850?su003d20
这种 Aurora 设计的结果是会话后端进程直接发送更改 (WAL) 并等待 COMMIT 上的同步确认。这显然会增加更多延迟,因为一切都在网络上进行,性能和可用性之间的权衡。在此博客中,我将展示在 RDS PostgreSQL 和 Aurora PostgreSQL 中运行的逐行插入示例。新的写入设计意味着新的等待事件:XactSync 不是 PostgreSQL 等待事件,而是提交时同步时的 Aurora 专用事件。
设计一个提交过于频繁的应用程序通常是一个坏主意。当我们有很多行要更改时,最好在一个事务或多个具有中间提交的事务中进行,而不是提交每一行。建议在 Aurora 中更为重要(这也是本文的主要目标)。我将使用不同大小的中间提交运行我的示例。
极光实例
我在我的实验室帐户中创建了一个在 db.r5large 上运行的 Aurora PostgreSQL 11.6 数据库。这里的默认配置是我更改的设置:我将其设为可公开访问(这是一个临时实验室),我禁用了自动备份、加密、性能洞察、增强监控和删除保护,因为我在这里不需要它们。其中的一些截图:

我创建了一个“演示”数据库并设置了一个简单的主密码。
创建表
我将创建一个演示表和一个 my_insert 过程,其中第一个参数是要插入的行数,第二个参数是提交计数(我仅在插入此行数后提交):
cat > /tmp/cre.sql<<'SQL'
drop table if exists demo;
create table demo (id int primary key,n int);
create or replace procedure my_insert(rows_per_call int,rows_per_xact int) as $$
declare
row_counter int :=0;
begin for i in 1..rows_per_call loop
insert into demo values (i,i);
row_counter:=row_counter+1;
if row_counter>=rows_per_xact then commit; row_counter:=0; end if;
end loop; commit; end;
$$ language plpgsql;
\timing on
truncate table demo;
\x
select * from pg_stat_database where datname='demo';
select * from pg_stat_database where datname='demo' \gset
call my_insert(1e6::int,1000);
select *,tup_inserted-:tup_inserted"+ tup_inserted",xact_commit-:xact_commit"+ xact_commit" from pg_stat_database where datname='demo';
select * from pg_stat_database where datname='demo';
select * from pg_stat_wal_receiver;
select * from pg_stat_bgwriter;
create extension pg_stat_statements;
select version();
SQL
我将它保存到 /tmp/cre.sql 并运行它:
db=aur-pg.cluster-crtkf6muowez.us-east-1.rds.amazonaws.com
PGPASSWORD=FranckPachot psql -p 5432 -h $db -U postgres demo -e < /tmp/cre.sql
结果如下:
select * from pg_stat_database where datname='demo';
-[ RECORD 1 ]--+------------------------------
datid | 16400
datname | demo
numbackends | 1
xact_commit | 4
xact_rollback | 0
blks_read | 184
blks_hit | 1295
tup_returned | 773
tup_fetched | 697
tup_inserted | 23
tup_updated | 0
tup_deleted | 0
conflicts | 0
temp_files | 0
temp_bytes | 0
deadlocks | 0
blk_read_time | 270.854
blk_write_time | 0
stats_reset | 2020-04-26 15:03:30.796362+00
Time: 176.913 ms
select * from pg_stat_database where datname='demo'
Time: 110.739 ms
call my_insert(1e6::int,1000);
CALL
Time: 17780.118 ms (00:17.780)
select *,tup_inserted-23"+ tup_inserted",xact_commit-4"+ xact_commit" from pg_stat_database where datname='demo';
-[ RECORD 1 ]--+------------------------------
datid | 16400
datname | demo
numbackends | 1
xact_commit | 1017
xact_rollback | 0
blks_read | 7394
blks_hit | 2173956
tup_returned | 5123
tup_fetched | 839
tup_inserted | 1000023
tup_updated | 2
tup_deleted | 0
conflicts | 0
temp_files | 0
temp_bytes | 0
deadlocks | 0
blk_read_time | 317.762
blk_write_time | 0
stats_reset | 2020-04-26 15:03:30.796362+00
+ tup_inserted | 1000000
+ xact_commit | 1013
这验证了我收集的统计数据:每 1000 次提交一百万次插入。重要的一点:在 Aurora 上插入这1000000 行需要 17.7 秒。
在 RDS Aurora 上运行
好吧,17 秒对我来说还不够。在以下测试中,我将插入 1000 万行。让我们在一笔大交易中开始这样做:
cat > /tmp/run.sql <<'SQL'
truncate table demo;
vacuum demo;
\x
select * from pg_stat_database where datname='demo' \gset
\timing on
call my_insert(1e7::int,10000000);
\timing off
select tup_inserted-:tup_inserted"+ tup_inserted",xact_commit-:xact_commit"+ xact_commit" from pg_stat_database where datname='demo';
SQL
我在我的 Aurora 实例上运行它。
[opc@b aws]$ PGPASSWORD=FranckPachot psql -p 5432 -h $db -U postgres demo -e < /tmp/run.sql
truncate table demo;
TRUNCATE TABLE
vacuum demo;
VACUUM
Expanded display is on.
select * from pg_stat_database where datname='demo'
Timing is on.
call my_insert(1e7::int,10000000);
CALL
Time: 133366.305 ms (02:13.366)
Timing is off.
select tup_inserted-35098070"+ tup_inserted",xact_commit-28249"+ xact_commit" from pg_stat_database where datname='demo';
-[ RECORD 1 ]--+---------
+ tup_inserted | 10000000
+ xact_commit | 62
这将运行具有 1000 万行的调用,每 1000 万行提交一次,这意味着所有在一个事务中。数据库统计显示很少有提交(可能同时有一些不相关的后台作业)。插入的经过时间:2:13 分钟。
我没有启用 Performance Insight,所以我将运行脏活动会话采样:
while true ; do echo "select pg_sleep(0.1);select state,to_char(clock_timestamp(),'hh24:mi:ss'),coalesce(wait_event_type,'-null-'),wait_event,query from pg_stat_activity where application_name='psql' and pid != pg_backend_pid();" ; done | PGPASSWORD=FranckPachot psql -p 5432 -h $db -U postgres demo | awk -F "|" '/^ *active/{print > "/tmp/ash.txt";$2="";c[$0]=c[$0]+1}END{for (i in c){printf "%8d %s\n", c[i],i}}' | sort -n & PGPASSWORD=FranckPachot psql -p 5432 -h $db -U postgres demo -e < /tmp/run.sql ; pkill psql
这会在后台运行对 PG_STAT_ACTIVITY 的查询,该查询通过管道传输到 AWK 脚本,以获取每个等待的样本数。没有等待事件意味着不等待(可能在 CPU 上),我将其标记为“-null-”。
这是相同运行的结果。 2:15 这证明我的采样在另一个具有大量可用 CPU 的会话中运行没有开销:
Expanded display is on.
select * from pg_stat_database where datname='demo'
Timing is on.
call my_insert(1e7::int,10000000);
CALL
Time: 135397.016 ms (02:15.397)
Timing is off.
select tup_inserted-45098070"+ tup_inserted",xact_commit-28338"+ xact_commit" from pg_stat_database where datname='demo';
-[ RECORD 1 ]--+----
+ tup_inserted | 0
+ xact_commit | 819
[opc@b aws]$
1 active IO XactSync call my_insert(1e7::int,10000000);
30 active IO WALWrite call my_insert(1e7::int,10000000);
352 active -null- call my_insert(1e7::int,10000000);
[opc@b aws]$
大多数时候(92% 的 pg_stat_activity 样本)我的会话不在等待事件中,这意味着在 CPU 中运行。 8% 的样本是“WALWrite”,只有一个样本是“XactSync”。
您在PostgreSQL 文档但在Aurora 文档中找不到此“XactSync”:
IO:XactSync - 在此等待事件中,会话正在发出 COMMIT 或 ROLLBACK,要求保留当前事务的更改。 Aurora 正在等待 Aurora 存储确认持久性。
WALWrite 是发送 WAL 但不等待确认的会话进程(这是我从小的等待样本中的猜测,因为我没有看到这种内部 Aurora 行为记录在案)。
我现在将以不同的提交大小运行,从最大值(一个事务中的所有内容)开始到每 100 行提交一次。我只复制有趣的部分:经过的时间和等待事件样本计数:
call my_insert(1e7::int,1e7::int);
CALL
Time: 135868.757 ms (02:15.869)
1 active -null- vacuum demo;
44 active IO WALWrite call my_insert(1e7::int,1e7::int);
346 active -null- call my_insert(1e7::int,1e7::int);
call my_insert(1e7::int,1e6::int);
CALL
Time: 136080.102 ms (02:16.080)
3 active IO XactSync call my_insert(1e7::int,1e6::int);
38 active IO WALWrite call my_insert(1e7::int,1e6::int);
349 active -null- call my_insert(1e7::int,1e6::int);
call my_insert(1e7::int,1e5::int);
CALL
Time: 133373.694 ms (02:13.374)
30 active IO XactSync call my_insert(1e7::int,1e5::int);
35 active IO WALWrite call my_insert(1e7::int,1e5::int);
315 active -null- call my_insert(1e7::int,1e5::int);
call my_insert(1e7::int,1e4::int);
CALL
Time: 141820.013 ms (02:21.820)
32 active IO WALWrite call my_insert(1e7::int,1e4::int);
91 active IO XactSync call my_insert(1e7::int,1e4::int);
283 active -null- call my_insert(1e7::int,1e4::int);
call my_insert(1e7::int,1e3::int);
CALL
Time: 177596.155 ms (02:57.596)
1 active -null- select tup_inserted-95098070"+ tup_inserted",xact_commit-33758"+ xact_commit" from pg_stat_database where datname='demo';
32 active IO WALWrite call my_insert(1e7::int,1e3::int);
250 active IO XactSync call my_insert(1e7::int,1e3::int);
252 active -null- call my_insert(1e7::int,1e3::int);
call my_insert(1e7::int,1e2::int);
CALL
Time: 373504.413 ms (06:13.504)
24 active IO WALWrite call my_insert(1e7::int,1e2::int);
249 active -null- call my_insert(1e7::int,1e2::int);
776 active IO XactSync call my_insert(1e7::int,1e2::int);
即使 XactSync 的频率随着提交次数的增加而增加,但当提交大小高于一万行时,经过的时间几乎相同。然后它迅速增加,每 100 行提交一次只会使经过的时间达到 6 分钟,其中 74% 的时间在 XactSync 上等待。
这个等待事件特定于 Aurora 前台进程通过网络将重做发送到存储的方式,我将使用在挂载块存储 (EBS) 上运行的普通 PostgreSQL (RDS) 运行相同的操作。
我在这些运行期间在此处添加了一些 CloudWatch 指标,从 11:30 开始,大提交大小到逐行提交仍在 12:00 运行
。
如您所见,我已经添加了用于下一个测试的测试,RDS PostgreSQL 在 12:15 和 12:30 之间运行相同,在下一篇文章中有详细说明:https://dev.to/aws-英雄/aws-aurora-vs-rds-postgresql-on-frequent-commits-27c6。解释这些结果超出了这篇博文的范围。我们已经看到,在频繁的提交运行中,大部分时间都是在 IO:XactSync 等待事件上。但是,CloudWatch 显示 CPU 利用率为 50%,这意味着对于 r5.large,CPU 中始终有一个线程。这并不是我对等待事件的期望。
更多推荐
所有评论(0)