对PostgreSQL的异步和“直接”IO支持

2021-03-30 03:05:24

在过去的一年中,我花了很多时间试图弄清楚我们如何为Postgres添加AIO(异步IO)和DIO(直接IO)支持。虽然在那里'仍然是一个*很多打开的问题,我认为我现在在大多数更大的架构问题上都有一个体面的处理。因此这段长电子邮件。

只是为了清楚:我不希望目前设计以幸存下来。如果有一些句子下面那么听起来有点像描述新世界,那个'因为它们来自README.MD的补丁系列......

- 降低CPU使用率/较高吞吐量。特别是现代存储缓冲写入是由操作系统的瓶颈,必须使用CPU将数据从内核和#39; S页面缓存复制到Postgres缓冲池。而Direct IO通常可以直接在存储设备和Postgres之间移动数据;缓冲区缓存,使用DMA。虽然该转移正在进行中,CPU可以自由地执行其他工作,影响很小。 - 避免在操作系统缓存和postgres之间进行双重缓冲; shared_buffers。 - 更好地控制脏数据写回的时序和步伐。 - 并发WAL写入的潜力(通过O_DIRECT | O_DSYNC写道)

- 没有aio,直接IO对于大多数目的而言是不可用的。 - 即使使用AIO,需要修改许多Postgres的部分以执行显式预取。 - 在Shared_Buffers无法适当地设置的情况下,例如,如此由于共享硬件上托管了许多不同的Postgres实例,因此使用缓冲IO时,性能通常会更差。

- 没有异步IO(AIO)PG必须依靠操作系统来隐藏来自Postgres同步IO的成本。虽然这在很多工作负载中令人惊讶地令人惊讶的是,但我们希望在预取和受控写回的工作。 - 有重要的昂贵操作,如fdatasync(),操作系统无法隐藏存储延迟。这对WAL写入尤其重要,其中异步发出FDATASYNC()或O_DSYNC写入的能力可以产生显着更高的吞吐量。 - 将数据与查询执行异步和同时将数据置于共享缓冲区,意味着有更多的查询执行CPU时间。

- 平台依赖项:公共AIO API通常特定于一个平台(Linux Aio,Linux IO_调节,Windows IoCP,Windows重叠的IO)或几个平台(Posix Aio,但在那里的许多差异)。

- PG中有很多单独的位置。将所有这些用于有效使用AIO是一个,UM,大型企业。

- 缓冲区管理API中的任何内容都希望在同一时间内有多个IO - 这是必需的。

为避免在没有本机AIO的没有本机AIO支持平台的问题上,支持基于工人的AIO实现(并且当前是默认值)。这也方便地检查问题是否与本机IO实现相关。

谢谢托马斯芒罗在这个地区帮助一个*很多*。他写了工人模式,posix aio模式,添加了ci,做了很多其他测试,听到我...

使用AIO以天真的方式可以很容易地导致AIO的源/目标是共享资源的环境中的死锁,如Postgres&#39中的页面; shared_buffers。

考虑一个后端在表上执行readAhead,在当前&#34之前启动io的许多缓冲区;扫描位置"如果该后端执行块或甚至慢慢执行某些操作,则可能不会处理异步启动读取的IO完成。

这种AIO实现通过要求AIO方法允许通过系统中的任何后端(例如,IO_URE,以及间接POSIX,通过信号处理程序)来处理AIO完成来解决此问题,或者即使在发布后端,也可以保证将发生AIO处理被阻止(例如,工人模式,将完成处理卸载到AIO工人)。

(读写| fsync | flush_range)表示操作,而(SMGR | SB | WAL)确定如何完成IO完成,错误。

(有关此设计选择的更多详细信息,请参阅下文 - 它可能是不是正确的)

4)2)单独使用*不是*导致IO提交给内核,但要将其挂起IOS的每个后端列表。挂起的iOS可以明确地刷新pgaio_submit_pending(),但如果挂起的列表变为太大,或者当前后端等待IO,也将提交。

