周其仁:停止改革,我们将面临三大麻烦

抛开立场观点不谈,且看周小平写一句话能犯多少语病

罗马尼亚的声明:小事件隐藏着大趋势——黑暗中的风:坚持做对的事相信未来的结果

布林肯突访乌克兰,为何选择去吃麦当劳?

中国不再是美国第一大进口国,贸易战殃及纺织业? 美国进一步延长352项中国商品的关税豁免期

生成图片,分享到微信朋友圈

自由微信安卓APP发布,立即下载! | 提交文章网址
查看原文

从实际故障看PostgreSQL中的共享内存

xiongcc PostgreSQL学徒 2023-01-23

前言

线上有位同事找到我,说生产查询报错了,提示"Cause:org.postgresql.util.PGSQLException:ERROR:could not open shared memory segment "/PostgreSQL.1486998300":No such file or directory",乍一看错误,貌似和shared memory有关?莫慌,慢慢分析

处理

正好在之前专门理了一下,PostgreSQL是多进程模型,最常见的IPC就是共享内存(shared memory),而这个PostgreSQL.1486998300对应的是动态共享内存DSM(dynamic shared memory)。

postgres=# select name,setting,enumvals from pg_settings where name = 'dynamic_shared_memory_type'; name | setting | enumvals ----------------------------+---------+------------------- dynamic_shared_memory_type | posix | {posix,sysv,mmap}(1 row)

参照官网解释:

Specifies the dynamic shared memory implementation that the server should use. Possible values are posix (for POSIX shared memory allocated using shm_open), sysv (for System V shared memory allocated via shmget), windows (for Windows shared memory), and mmap (to simulate shared memory using memory-mapped files stored in the data directory). Not all values are supported on all platforms; the first supported option is the default for that platform. The use of the mmap option, which is not the default on any platform, is generally discouraged because the operating system may write modified pages back to disk repeatedly, increasing system I/O load; however, it may be useful for debugging, when the pg_dynshmem directory is stored on a RAM disk, or when other shared memory facilities are not available.

指定服务器应该使用的动态共享内存的实现方式。可选的值是posix(用于使用 shm_open分配的 POSIX 共享内存)、sysv (用于通过shmget分配的 System V 共享内存)、 windows(用于 Windows 共享内存)、mmap (使用存储在数据目录中的内存映射文件模拟共享内存)以及none(禁用这个特性)。并非所有平台上都支持所有值,平台上第一个支持的选项就是其默认值。在任何平台上mmap选项都不是默认值,通常不鼓励使用它,因为操作系统会反复地把修改过的页面写回到磁盘上,从而增加了系统的I/O负载。不过当 pg_dynshmem目录被存储在一个 RAM 盘时或者没有其他共享内存功能可用时, 它还是有用的。

传统IO

首先温故一下传统IO的实现方式,如下图,可以看到,Page Cache 的本质是由 Linux 内核管理的内存区域。我们通过 mmap 以及 buffered I/O 将文件读取到内存空间实际上都是读取到 Page Cache 中。

buffered I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是buffered I/O。在 Linux 的buffered I/O 机制中,这种访问文件的方式是通过两个系统调用实现的:read() 和 write()。调用read()时,如果操作系统内核地址空间的页缓存( page cache )有数据就读取出该数据并直接返回给应用程序,如果没有就触发缺页中断(Page Fault),从磁盘读取数据到页缓存,然后再从页缓存拷贝到应用程序的地址空间。调用write()时,数据会先从应用程序的地址空间拷贝到操作系统内核地址空间的页缓存,然后再写入磁盘。根据Linux的延迟写机制,当数据写到操作系统内核地址空间的页缓存就意味write()完成了(也就是write-throuth和write-back),操作系统会定期地将页缓存的数据刷到磁盘上。根据内核参数控制,何时去刷写脏页

•vm.dirty_background_ratio/vm.dirty_background_bytes•vm.dirty_ratio/vm.dirty_bytes•vm.dirty_expire_centisecs•vm.dirty_writeback_centisecs

mmap实现原理

从传统IO的过程中,我们可以发现有个地方可以优化:如果可以直接在用户空间读写Page Cache,那么就可以免去将Page Cache的数据复制到用户空间缓冲区的过程。

为此,Linux提供了内存映射函数mmap,使用mmap函数的时候,会在当前进程的用户地址空间中开辟一块内存,这块内存与系统的文件进行映射。对其的读取和写入,会转化为对相应文件的操作。它把文件内容映射到一段内存上,通过对这段内存的读取和修改,实现对文件的读取和修改。mmap()系统调用使得进程之间可以通过映射一个普通的文件实现共享内存。普通文件映射到进程地址空间后,进程可以像访问内存的方式对文件进行访问,不需要其他内核态的系统调用(read,write)去操作。

