暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PgSQL和MySQL数据缓冲池配置的探讨

昨晚雨停了 2021-04-25
1651

Linux File IO文件读写API讨论PgSQL和MySQL缓冲区配置参考

以下是PgSQL和MySQL两个数据库在文档上关于数据缓存区的推荐配置介绍:

  • MySQL/InnoDB:

    默认128MB;

    A larger buffer pool requires less disk I/O to access the same table data more than once. On a dedicated database server, you might set the buffer pool size to 80% of the machine's physical memory size.

  • PostgreSQL

    默认128MB;

    If you have a dedicated database server with 1GB or more of RAM, a reasonable starting value for shared_buffers
    is 25% of the memory in your system. There are some workloads where even larger settings for shared_buffers
    are effective, but because PostgreSQL also relies on the operating system cache, it is unlikely that an allocation of more than 40% of RAM to shared_buffers
    will work better than a smaller amount.

总结就是,PostgreSQL推荐设置是25%~40%的内存;MySQL推荐设置是80%;那么是什么造成这两个的不同呢?

借这个问题加上最近一个朋友的启发,找到了能把自己说服的一个原因:两个数据库的不同并发机制(进程vs线程)对应的Linux的文件读写上的差异,进而导致对内存的使用方式不同

本文梳理了一下Linux的文件读写,最后讨论下这个问题。

Linux File IO

我们要将数据库的数据写入到磁盘文件中,要经历图中的几层缓存;

首先是自己维护的BufferPool,然后在我们调用文件读写API时,在Library内部也会有缓存;这部分缓存也是在用户态;

然后,当我们调用write将数据写出时,首先会写入到kernel Buffer中(前提是没有打开O_DIRECT,后面会详细讨论)。

最后,操作系统会周期性的清理自己的Buffer就是将Kernel Buffer中的数据写到磁盘中,或者我们对文件调用fsync,那么才最终落盘。

通过DMA的机制写盘,不占用系统CPU资源。

了解了上述大致过程,那么还是不了解什么造成的MySQL和PostgreSQL的不同,下面我们详细讨论一下文件读写的API。

文件读写API

在Linux下编程,我们可以有很多种方式操作文件,下面对各个接口进行了梳理:

  • 系统调用

    OperationFunction(s)
    Openopen()
    , creat()
    Writewrite()
    , aio_write()
    , pwrite()
    , pwritev()
    Syncfsync()
    , sync()
    ,fdatasync()
    Closeclose()

    这部分是OS文件系统的调用,会陷入内核态;其中write:只保证数据从应用地址空间拷贝到内核地址空间,即kernel space buffer,只有fsync才保证将数据和元数据都实实在在地落盘了(fdatasync只同步数据部分,这里涉及到file integrity和data integrity,可以参考Linux的手册)。

    注意,当open的时候如果加上了O_SYNC参数,那么write调用就等价于write+fsync;当open加上O_DIRECT参数时,write的时候会绕过kernel buffer,但是需要要求写的时候要对齐写,比如对齐512或者4k;

    因此,只有open的时候O_DIRECT|O_SYNC,那么写的时候才是真的物理写。

  • glibc

    OperationFunction(s)
    Openfopen()
    , fdopen()
    , freopen()
    Writefwrite()
    , fputc()
    , fputs()
    , putc()
    , putchar()
    , puts()
    Syncfflush()
    Closefclose()

    这部分是C library的IO流式读写,对底层系统调用进行了封装;如果采用这种方式操作文件,那么fwrite的时候,可能数据还是留在用户态,这优化了频繁陷入内核态的上下文切换的代价。

  • mmap

    OperationFunction(s)
    Openopen()
    , creat()
    Mapmmap()
    Writememcpy()
    , memmove()
    , read()
    Syncmsync()
    Unmapmunmap()
    Closeclose()

    mmap将外存的文件块映射到内存中,可以利用OS的页面管理(虚拟空间映射,页面缓存与自动刷出,页面对齐等),一些轻量级的存储引擎直接利用mmap,而不是自己实现缓冲区管理,比如BlotDB;注意mmap同样会占用进程页表,进而可能有TLB miss的代价。

  • Zero-copy

    OperationFunction(s)
    Transfertosendfile()

    上述文件读写方式,适用于对单个文件的多次频繁IO;而当我们需要将一个大文件传输到另一个大文件中时,如果采用read+write的方式,那么会和下图类似,频繁的在用户态和内核态之间拷贝数据。

