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

PostgreSQL的IO调优

昨晚雨停了 2021-04-25
1388

Linux IO概述pagecachePostgreSQL的相关调整存储内存申请NUMAShared MemoryOver Commit内存访问HugePagesTLBSwapSpace内存回写DirtyPage Flush外存

Linux IO概述

我们都知道,除了上层透明的cpu缓存以外,有两个和IO相关的组件——内存和磁盘。除了选择优质的硬件以外,我们通常的调优都是针对内存和磁盘之间的cache。

在linux 2.2之前,有两种cache——page cache和buffer cache。在linux 2.4之后,两种合并为pagecache。通过free
命令可以看到内存的使用情况。

  [liuyangming@ymtest ~]$ free -m
            total       used       free     shared    buffers     cached
Mem:        387491      75453     312038      15021        367      62036
-/+ buffers/cache:      13049     374442
Swap:        65535          0      65535
复制

buffers:内核中使用的buffer。

cached:pagecache(可能还有少量别用途,比如slab)。

pagecache

当我们向文件进行写入时,先写到pagecache中,然后通过主动sync或者间歇性的Linux调度flush,将脏页写盘。

在 <= Linux  2.6.31时,系统通过pdflush,进行间歇性的刷盘;

在 Linux  2.6.32中,进行了改进,不同的磁盘有不同的刷盘进程。

pagecache就是应用程序的内存之外的部分。如果应用程序需要更多的内存,那么pagecache中没有被使用的部分就会被删除,以供应用程序使用。

但是,我们要知道pagecahe并不是在所有场景中都是有用的。比如数据库的redo日志,并不需要在pagecache中缓存,应该能够直接落盘。

PostgreSQL的相关调整

存储

内存申请

NUMA

NUMA是一种CPU的硬件架构。NUMA相对的是SMP,SMP中所有CPU争用一个总线来访问内存;而NUMA称作非对称性内存访问:简单来说是,每个CPU有自己的Memory Zone,访问自己的很快,访问别人的慢。

NUMA的内存分配策略默认是localalloc(还有preferred、membind、interleave),即从本地node分配,如果本地node分配不了,根据系统参数:vm.zone_reclaim_mode,决定是否从其他node分配内存:

  • 取0,系统倾向于从其他node分配内存

  • 取1,系统倾向于从本地节点回收Cache

当系统中有多个Node,且zone_reclaim_mode=1时;可能内存不能完全利用起来,如果你对内存的使用,更偏向于cache的应用而不是数据局部性的应用,那么建议取0;

在PostgreSQL中,我们通常取0,并且会在bios中将numa关闭:

  grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="numa=off"
复制

Shared Memory

PostgreSQL有自己的shared_buffer
,在<=9.2时,PostgreSQL是基于SYSTEM V的API,SYSTEM V的接口要求设置SHMMAX大小。而>=9.3之后,PostgreSQL基于POSIX的API实现。

Shared Memory 接口比较

SHMMAX:单个进程可以分配的最大共享内存段的大小;

SHMALL:系统范围的总共享内存大小。

System VPosix
Shared Memory 接口调用:shmget(), shmat(), shmdt(), shmctl()Shared Memory接口调用:shm_open(), mmap(), shm_unlink()

PostgreSQL的shared_buffer推荐配置是内存的1/4大小,有个别情况可以配置为大于3/4;但是不要配置为1/4~3/4之间的值。

Over Commit

因为Linux的内存申请和分配是两码事,内存分配发生在内存使用的瞬间,并且应用往往会申请比需要的多;所以,Linux是允许memory overcommit。

在不允许overcommit的情况下,如果申请了2MB内存,但是实际使用了1MB,那么存在内存浪费。而当允许overcommit时,如果没有swap,可能会存在OOM——这时系统的OOM Killer会选择杀掉若干个进程,来释放内存(或者配置vm.panic_on_oom,使系统重启)。