Linux中的mmap映射 [一]

对于传统读写IO,以读为例,涉及到了数据的两次拷贝:磁盘→内核,内核→用户态。写操作也是一样,将页缓存的数据写入磁盘的时候,必须先拷贝到内核空间对应的主存,然后在写入磁盘中。而且当存在多个进程同时读取同一个文件时,每一个进程中的地址空间都会保存一份副本,造成了物理内存的浪费。

第二种方式就是mmap。open一个文件,然后调用mmap系统调用,将文件的内容的全部或一部分直接映射到进程的地址空间,映射完成后,进程可以像访问普通内存一样做其他的操作,比如memcpy等等。mmap并不分配物理地址空间,它只是占有进程的虚拟地址空间。这跟第一种方式不一样的,第一种方式需要预先分配好物理内存,内核才能将页高速缓冲中的文件数据拷贝到用户进程指定的内存空间中。当多个进程需要同时访问同一个文件时,每个进程都将文件所存储的内核高速缓冲映射到自己的进程地址空间。当第一个进程访问内核中的缓冲区时候,前面讲过并没有实际拷贝数据,这时MMU在地址映射表中是无法找到与地址空间相对应的物理地址的,也就是MMU失败,就会触发缺页中断(Page Fault)。内核将文件的这一页数据读入到内核高速缓冲区中,并更新进程的页表,使页表指向内核缓冲中的这一页。之后有其他的进程再次访问这一页的时候,该页已经在内存中了,内核只需要将进程的页表登记并且指向内核的页高速缓冲区即可。

这种图会更加清晰

通过上图还可以发现,Direct IO更加暴力,直接让用户态和块IO层对接,直接放弃Page Cache,从磁盘直接和用户态拷贝数据。在前阵子的杭州线下PostgreSQL分享会上,唐成老师还就Direct IO进行了一些分享,在此引用一下:

1.避免双缓存:即double buffer的问题,在PostgreSQL中,一个数据块会在shared buffer中存在一份,在page cache里也会存在一份2.提升性能:降低CPU占用率,提升IO吞吐量,没有使用DirectIO时,CPU需要把数据从PostgreSQL的缓存中COPY到内核的缓存中。使用了DirectIO后,可以直接把数据块以DMA的方式发送 到存储设备中3.避免写脏数据时的抖动:没有使用DirectIO时,真实 写脏数据时,脏数据常常是靠操作系统写入的。但实际上操作系统并不了解数据库的情况,并不能很精细的写脏数据。4.潜在地实现了WAL并发写:组合O_DIRECT | O_DSYNC ,WAL可以并发写

posix实现原理

刚刚提到了,dynamic_shared_memory_type的可选值还包括posix,此处可以参照《Posix standard》

什么是posix

posix全称可移植操作系统接口(Portable Operating System Interface of UNIX ),POSIX是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称,其正式称呼为IEEE 1003,而国际标准名称为ISO/IEC 9945。

POSIX是Unix的标准。1974年,贝尔实验室正式对外发布Unix。因为涉及到反垄断等各种原因,加上早期的Unix不够完善,于是贝尔实验室以慷慨的条件向学校提供源代码,所以Unix在大专院校里获得了很多支持并得以持续发展。于是出现了好些独立开发的与Unix基本兼容但又不完全兼容的OS,通称Unix-like OS。

20世纪80年代中期,Unix厂商试图通过加入新的、往往不兼容的特性来使它们的程序与众不同。局面非常混乱,麻烦也就随之而来了。

为了提高兼容性和应用程序的可移植性,阻止这种趋势, IEEE(电气和电子工程师协会)开始努力标准化Unix的开发,后来由 Richard Stallman命名为“Posix”。这套标准涵盖了很多方面,比如Unix系统调用的C语言接口、shell程序和工具、线程及网络编程。大名鼎鼎的Unix和Linux就遵循这套标准。有了这个规范,你就可以调用通用的API了,Linux提供的POSIX系统调用在Unix上也能执行,因此学习Linux的底层接口最好就是理解POSIX标准。

实现原理

POSIX接口的共享内存,底层最终都是调用的mmap,只是不同的是共享内存打开文件调用shm_open(),而内存映射调用open()。只是说mmap并不完全是为了共享内存来设计的,它本身提供了不同于一般对普通文件的访问的方式,进程可以像读写内存一样对普通文件进行操作,IPC的共享内存是纯粹为了共享。所以,共享内存在POSIX上一定程度上就是指的内存映射了。

对于共享内存区使用的API shm_open(),传入的参数叫shm_name,宏中声明了shm_name,构造出shm_dir,shm_dir的内容就是/dev/shm/,之后通过和name拼接:

__mempcpy (__mempcpy (__mempcpy (shm_name, shm_dir, shm_dirlen), \prefix, sizeof prefix - 1),name,namelen)

生成了shm_name。等等,/dev/shm是个什么鬼?

Tmpfs is a file system which keeps all files in virtual memory. Everything in tmpfs is temporary in the sense that no files will be created on your hard drive. If you unmount a tmpfs instance, everything stored therein is lost. Since tmpfs lives completely in the page cache and on swap, all tmpfspages currently in memory will show up as cached. It will not show up as shared or something like that. Further on you can check the actual RAM+swap use of a tmpfs instance with df(1) and du(1).


Tmpfs是一个文件系统,可将所有文件保留在虚拟内存中。tmpfs中的所有内容都是临时的,因为没有文件会在硬盘上创建。如果您卸载tmpfs实例,存储在其中的所有东西都会丢失。由于tmpfs都存在于page cache和swap中,因此所有tmpfs当前在内存中的页面都会显示在cache里面。它不会作为shared或类似的东西出现。可以使用df(1)和du(1)来检查tmpfs真正使用的RAM + SWAP量。

原来是一个特殊的文件系统,看一下确实贺然写着tmpfs。如果/dev/shm/下没有任何文件,它占用的内存实际上就是0字节,仅在使用shm_open文件时,/dev/shm才会真正占用内存。

[postgres@xiongcc ~]$ df -h /dev/shm/Filesystem Size Used Avail Use% Mounted ontmpfs 880M 0 880M 0% /dev/shm

于是总结一下,mmap和posix共享内存区的区别在于共享的数据的载体(底层支撑对象)不一样:

1.内存映射文件的数据载体是物理文件2.共享内存区对象,共享的数据载体是物理内存3.另外共享内存和文件内存映射的接口、用法不一样,一个是open(),另一个是shm_open(),POSIX的共享内存实现会默认把共享内存文件放在/dev/shm/分区下,如果没有这个分区,需要手动挂载一下。

所以,这也就明了了,为什么会说

在任何平台上mmap选项都不是默认值,通常不鼓励使用它,因为操作系统会反复地把修改过的页面写回到磁盘上,从而增加了系统的I/O负载。不过当 pg_dynshmem目录被存储在一个 RAM 盘时或者没有其他共享内存功能可用时, 它还是有用的。

因为mmap增加了IO负载,底层载体实际是磁盘文件,假如你是将pg_dynshmem放在内存盘上时,那么理论上读写速度和posix就没太大差异了。

system v实现原理

System V共享内存区在概念上类似与Posix共享内存区。代之以调用shm_open后调用mmap,先调用shmget,在调用shmat,在终端市使用ipcs命令可以查看SystemV的IPC对象。

在<=9.2时,PostgreSQL是基于SYSTEM V的API,SYSTEM V的接口要求设置SHMMAX大小,所以低版本会让我们设置一大堆shmmax、shmall等,十分麻烦。而>=9.3之后,PostgreSQL大多数基于POSIX的API实现。而System V共享内存,仅用于提供互锁来保护数据目录,量很少。PostgreSQL requires a few bytes of System V shared memory (typically 48 bytes, on 64-bit platforms) for each copy of the server.

差异

那么什么时候会用mmap,什么时候用system V或者Posix呢?

If you're on posix system without mmap, you'd use posix_shm. If you're on a unix without posix_shm you'd use sysv_shm. If you only need to share memory vs a parent/child you'd use mmap if available.

实操

有了这些基础知识的储备之后,再来理解这个报错就轻松了。"Cause:org.postgresql.util.PGSQLException:ERROR:could not open shared memory segment "/PostgreSQL.1486998300":No such file or directory"。

首先默认参数是posix,那么就会在/dev/shm目录下有所体现,查看一下

postgres=# show dynamic_shared_memory_type ; dynamic_shared_memory_type ---------------------------- posix(1 row)
[postgres@xiongcc ~]$ ls -l /dev/shm/ | grep PostgreSQL-rw------- 1 postgres postgres 26976 Nov 22 15:37 PostgreSQL.1475249042 [postgres@xiongcc ~]$ df -h /dev/shm/Filesystem Size Used Avail Use% Mounted ontmpfs 1.8G 44K 1.8G 1% /dev/shm

看看这个文件PostgreSQL.1475249042,可以看到,是postmaster touch的这个文件