这些主要原因是立即提交IO的两个主要原因: - 如果邻近,我们可以将多个iO合并为一个"内核级别" Io在提交期间。较大的iOS比较有效。 - 几个AIO API允许在一个系统调用中提交一批iOS。

5)等待IO:pgaio_io_wait()等待IO"拥有的"通过当前的后端。当其他后端可能需要等待IO完成时,pgaio_io_ref()可以对共享内存(例如a bufferdesc)的那个aio引用,可以使用pgaio_io_wait_ref()。

6)处理请求的结果。如果在3)中注册了回调,则此ISN' t始终是必要的。可以使用pgaio_io_result()访问aio的结果,该pgaio_io_result()返回一个整数,其中负数是-errno,正数是[部分]成功条件(例如,可能指示短读数)。

7)版本IO的所有权(PGAIO_IO_RELEASE())或重复使用IO的另一个操作(pgaio_io_recycle())

大多数想要使用aio的地方' t本身需要关心管理飞行中的写入数量,或readahead距离。帮助有两个辅助公用事业,A"流读"和一个"流媒体写作"

"流读"帮助者使用回调来确定要预取的块 - 这允许以顺序方式进行readahead,但重要的是还允许异步地"阅读前方"非顺序块。

例如。对于真空,Lazy_scan_heap()有一个回调,它使用可见性映射来弄清楚接下来需要读取哪个块。类似地,Lazy_Vacuum_heap()使用LVDeadTuples中的TID来弄清楚需要哪些块。在这里作为一个例子

我在这个过程中我有一个难度是如何根据分层的方式初始化iOS(从bufmgr.c上的smgr.c和md.co to fd.cd.c向后,但也是xlog.c)。有时需要在Bufmgr.c级上初始化aio,有时在md.c等级上初始化。有时在FD.C的水平上。但是能够对完成任何此类IO元数据进行反应,但需要对操作进行任何此类IO元数据。

在FD.C初始化的iOS上提前,并且上下文信息刚刚传递给FD.C.但这似乎是错的 - fd.cn' t必须知道哪个缓冲区是关于哪个缓冲区。但更高的级别应该知道操作所在的文件所在的文件,所以他们可以' t做所有的工作......

避免这种情况,我最终拆分了"开始一个aio"操作进入更高级别的部分,例如, pgaio_io_start_read_smgr() - 知道哪个SMGR实现在使用中,因此也不是什么文件/偏移WE'重新处理SMGR和GT; MD-> FD实际上&# 34;准备" IO(即找出文件/偏移量)。这目前如下所示:

一旦这达到了FD.c层,新的filestartread()函数调用IO上的pgaio_io_prep_read() - 但不需要了解奇怪的更高级别的东西,如Relfilenodes。

我不确定这是正确的设计 - 但似乎比我早些时候更好......

共享回调,可以通过任何后端调用(通常是发布后端/ aio工人,但如果他们正在等待IO完成),则可以是其他后端)。对于共享资源的操作(例如,共享缓冲区读/写入或WAL WRITE)这些共享回调需要转换IO正在完成的对象的状态。例如。对于共享缓冲区读取,这意味着设置bm_valid /未设置bm_io_in_progress。

这些回调存在的主要原因是,它们使其安全的后端在缓冲区上发出非阻塞IO(请参阅上面的死锁部分)。由于任何阻塞后端可能导致IO完成,死锁危险消失了。

本地回调,其中IO的发行者可以与IO关联。这些可用于发出进一步的readahead。我最初没有这些,但我发现很难在飞行中拥有可控的IO数量。它们目前主要用于错误处理(例如,当XLogfileInit()由于ENSPC而创建文件时出错),并发出更多IO(例如,ReadAhead for Heapam)。