Linux的overcommit根据vm.overcommit_memory的取值不同,有三种策略:

  • 0 – Heuristic overcommit handling。这是缺省值,它根据某种算法决定是否可以overcommit。

  • 1 – Always overcommit。总是允许overcommit,

  • 2 – Don’t overcommit。禁止超过阈值的overcommit。

在PostgreSQL中,往往采用的是第三种,而第三种的阈值是通过vm.overcommit_ratio或vm.overcommit_kbytes来设置的,在/proc/meminfo
中,可以看到阈值CommitLimit的大小:

  $ grep -i commit proc/meminfo
CommitLimit:    396446012 kB
Committed_AS:   83477808 kB
复制

Committed_AS是已经申请(不是分配)的内存的大小,通过sar -r
可以查看内存的申请和分配的情况:

  [liuyangming@ymtest ~]$ sar -r
Linux 2.6.32-696.el6.x86_64 (ymtest)    01/25/2019      _x86_64_        (56 CPU)

12:00:01 AM kbmemfree kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit
12:10:01 AM 319555932  77235820     19.47    371744  63505568 161269628     34.76
12:20:01 AM 319554560  77237192     19.47    371804  63505796 161274804     34.76
12:30:01 AM 319552532  77239220     19.47    371884  63506028 161274096     34.76
12:40:02 AM 319553188  77238564     19.47    371900  63506252 161273828     34.76
12:50:01 AM 319553436  77238316     19.47    371948  63506468 161273712     34.76
01:00:01 AM 319550008  77241744     19.47    372024  63506704 161279040     34.77
01:10:01 AM 319551520  77240232     19.47    372080  63506928 161271600     34.76
01:20:01 AM 319551072  77240680     19.47    372108  63507164 161273632     34.76
01:30:01 AM 319550884  77240868     19.47    372124  63507388 161273876     34.76
01:40:01 AM 319551924  77239828     19.47    372140  63507612 161273568     34.76
01:50:01 AM 319551520  77240232     19.47    372204  63507844 161273584     34.76
复制