[postgres@xiongcc ~]$ ps -ef | grep pgdata | grep -v grep ---postmasterpostgres 23477 1 0 15:37 ? 00:00:00 /usr/pgsql-14/bin/postgres -D pgdata[postgres@xiongcc ~]$ lsof -p 23477COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEpostgres 23477 postgres cwd DIR 253,1 4096 924698 /home/postgres/pgdatapostgres 23477 postgres rtd DIR 253,1 4096 2 /postgres 23477 postgres txt REG 253,1 29481112 1202266 /usr/pgsql-14/bin/postgrespostgres 23477 postgres mem REG 253,1 62184 657587 /usr/lib64/libnss_files-2.17.sopostgres 23477 postgres DEL REG 0,4 136490945 /dev/zeropostgres 23477 postgres mem REG 253,1 106070960 663472 /usr/lib/locale/locale-archivepostgres 23477 postgres mem REG 253,1 2118128 657569 /usr/lib64/libc-2.17.sopostgres 23477 postgres mem REG 253,1 1141928 657577 /usr/lib64/libm-2.17.sopostgres 23477 postgres mem REG 253,1 19776 657575 /usr/lib64/libdl-2.17.sopostgres 23477 postgres mem REG 253,1 44448 657599 /usr/lib64/librt-2.17.sopostgres 23477 postgres mem REG 253,1 143944 657595 /usr/lib64/libpthread-2.17.sopostgres 23477 postgres mem REG 253,1 159640 656965 /usr/lib64/ld-2.17.sopostgres 23477 postgres mem REG 0,18 26976 136490947 /dev/shm/PostgreSQL.1475249042postgres 23477 postgres DEL REG 0,4 5603328 /SYSV000e1c1apostgres 23477 postgres 0r CHR 1,3 0t0 18 /dev/nullpostgres 23477 postgres 1u CHR 136,3 0t0 6 /dev/pts/3postgres 23477 postgres 2u CHR 136,3 0t0 6 /dev/pts/3postgres 23477 postgres 3r FIFO 0,8 0t0 136490948 pipepostgres 23477 postgres 4w FIFO 0,8 0t0 136490948 pipepostgres 23477 postgres 5u IPv4 136490954 0t0 TCP localhost:postgres (LISTEN)postgres 23477 postgres 6u unix 0xffff88008d856c00 0t0 136491581 /tmp/.s.PGSQL.5432postgres 23477 postgres 7u IPv4 136491585 0t0 UDP localhost:36316->localhost:36316 [postgres@xiongcc ~]$ stat /dev/shm/PostgreSQL.1475249042 File: ‘/dev/shm/PostgreSQL.1475249042’ Size: 26976 Blocks: 56 IO Block: 4096 regular fileDevice: 12h/18d Inode: 136490947 Links: 1Access: (0600/-rw-------) Uid: ( 1000/postgres) Gid: ( 1000/postgres)Access: 2021-11-22 15:37:08.566598752 +0800Modify: 2021-11-22 15:37:08.566598752 +0800Change: 2021-11-22 15:37:08.566598752 +0800 Birth: -

那么假如改成mmap,应该就会使用文件了,看下效果

[postgres@xiongcc ~]$ psqlpsql (14rc1)Type "help" for help. postgres=# show dynamic_shared_memory_type ; dynamic_shared_memory_type ---------------------------- mmap(1 row) postgres=# \q[postgres@xiongcc ~]$ ll pgdata/pg_dynshmem/total 28-rw------- 1 postgres postgres 26976 Nov 22 15:40 mmap.4202052336

满足预期,会位于pg_dynshmem子目录下面

所以用脚趾都能想到,两种形态的共享内存,一个位于内存中(Cache),一个是磁盘文件,性能肯定有所差异。

BenckmarkSQL的测试结果

ClientsPOSIX tpsMMAP tps
2529227
4860569
623081493
83773431418
106645367267

因为动态共享内存在并行查询中会使用到,对于并行查询而言,执行时创建的 worker 进程与 leader 进程通过共享内存实现数据交互。但这部分内存无法像普通的共享内存那样在系统启动时预先分配,毕竟直到真正执行时才知道有多少 worker 进程,以及需要分配多少内存。动态共享内存,即在执行时动态创建,用于 leader 与 worker 间通信,执行完成后释放。

那么我们需要构造一个比较大的表,让他去走并行查询,此处以count统计行数为例

postgres=# \dt+ test_vacuum List of relations Schema | Name | Type | Owner | Persistence | Access method | Size | Description --------+-------------+-------+----------+-------------+---------------+---------+------------- public | test_vacuum | table | postgres | unlogged | heap | 4647 MB | (1 row) postgres=# select reltuples,relpages from pg_class where relname = 'test_vacuum'; reltuples | relpages -----------+---------- 1.1e+08 | 594595(1 row) postgres=# explain select count(*) from test_vacuum ; QUERY PLAN --------------------------------------------------------------------------------------------------- Finalize Aggregate (cost=1168511.88..1168511.89 rows=1 width=8) -> Gather (cost=1168511.67..1168511.88 rows=2 width=8) Workers Planned: 2 -> Partial Aggregate (cost=1167511.67..1167511.68 rows=1 width=8) -> Parallel Seq Scan on test_vacuum (cost=0.00..1052928.33 rows=45833333 width=0)(5 rows)