目前,补丁系列将许多子系统转换为AIO。它们具有非常不同的质量。我主要是在建筑上被认为是兴趣的转换,或者由于缓慢而导致痛苦的公平疼痛(例如,使用DIO时,没有AIO没有乐趣)。有些人也有趣;)

大多数转换都很简单。例如。堆扫描,检查点,BGWriter,真空是不是太复杂。

1)异步,并发,WAL写入。这很重要,因为我们现在的IO延迟是非常典型的,因为同时有效地只有一个沃尔IO。即使在许多情况下,有可能发出WAL写入,有一个[SET]后端等待在其完成满足其XLogflush()需要时,但同时已经发出了其他后端的下一个WAL WRITE 。

2)异步缓冲区更换。即使使用缓冲IO,我们会在戒指软骨写出数据(真空!)时经历了很多痛苦。但随着DIO这个问题变得更糟 - 内核可以' t隐藏了我们的写入延迟了。此更改使每个后端异步清除它需要即将需要的缓冲区。当铃声被使用时,这意味着在没有,执行时钟扫描和清理受害者缓冲区时清理铃声中的缓冲器。由于1)XLogFlush()也可以异步完成。

有两个新视图:PG_STAT_AIOS显示当前正在进行的AIO,PG_STAT_AIO_BACKENS显示关于AIO的每个后端统计信息。

我不打算将所有补丁附加到Aio - 它'现在太多了......我希望我能够迭代地减少系列的大小,更容易看块。

这是一个值得一封电子邮件,它已经很晚了,我已经很晚了,我想在发布更多数字之前重新运行基准。所以这里只是我可以在睡着之前跑的几个。

1)60S并将89MB的并联复制二进制单独的表(S_B = 96GB):

慢性NVME SSD分支DIO客户端TPS / STDDEV检查点写入时间MAST N 8 3.0 / 2296 MS 4.1S / 379647缓冲器= 723MIB / S AIO N 8 3.8 / 1985 MS 11.5S / 1028669缓冲区= 698MIB / AIO Y 8 4.7 / 204 MS 10.0s / 1164933缓冲率= 910mib / s

突击胜点2个快速的NVME SSD(PCIE3):分支机构客户端TPS / STDDEV检查点写入时间Master N 8 9.7 / 62 MS 7.6s / 1206376缓冲器= 1240mib / s aio n 8 11.4 / 82 ms 14.3s / 2838129缓冲液= 1550mib / s aio y 8 18.1 / 56 ms 8.9s / 4486170缓冲液= 3938mib / s

PG_PREWARM(62GB,读取)分支DIO时间BW Master N 17.4s 3626MIB / S AIO N 10.3S 6126MIB / S(更高CPU使用)Aio Y 9.8s 6438Mib / s

PG_PREWARM(62GB,缓冲区)分支DIO时间BW Master N 38.3s 1647MIB / S AIO N 13.6S 4639MIB / S(更高CPU使用率)Aio Y 10.7s 5897Mib / s

Branch DIO MAX_POLLEAL时间MASTER N 0 40.5S MASTER N 1 22.6S MASTER N 2 16.4S Master N 4 10.9s Master N 8 9.3s

aio y 0 33.1s aio y 1 17.2s aio y 2 11.8s aio y 4 9.0s aio y 8 9.2s

在本地SSD上有一些,但在大多数交易R / W工作负载中,但不是巨大的性能优势。但是在云存储 - 延迟高度延迟 - AIO可以产生巨大的优势。我看到了4倍。

在那里'也肯定是Aio目前伤害的情况 - 大多数我刚刚没有的人和#39; T获得aroung到地址。

在那里有很多案例,其中dio目前伤害 - 主要是因为所需的智慧哈登' t尚未添加。

我计划在这个单独的电子邮件中发送关于较小块的单独电子邮件 - 整个主题太大了。特别是我计划在缓冲区锁定/状态管理周围发送某些东西 - 它'是这个imo周围的核心问题之一。