分享人:唐成 中启乘数科技(杭州)有限公司联合创始人
正文:本文从五方面介绍了PostgreSQL DirectIO开发实践。
• DirectIO的基本知识
• 为什么需要DirectIO?
• 实现DirectIO需要解决的一些问题
• DirectIO和AIO的具体代码
• DirectIO和AIO的使用
一、DirectIO的基本知识
DirectIO是绕过文件系统的缓存,直接把IO发到磁盘或底下,实现里有一个特点是打开一个文件的时候加一个O下划线Direct的标志。我们用代码打开这个文件,把标志加上是实现不了的。因为要求内存对齐,申请内存的虚地址要跟扇区的512的虚地址一致,如果中间开始程序就会是无效的参数,要实现DirectIO就要把IO的内存地址改到这里。
Direct IO中的一些疑问?
DirectIO有几个让人迷惑的地方:假设我们已经用了DirectIO是否还要fsync,其实很多时候还是需要的,比如文件的末尾写了一个东西文件就会变大了,
文件在原数据里面变大,如果没有fsync而只有DirectIO的话,原数据的大小没变。如果把大数据变回之前的小数据,后面的数据就会丢失。很多时候fsync是去不掉。
DirectIO对齐大小到底是有多大呢?有些人认为就是4k,但实际上是512个扇区的大小,但如果底下SID是4k要对齐,那对齐大小是多大?
二、为什么需要DirectIO?
在PG里有double buffering的问题,假设PG有共享内存的数据块做缓存,实际上PG是基于文件系统的cache,cache的数据块占两块内存,内存利用率不高。
如果一个机器是128G内存,给了buffering 64G,实际上64G在PG的共享内存有个cache,但文件系统也有cache。Linux操作系统的文件系统的缓存大小是无法控制的,有很多参数可以小,但是文件系统缓存不能超过总内存的10%,这是不好控制的。
在最佳实践PG的数据库如何配置呢?double buffering有两种方案:一种是把PG的buffering设的很小,比如16g以下,更多内存让文件系统去做。另一种是把buffering设置的很大,文件系统很小,文件系统会把剩余的内存给吃掉。
如果把PG内存数据化缓存的性能提高,文件系统的性能会变低,实测结果会高10%左右。如果把buffering设置的很小,更多的使用文件系统,稳定性会变差,对云厂商来说更严重。
DirectIO可以提高性能, bufferingIO用程序化内存把IO放在这里,发给操作系统后,操作系统拷贝到内核,内存会变慢。如果使用DirectIO后,可以直接把数据块以DMA的方式发送到存储设备中。
如果用操作系统的bufferingIO去写脏数据,有时候性能会大起大落太不稳定了,用DirectIO后就更平稳了。
还有潜在的实现了WAL的并发写。
三、实现DirectIO需要解决的一些问题
1)DirectIO的几座大山
如果把IO的模型直接换成DirectIO把内存对齐,通常会变慢,从快变慢就难以接受。以前大批量导数据的时候,数据给操作系统缓存来做会比较快,现在真实的落盘后就变慢了。
代码需要修改的地方还有很多,以前PG是基于bufferingIO设计的,有人做的补丁文件,是否合到社区主干版本里还没有定。对齐没做好会导致内存浪费。
2)使用AIO
在AIO没有返回前可以干一些其他的事情,过一会儿再看IO是否回到AIO模型了,以前在Linux里面有IO的模型,对PG来说可以自己实现。PG有了AIO后,就能专注干lO的现实进程,如果没有AIO,很多性能问题就比较难解决。
四、DirectIO和AIO的具体代码
1)一些链接
这里有一些链接给到大家,感兴趣的可以来看。
2)提供的内存对齐的函数
代码里除了palloc,现在加了两个对齐函数,底下lO对齐,在PG里面函数最多用的是申请一个块大小8k的内存,函数返回来指针边界对齐。下次发IO时,right函数就不会报无效参数,如果是点IO的模式就已经提供了这两个函数。
3)内存分配原理
在PG里有一个内存管理的Memorytext,用来防止内存泄露。只要SQL执行完相关的内存,Context每次申请一块内存时,又把前面八个字节指针,按照现在4k的内存搭起来对齐,增加了对齐的分配模式,它前面加的Context,从操作系统获得一个可能是没对齐的指针,再往后找一个位置对齐返回。这个代码是这样实现的。
4)代码修改: palloc内存对齐
这个地方要改的地方挺多的,把polloc跟IO相关的全改成对齐,否则IO就会报错。
5)代码修改:对齐内存的palloc_io_aligned的使用
这里就不详细介绍了。
6)代码修改:共享内存的对齐 buf_init.c
我们申请过共享内存8k缓存,缓存从8k直接写下去,边界内存也在对齐,在共享内存这里要改一下,在前面加一行对齐。这里比以前多加了一个块儿,因为要对齐又怕会浪费一个数据块,改一下共享内存就对齐了。
7)代码修改: 在md.c中加O_DIRECT
最重要的是打开文件的时候要把O_DIRECT加上,对PG还有PG_O_DIRECT,它和标志位是一样的。
五、DirectIO和AIO的使用
1)配置参数
在使用实现时加了一些参数,第一个参数是io_data_direct,默认设置成off,如果把它设置成on,就开启功能了。最重要的参数io_method,默认是worker,io_wal_concurrency默认是32,如果用这个刷日志最多是32个,io_wal_direct默认值是off,如果想使用就设置成on,日志初始化块,把参数可以设置成on,后面还有参数要填,比如worker线程的个数。
2)配置参数之 io_method
以前内核没有这些代码时,要处理单线程百万的这个IO时,对CPU占用率比较高,每个核能处理百万是达不到的。以前英特尔黑科技叫DVDK,它实现的存储层叫SPDK,SPDK把不要的操作系统内核绕过去叫kernel by pass,现在的内核io_uring也有赶超的架势了。io_uring 和 SPDK 的性能非常接近,它的通用性比较好,同时完爆 liba_io。
3)异步IO的worker进程
打开后可以看到八个io worker在后台进程里,看起来有点像MySQL的数据库,MySQL有io线程的,但这个版本是否合进去社区还在讨论。