实际跑一下,观察一下效果

postgres=# explain analyze select count(*) from test_vacuum ;---running [postgres@xiongcc ~]$ ll /dev/shm/total 400-rw------- 1 root root 0 Sep 16 09:24 aliyun-assist-agent-coldstarted-rw------- 1 postgres postgres 26976 Nov 22 15:47 PostgreSQL.1133681770-rw------- 1 postgres postgres 196736 Nov 22 15:48 PostgreSQL.4093131944-rw------- 1 postgres postgres 179712 Nov 22 15:48 PostgreSQL.41056914

再开个窗口,触发一次 select count(*) from test_vacuum ;

[postgres@xiongcc ~]$ ll /dev/shm/total 772-rw------- 1 root root 0 Sep 16 09:24 aliyun-assist-agent-coldstarted-rw------- 1 postgres postgres 26976 Nov 22 15:47 PostgreSQL.1133681770-rw------- 1 postgres postgres 179712 Nov 22 15:50 PostgreSQL.1144569852-rw------- 1 postgres postgres 196736 Nov 22 15:49 PostgreSQL.2052496742-rw------- 1 postgres postgres 179712 Nov 22 15:49 PostgreSQL.3106020600-rw------- 1 postgres postgres 196736 Nov 22 15:48 PostgreSQL.4093131944

所以假如删除了这个share memory segment的话,就会报错了

[postgres@xiongcc ~]$ rm -rf /dev/shm/PostgreSQL.*[postgres@xiongcc ~]$ ll /dev/shm/total 0-rw------- 1 root root 0 Sep 16 09:24 aliyun-assist-agent-coldstarted
postgres=# explain analyze select count(*) from test_vacuum ;ERROR: could not open shared memory segment "/PostgreSQL.4093131944": No such file or directoryCONTEXT: parallel worker postgres=# explain analyze select count(*) from test_vacuum ;ERROR: could not open shared memory segment "/PostgreSQL.2052496742": No such file or directoryCONTEXT: parallel worker

相应日志

2021-11-22 15:52:14.918 CST [23629] ERROR: could not open shared memory segment "/PostgreSQL.4093131944": No such file or directory2021-11-22 15:52:14.919 CST [23599] ERROR: could not open shared memory segment "/PostgreSQL.4093131944": No such file or directory2021-11-22 15:52:14.919 CST [23599] CONTEXT: parallel worker2021-11-22 15:52:14.919 CST [23599] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 15:52:14.920 CST [23628] FATAL: terminating background worker "parallel worker" due to administrator command2021-11-22 15:52:14.926 CST [23585] LOG: background worker "parallel worker" (PID 23628) exited with exit code 12021-11-22 15:52:14.926 CST [23585] LOG: background worker "parallel worker" (PID 23629) exited with exit code 12021-11-22 15:52:18.126 CST [23631] ERROR: could not open shared memory segment "/PostgreSQL.2052496742": No such file or directory2021-11-22 15:52:18.126 CST [23604] ERROR: could not open shared memory segment "/PostgreSQL.2052496742": No such file or directory2021-11-22 15:52:18.126 CST [23604] CONTEXT: parallel worker2021-11-22 15:52:18.126 CST [23604] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 15:52:18.126 CST [23585] LOG: background worker "parallel worker" (PID 23631) exited with exit code 12021-11-22 15:52:18.126 CST [23630] ERROR: could not map dynamic shared memory segment2021-11-22 15:52:18.127 CST [23585] LOG: background worker "parallel worker" (PID 23630) exited with exit code 1

最简单直接重连进程即可,会再次fork出相应的share memory segment。

假如跑的过程中,删除掉相应的share memory segment,再cancel掉查询,日志也会提示很明显

[postgres@xiongcc ~]$ ll /dev/shm/total 772-rw------- 1 root root 0 Sep 16 09:24 aliyun-assist-agent-coldstarted-rw------- 1 postgres postgres 179712 Nov 22 15:58 PostgreSQL.1189924088-rw------- 1 postgres postgres 196736 Nov 22 15:58 PostgreSQL.2670392812-rw------- 1 postgres postgres 26976 Nov 22 15:58 PostgreSQL.2739528852 ---启动时创建的segment-rw------- 1 postgres postgres 196736 Nov 22 15:58 PostgreSQL.3043848694-rw------- 1 postgres postgres 179712 Nov 22 15:58 PostgreSQL.399216344[postgres@xiongcc ~]$ rm -rf /dev/shm/PostgreSQL.* ---第一步 postgres=# explain analyze select count(*) from test_vacuum ; ---第二步^CCancel request sentERROR: canceling statement due to user request postgres=# explain analyze select count(*) from test_vacuum ; ---第三步^CCancel request sentERROR: canceling statement due to user request