这时可以考虑使用sendfile。sendfile不需要内核态和用户态之间的数据拷贝,但是DMA需要在内核中需要维护一个连续的buffer用来传输数据,如下:

  • Linux AIO

    AIO需要文件按照O_DIRECT的方式打开,AIO的接口有两种:

    具体的使用方式,本文暂不讨论。

    或者可以只用非阻塞的方式(O_NONBLOCK)打开文件,然后通过epoll进行多路复用读写

    • Linux本身的系统调用:io_submit(2),io_setup(2), io_cancel(2), io_destroy(2), io_getevents(2)),

    • POSIX类型的glibc接口:aio_cancel(3),aio_error(3), aio_init(3), aio_read(3), aio_return(3), aio_write(3), lio_listio

讨论PgSQL和MySQL缓冲区配置

啰嗦了这么多API,主要就是借此机会温故知新一下;那么回到一开始的问题,为什么两者设置的不同呢?

数据库要保证数据的写入,不管是写入的时间还是写入的位置,完全在自己的掌控之中。BufferPool在DB中,可以作为读cache和写buffer,减少物理读的次数,也减少物理写的次数。但是,最终BufferPool中的数据还是要落盘的,那么落盘时的操作就是调用上述的API。

因此,造成配置的差异原因可能就是两者使用API的方式不同,其区别在于是否要留一大部分Kernel Buffer;在MySQL/InnoDB中,我们可以通过参数innodb_flush_method
,决定data和log是否使用O_DIRECT(绕过Kernel Buffer)和O_SYNC;而在PostgreSQL中,我们只有通过wal_sync_method
fsync
选择是否O_SYNC,没有对O_DIRECT进行选择的入口。

因此,PostgreSQL似乎就没得选,就是要用Kernel Buffer。而在PostgreSQL 的xlog.c中,可以看到这样一句话:

Optimize writes by bypassing kernel cache with O_DIRECT when usingO_SYNC/O_FSYNC and O_DSYNC.  But only if archiving and streaming aredisabled, otherwise the archive command or walsender process will readthe WAL soon after writing it, which is guaranteed to cause a physicalread if we bypassed the kernel cache.

众所周知,PostgreSQL是多进程的架构,那么进程间能共享的内存除了shared_memory外,就是Kernel Buffer了;但是shared_memory不能配置的太大,那样的话每个PostgreSQL进程的页表就会很庞大,进而带来TLB的miss等问题(虽然可以通过huge_page来启用大页,进而减小页表的大小,但huge_page不是所有系统都支持的);因此,shared_memory的推荐配置就是25%到40%,而不是大量的内存,因为Kernel Buffer对于PostgreSQL也很重要;从运维经验上同样是有教训的,之前线上出现过kernel buffer清空导致的PostgreSQL性能波动;如果这个波动发生在高峰期,那么就故障无疑了。

可以看出PostgreSQL的多进程设计对Kernel Buffer是有依赖的,因此在线上监控的时候,Kernel Buffer是否一直充满,也是一个很有必要的监控项;

找了下资料,发现PostgreSQL目前的存储引擎似乎就没打算用direct IO?那么,可以期待下Zheap的引擎哈

而MySQL是多线程架构,bufferPool就是进程堆内空间,而且可以有多个bufferpool,那么可以尽量多将内存留给自己用,这样就会将更多的热点数据放在内存中处理,得到很好的性能,这就很直观了

参考

[Ensuring data reaches disk](https://lwn.net/Articles/457667/)
[zhenghe](https://zhenghe.gitbook.io/open-courses/ucb-cs162/topic-ensuring-data-reaches-disk)
[linux open](https://linux.die.net/man/2/open)
[postgreSQL double buffer](https://www.postgresql-archive.org/a-question-about-Direct-I-O-and-double-buffering-td2062191.html)
[linux aio](https://github.com/littledan/linux-aio)



文章转载自昨晚雨停了,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论