PostgreSQL DirectIO开发实践

分享人:唐成   中启乘数科技(杭州)有限公司联合创始人

正文:本文从五方面介绍了PostgreSQL DirectIO开发实践。

• DirectIO的基本知识

• 为什么需要DirectIO?

• 实现DirectIO需要解决的一些问题

• DirectIO和AIO的具体代码

• DirectIO和AIO的使用


一、DirectIO的基本知识

PostgreSQL DirectIO开发实践

DirectIO是绕过文件系统的缓存,直接把IO发到磁盘或底下,实现里有一个特点是打开一个文件的时候加一个O下划线Direct的标志。我们用代码打开这个文件,把标志加上是实现不了的。因为要求内存对齐,申请内存的虚地址要跟扇区的512的虚地址一致,如果中间开始程序就会是无效的参数,要实现DirectIO就要把IO的内存地址改到这里。

Direct IO中的一些疑问?

PostgreSQL DirectIO开发实践

DirectIO有几个让人迷惑的地方:假设我们已经用了DirectIO是否还要fsync,其实很多时候还是需要的,比如文件的末尾写了一个东西文件就会变大了,

文件在原数据里面变大,如果没有fsync而只有DirectIO的话,原数据的大小没变。如果把大数据变回之前的小数据,后面的数据就会丢失。很多时候fsync是去不掉。

DirectIO对齐大小到底是有多大呢?有些人认为就是4k,但实际上是512个扇区的大小,但如果底下SID是4k要对齐,那对齐大小是多大?


二、为什么需要DirectIO?


PostgreSQL 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的几座大山


PostgreSQL DirectIO开发实践

如果把IO的模型直接换成DirectIO把内存对齐,通常会变慢,从快变慢就难以接受。以前大批量导数据的时候,数据给操作系统缓存来做会比较快,现在真实的落盘后就变慢了。

代码需要修改的地方还有很多,以前PG是基于bufferingIO设计的,有人做的补丁文件,是否合到社区主干版本里还没有定。对齐没做好会导致内存浪费。


2)使用AIO


PostgreSQL DirectIO开发实践

在AIO没有返回前可以干一些其他的事情,过一会儿再看IO是否回到AIO模型了,以前在Linux里面有IO的模型,对PG来说可以自己实现。PG有了AIO后,就能专注干lO的现实进程,如果没有AIO,很多性能问题就比较难解决。


四、DirectIO和AIO的具体代码


1)一些链接

PostgreSQL DirectIO开发实践

这里有一些链接给到大家,感兴趣的可以来看。


2)提供的内存对齐的函数

PostgreSQL DirectIO开发实践

代码里除了palloc,现在加了两个对齐函数,底下lO对齐,在PG里面函数最多用的是申请一个块大小8k的内存,函数返回来指针边界对齐。下次发IO时,right函数就不会报无效参数,如果是点IO的模式就已经提供了这两个函数。


3)内存分配原理

PostgreSQL DirectIO开发实践

在PG里有一个内存管理的Memorytext,用来防止内存泄露。只要SQL执行完相关的内存,Context每次申请一块内存时,又把前面八个字节指针,按照现在4k的内存搭起来对齐,增加了对齐的分配模式,它前面加的Context,从操作系统获得一个可能是没对齐的指针,再往后找一个位置对齐返回。这个代码是这样实现的。


4)代码修改: palloc内存对齐

PostgreSQL DirectIO开发实践

这个地方要改的地方挺多的,把polloc跟IO相关的全改成对齐,否则IO就会报错。


5)代码修改:对齐内存的palloc_io_aligned的使用

PostgreSQL DirectIO开发实践

这里就不详细介绍了。


6)代码修改:共享内存的对齐 buf_init.c

PostgreSQL DirectIO开发实践

我们申请过共享内存8k缓存,缓存从8k直接写下去,边界内存也在对齐,在共享内存这里要改一下,在前面加一行对齐。这里比以前多加了一个块儿,因为要对齐又怕会浪费一个数据块,改一下共享内存就对齐了。


7)代码修改: 在md.c中加O_DIRECT

PostgreSQL DirectIO开发实践PostgreSQL DirectIO开发实践

最重要的是打开文件的时候要把O_DIRECT加上,对PG还有PG_O_DIRECT,它和标志位是一样的。


五、DirectIO和AIO的使用


1)配置参数

PostgreSQL DirectIO开发实践

在使用实现时加了一些参数,第一个参数是io_data_direct,默认设置成off,如果把它设置成on,就开启功能了。最重要的参数io_method,默认是worker,io_wal_concurrency默认是32,如果用这个刷日志最多是32个,io_wal_direct默认值是off,如果想使用就设置成on,日志初始化块,把参数可以设置成on,后面还有参数要填,比如worker线程的个数。


2)配置参数之 io_method

PostgreSQL DirectIO开发实践

以前内核没有这些代码时,要处理单线程百万的这个IO时,对CPU占用率比较高,每个核能处理百万是达不到的。以前英特尔黑科技叫DVDK,它实现的存储层叫SPDK,SPDK把不要的操作系统内核绕过去叫kernel by pass,现在的内核io_uring也有赶超的架势了。io_uring 和 SPDK 的性能非常接近,它的通用性比较好,同时完爆 liba_io。


3)异步IO的worker进程

PostgreSQL DirectIO开发实践

打开后可以看到八个io worker在后台进程里,看起来有点像MySQL的数据库,MySQL有io线程的,但这个版本是否合进去社区还在讨论。

 

上一篇:PG+MySQL第12课


下一篇:Silverlight 2 End to End教程:创建Digg搜索客户端