日志提示

2021-11-22 15:58:27.198 CST [23673] ERROR: canceling statement due to user request2021-11-22 15:58:27.198 CST [23673] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 15:58:27.267 CST [23676] FATAL: terminating background worker "parallel worker" due to administrator command2021-11-22 15:58:27.267 CST [23676] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 15:58:27.270 CST [23677] FATAL: terminating background worker "parallel worker" due to administrator command2021-11-22 15:58:27.270 CST [23677] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 15:58:27.355 CST [23662] LOG: background worker "parallel worker" (PID 23677) exited with exit code 12021-11-22 15:58:27.359 CST [23676] WARNING: could not remove shared memory segment "/PostgreSQL.1189924088": No such file or directory2021-11-22 15:58:27.360 CST [23662] LOG: background worker "parallel worker" (PID 23676) exited with exit code 12021-11-22 15:58:28.148 CST [23679] ERROR: canceling statement due to user request2021-11-22 15:58:28.148 CST [23679] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 15:58:28.149 CST [23680] FATAL: terminating background worker "parallel worker" due to administrator command2021-11-22 15:58:28.149 CST [23680] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 15:58:28.150 CST [23662] LOG: background worker "parallel worker" (PID 23680) exited with exit code 12021-11-22 15:58:28.154 CST [23681] FATAL: terminating background worker "parallel worker" due to administrator command2021-11-22 15:58:28.154 CST [23681] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 15:58:28.154 CST [23681] WARNING: could not remove shared memory segment "/PostgreSQL.399216344": No such file or directory2021-11-22 15:58:28.155 CST [23662] LOG: background worker "parallel worker" (PID 23681) exited with exit code 1

再在原来的会话里执行SQL的话,就会报错了

postgres=# explain analyze select count(*) from test_vacuum ;^CCancel request sentERROR: canceling statement due to user requestpostgres=# explain analyze select count(*) from test_vacuum ;ERROR: could not open shared memory segment "/PostgreSQL.2670392812": No such file or directoryCONTEXT: parallel worker postgres=# explain analyze select count(*) from test_vacuum ;^CCancel request sentERROR: canceling statement due to user requestpostgres=# explain analyze select count(*) from test_vacuum ;ERROR: could not open shared memory segment "/PostgreSQL.3043848694": No such file or directoryCONTEXT: parallel worker

日志报错

2021-11-22 16:02:46.276 CST [23711] ERROR: could not open shared memory segment "/PostgreSQL.2670392812": No such file or directory2021-11-22 16:02:46.277 CST [23673] ERROR: could not open shared memory segment "/PostgreSQL.2670392812": No such file or directory2021-11-22 16:02:46.277 CST [23673] CONTEXT: parallel worker2021-11-22 16:02:46.277 CST [23673] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 16:02:46.277 CST [23712] ERROR: could not map dynamic shared memory segment2021-11-22 16:02:46.277 CST [23662] LOG: background worker "parallel worker" (PID 23711) exited with exit code 12021-11-22 16:02:46.277 CST [23662] LOG: background worker "parallel worker" (PID 23712) exited with exit code 12021-11-22 16:02:52.693 CST [23713] ERROR: could not open shared memory segment "/PostgreSQL.3043848694": No such file or directory2021-11-22 16:02:52.693 CST [23679] ERROR: could not open shared memory segment "/PostgreSQL.3043848694": No such file or directory2021-11-22 16:02:52.693 CST [23679] CONTEXT: parallel worker2021-11-22 16:02:52.693 CST [23679] STATEMENT: explain analyze select count(*) from test_vacuum ;2021-11-22 16:02:52.693 CST [23662] LOG: background worker "parallel worker" (PID 23713) exited with exit code 12021-11-22 16:02:52.694 CST [23714] ERROR: could not map dynamic shared memory segment2021-11-22 16:02:52.694 CST [23662] LOG: background worker "parallel worker" (PID 23714) exited with exit code 1

那么正常情况下是如何的呢?我们再深入探究一下,恢复一下环境

[postgres@xiongcc ~]$ ll /dev/shm/total 28-rw------- 1 root root 0 Sep 16 09:24 aliyun-assist-agent-coldstarted-rw------- 1 postgres postgres 26976 Nov 22 16:38 PostgreSQL.1313209960 ---postmaster创建