其中,kbcommit对应的就是Committed_AS,而%commit
Committed_AS/(MemTotal+SwapTotal

CommitLimit计算

CommitLimit = (total RAM * vm.overcommit_ratio 100) + Swap

如果使用了hugepage,

CommitLimit = ([total RAM] – [total huge TLB RAM]) * vm.overcommit_ratio 100 + swap

内存访问

HugePages

在Linux中,内存页默认是4k。当内存很大时,如果页很小,整体性能就会变差。PostgreSQL中只支持Linux的HugePage(在BSD中有Super Page,在Window中有Large Page)。启用了huge_pages
后,相应的内存页表就会变小,进而会提高内存管理的性能。

Linux的HugePage大小从2MB到1Gb不等;默认是2MB,大小在系统启动的时候设置。

  >> cat proc/meminfo | grep -i huge
AnonHugePages:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
复制

在如上的配置中,尽管huge page设置成2MB,但是HugePages_Total为0,意思是还未启动HugePage。

基于如下脚本的运行结果,我们可以设置配置相应的HugePage的大小。

  #!/bin/bash
PGDATA="???"
pid=`head -1 $PGDATA/postmaster.pid`
echo "Pid:            $pid"
peak=`grep ^VmPeak proc/$pid/status | awk '{ print $2 }'`
echo "VmPeak:            $peak kB"
hps=`grep ^Hugepagesize proc/meminfo | awk '{ print $2 }'`
echo "Hugepagesize:   $hps kB"
hp=$((peak/hps))
echo Set Huge Pages:     $hp
复制
  sysctl -w vm.nr_hugepages= 88
复制

这里有关于HugePage配置的BenchMark,但是具体情况具体分析。

Transparent Huge Pages (THP)

该选项一般也是强制关闭的,因为这个选项系统会在你不执行的情况下,将4kb的page换成大页;可能会导致数据库崩溃等不可控的错误。

在bios中关闭:

grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="transparent_hugepage=never"

TLB

CPU用来管理虚拟存储和物理存储的控制线路叫MMU(内存管理单元)。(一般是在bootloader中的eboot阶段的进入main()函数的时候启用MMU模块,如果没有启动MMU模块,CPU内核发出的地址将直接送到内存芯片上;有了MMU模块,MMU会截获CPU内核发出的(虚拟)地址,让将虚拟地址转换成物理地址发送到CPU芯片上。

但是引入MMU后,读取指令和数据的时候,都需要读取两次内存(1. 查询页表得到物理地址;2. 然后访问具体物理地址)。TLB(Translation Lookaside Buffer)就是页表的Cache,每行保存由单个PTE(Page Table Entry)组成的块,提高虚拟地址到物理地址的转换速度,通常称为快表

TLB和CPU Cache都是微处理器的硬件结构。CPU cache是基于cpu和memory之前的缓存器,而TLB只是当CPU使用虚拟地址的时候用来加速转换速度的。

TLB exhaustion

隐藏的内存占用问题

每个进程都需要小部分内存缓存其使用的所有PTE,可以查询/proc/$PID/status
中的VmPTE,得知PTE的大小。PTE的大小取决于进程访问的数据范围,如果PostgreSQL的进程访问了shared_buffer的所有页,那么VmPTE比较高。这时,如果链接数很多,那么可能会占用很多内存;

  for p in $(pgrep postgres); 
do
   grep "VmPTE:" /proc/$p/status;
done | awk '{pte += $2} END {print pte / 1024 / 1024}'
复制

当PTE的size比较大的时候,显然TLB的空间会不足,那么就会TLB miss,进而影响性能。

所以,有三种可能的办法:

  1. PostgreSQL尽量和pooler配合使用,减少链接数;

  2. 考虑使用hugepage,减少pte数量;

  3. 减少PostgreSQL的shared_buffer大小,减少每个进程的pte数据量;

SwapSpace

  >> swapon -s
Filename                                Type            Size    Used    Priority
/dev/sda2                               partition       67108860        0       -1
复制

交换空间是安装系统的时候配置的一个磁盘分区或者一个文件,用于提高系统的虚拟内存大小,并且如果系统内存不够可以暂时使用交换空间,避免OOM。

vm.swappiness
代表系统对交换空间的喜欢程度,越高越喜欢交换出去。如果设置成0,系统的OOM killer可能会杀死自己的进程,比较安全的是设置为1(默认是60);

在PostgreSQL系统中,希望能有较高的响应速度,希望能尽量使用内存,而不是换出去,因此会将系统参数vm.swappiness
设为较小的值。

内存回写

DirtyPage Flush

当pagecache变脏了,需要将脏页刷出内存。linux中通过一些参数来控制何时进行内存回写。

  • vm.dirty_background_ratio/vm.dirty_background_bytes

    内存中允许存在的脏页比率或者具体值。达到该值,后台刷盘。取决于外存读写速度的不同,通常将vm.dirty_background_ratio设置为5,而vm.dirty_background_bytes设置为读写速度的25%。

  • vm.dirty_ratio/vm.dirty_bytes

    前台刷盘会阻塞读写,一般vm.dirty_ratio设置的比vm.dirty_background_ratio大,设置该值确保系统不会再内存中保留过多数据,避免丢失。

  • vm.dirty_expire_centisecs

    脏页在内存中保留的最大时间。

  • vm.dirty_writeback_centisecs

    刷盘进程(pdflush/flush/kdmflush)周期性启动的时间

在PostgreSQL中,有自己管理的shared_buffer,但是并不是所有的读写都是走shared_buffer;类似地,PostgresQL也有自己的checkpointer和bgwriter进程,也是通过不同的阈值控制shared_buffer的刷写。

外存

  • 临时文件大小:temp_file_limit

  • 进程最多打开文件数:max_files_per_process

这两个主要就是一个上限的限制,max_files_per_process通常默认值1000就够用了。temp_file_limit是在work_mem不够的情况下,使用磁盘的临时文件。如果建索引或者一些大查询时,可以把temp_file_limit设置大一点,避免查询中断。


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

评论