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 V Posix 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,进而影响性能。
所以,有三种可能的办法:
PostgreSQL尽量和pooler配合使用,减少链接数;
考虑使用hugepage,减少pte数量;
减少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设置大一点,避免查询中断。