触发SQL,可以发现多了两个segment

postgres=# explain analyze select count(*) from test_vacuum ;---runnning [postgres@xiongcc ~]$ ll /dev/shm/total 400-rw------- 1 root root 0 Sep 16 09:24 aliyun-assist-agent-coldstarted-rw------- 1 postgres postgres 26976 Nov 22 16:38 PostgreSQL.1313209960 ---postmaster-rw------- 1 postgres postgres 179712 Nov 22 16:39 PostgreSQL.1863032232-rw------- 1 postgres postgres 196736 Nov 22 16:39 PostgreSQL.2304117568

跑完之后,可以发现parallel leader还在

[postgres@xiongcc ~]$ ps -ef | grep postgrespostgres 23986 1 0 16:38 ? 00:00:00 /usr/pgsql-14/bin/postgres -D pgdatapostgres 23988 23986 0 16:38 ? 00:00:00 postgres: checkpointer postgres 23989 23986 0 16:38 ? 00:00:00 postgres: background writer postgres 23990 23986 0 16:38 ? 00:00:00 postgres: walwriter postgres 23991 23986 0 16:38 ? 00:00:00 postgres: autovacuum launcher postgres 23992 23986 0 16:38 ? 00:00:00 postgres: archiver postgres 23993 23986 0 16:38 ? 00:00:00 postgres: stats collector postgres 23994 23986 0 16:38 ? 00:00:00 postgres: logical replication launcherpostgres 23995 22279 0 16:38 pts/2 00:00:00 psqlpostgres 23996 23986 4 16:38 ? 00:00:02 postgres: postgres postgres [local] EXPLAINpostgres 24003 23986 29 16:39 ? 00:00:02 postgres: parallel worker for PID 23996postgres 24004 23986 34 16:39 ? 00:00:02 postgres: parallel worker for PID 23996 [postgres@xiongcc ~]$ lsof -p 23996 | grep shmpostgres 23996 postgres mem REG 0,18 179712 136497542 /dev/shm/PostgreSQL.1863032232postgres 23996 postgres mem REG 0,18 196736 136497541 /dev/shm/PostgreSQL.2304117568postgres 23996 postgres mem REG 0,18 26976 136496800 /dev/shm/PostgreSQL.1313209960 [postgres@xiongcc ~]$ lsof -p 24004 | grep shmpostgres 24004 postgres mem REG 0,18 196736 136497541 /dev/shm/PostgreSQL.2304117568postgres 24004 postgres mem REG 0,18 179712 136497542 /dev/shm/PostgreSQL.1863032232postgres 24004 postgres mem REG 0,18 26976 136496800 /dev/shm/PostgreSQL.1313209960 [postgres@xiongcc ~]$ lsof -p 24003 | grep shmpostgres 24003 postgres mem REG 0,18 196736 136497541 /dev/shm/PostgreSQL.2304117568postgres 24003 postgres mem REG 0,18 179712 136497542 /dev/shm/PostgreSQL.1863032232postgres 24003 postgres mem REG 0,18 26976 136496800 /dev/shm/PostgreSQL.1313209960 [postgres@xiongcc ~]$ ll /dev/shm/total 224-rw------- 1 root root 0 Sep 16 09:24 aliyun-assist-agent-coldstarted-rw------- 1 postgres postgres 26976 Nov 22 16:38 PostgreSQL.1313209960 ---postmaster-rw------- 1 postgres postgres 196736 Nov 22 16:39 PostgreSQL.2304117568 ---parallel leader

这里我有个猜测,还待证实,不过应该是这样:因为PostgreSQL是有执行计划缓存的,假如多次跑SQL,可以延用之前的执行计划,第一次跑了之后,发现走了并行,PostgreSQL会留下leader进程的share memory segment,至于实际的worker,还要取决于是否有其他的并行进程在跑,比如max_parallel_workers是3,另外一个进程已经使用了2个worker,那么这一个进程就只能分配到一个进程了。

[postgres@xiongcc ~]$ ll /dev/shm/total 224-rw------- 1 root root 0 Sep 16 09:24 aliyun-assist-agent-coldstarted-rw------- 1 postgres postgres 26976 Nov 22 16:38 PostgreSQL.1313209960-rw------- 1 postgres postgres 196736 Nov 22 16:39 PostgreSQL.2304117568

另外我们知道,在并行中,还有一个参数 parallel_leader_participation,默认为on。

Allows the leader process to execute the query plan under Gather and Gather Merge nodes instead of waiting for worker processes. The default is on. Setting this value to off reduces the likelihood that workers will become blocked because the leader is not reading tuples fast enough, but requires the leader process to wait for worker processes to start up before the first tuples can be produced. The degree to which the leader can help or hinder performance depends on the plan type, number of workers and query duration.

Every background worker process which is successfully started for a given parallel query will execute the parallel portion of the plan. The leader will also execute that portion of the plan, but it has an additional responsibility: it must also read all of the tuples generated by the workers. When the parallel portion of the plan generates only a small number of tuples, the leader will often behave very much like an additional worker, speeding up query execution. Conversely, when the parallel portion of the plan generates a large number of tuples, the leader may be almost entirely occupied with reading the tuples generated by the workers and performing any further processing steps which are required by plan nodes above the level of the Gather node or Gather Merge node. In such cases, the leader will do very little of the work of executing the parallel portion of the plan.

允许领导者进程在Gather和Gather Merge节点下执行查询计划,而不是等待工作者进程。默认值是on。将此值设置为关闭,可以减少由于领导者读取元组的速度不够快而导致工作者受阻的可能性,但是需要领导者进程在产生第一个元组之前等待工作者进程的启动。领导进程对性能的帮助或阻碍程度取决于计划类型、工作进程的数量和查询持续时间。对于一个给定的并行查询,每个成功启动的后台工作进程将执行计划的并行部分。领导也将执行计划的这一部分,但是它有一个额外的责任:它还必须读取由工作者生成的所有元组。当计划的并行部分只产生少量的元组时,领导者的行为往往很像一个额外的工作者,加快了查询的执行速度。相反,当计划的并行部分产生了大量的元组时,领导者进程可能几乎完全被用来读取由工作者产生的元组,并执行任何进一步的处理步骤,这些步骤是由Gather节点或Gather合并节点以上的计划节点所要求的。在这种情况下,领导者在执行计划的并行部分时将做很少的工作。

那么设为off看一下

postgres=# set parallel_leader_participation to off;SETpostgres=# show parallel_leader_participation ; parallel_leader_participation ------------------------------- off(1 row) postgres=# explain analyze select count(*) from test_vacuum ;---running

可以看到,多了一个worker进程

[postgres@xiongcc ~]$ ll /dev/shm/total 1424-rw------- 1 root root 0 Sep 16 09:24 aliyun-assist-agent-coldstarted-rw------- 1 postgres postgres 26976 Nov 22 16:38 PostgreSQL.1313209960-rw------- 1 postgres postgres 196736 Nov 22 16:39 PostgreSQL.2304117568-rw------- 1 postgres postgres 1048576 Nov 22 16:45 PostgreSQL.418377524-rw------- 1 postgres postgres 179744 Nov 22 16:46 PostgreSQL.729891006

理清了这些思路之后,那么对于share memory也就可以类似参照了

Specifies the shared memory implementation that the server should use for the main shared memory region that holds PostgreSQL's shared buffers and other shared data. Possible values are mmap (for anonymous shared memory allocated using mmap), sysv (for System V shared memory allocated via shmget) and windows (for Windows shared memory). Not all values are supported on all platforms; the first supported option is the default for that platform. The use of the sysv option, which is not the default on any platform, is generally discouraged because it typically requires non-default kernel settings to allow for large allocations

postgres=# select setting,name,enumvals from pg_settings where name = 'shared_memory_type'; setting | name | enumvals ---------+--------------------+------------- mmap | shared_memory_type | {sysv,mmap}(1 row)

小结

所以,这一次的生产问题明了了:位于tmpfs下的PostgreSQL DSM(并行查询需要)不知道因为什么原因被删除了,现有的连接就会报错。最简单的方式就是让所有现有连接断了重连,即可恢复正常。当然重启大法最好用~

另外,我们会在base/pgsql_tmp下看到有sharedfileset的文件夹,这个也和并行相关,源码在sharedfileset.c里面,表示并行查询时生成的可以共用的临时文件。

* SharedFileSets provide a temporary namespace (think directory) so that * files can be discovered by name, and a shared ownership semantics so that * shared files survive until the last user detaches. * * SharedFileSets can be used by backends when the temporary files need to be * opened/closed multiple times and the underlying files need to survive across * transactions.

OK,好好恶补一下Linux知识吧!先从这张图开始

浅析Linux IO,你需要知道的底层_Linux_03

巨人的肩膀

https://zhuanlan.zhihu.com/p/67894878

https://www.leviathan.vip/2019/06/01/Linux%E5%86%85%E6%A0%B8%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-Page-Cache%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/

https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-DYNAMIC-SHARED-MEMORY-TYPE

https://www.youtube.com/watch?v=8hVLcyBkSXY

https://blog.csdn.net/zhangxiao93/article/details/52691901

https://blog.csdn.net/luckywang1103/article/details/50619251


文章有问题?点此查看未经处理的缓存