小案例一则,PG 中的延迟创建
前言
这两天在研究一个客户问题的时候,意外发现了 PG/GP 中的"懒创建" —— 临时文件所在目录并非初始化时便会创建,而是按需创建,或许是为了提升启动速度、降低文件系统噪音,也可能是避免初始化阶段创建一堆无用的目录,让我们一起瞅瞅这个小案例。
现象
对于 PG,默认情况下,产生的临时文件会位于 base 的 pgsql_tmp 子目录下,以 pid 为前缀,会话结束后 (安全退出) 自动释放,crash 之后是否保留临时文件则取决于 remove_temp_files_after_crash 参数。Greenplum 就是大号的 PG,GP 中有类似 work_mem 的 statement_mem 参数,不过是会话级的内存限制,即控制整个会话可以使用的内存,而不是算子级,查询所需内存超过 statement_mem 那么就溢出到磁盘,和 PG 是相同的原理。
但是以这个 GP 环境为例,可以看到 Segment 的 base 目录下并没有 pgsql_tmp 这个目录,莫非 GP 与 PG 有所不同,放在了其他路径之下?
[mxadmin@segment1 base]$ ll
total 128
drwx------ 2 mxadmin mxadmin 8192 Oct 13 16:30 1
drwx------ 2 mxadmin mxadmin 8192 Oct 28 01:11 14010
drwx------ 2 mxadmin mxadmin 12288 Oct 29 00:32 14011
drwx------ 2 mxadmin mxadmin 40960 Oct 29 15:26 18653
drwx------ 2 mxadmin mxadmin 8192 Oct 27 11:04 49152
[mxadmin@segment1 base]$ pwd
/home/mxdata_20251013162933/primary/mxseg0/base
不过咨询了下研发同事,经过 DEBUG,发现确实也在 pgsql_tmp 目录下。
那么问题来了,为啥我这个环境没有这个目录,会不会是动态按需创建?为此,我特意去重新初始化了一个 PG
[postgres@mypg ~]$ ll pgdata/base/
total 12
drwx------ 2 postgres postgres 4096 Oct 29 17:48 1
drwx------ 2 postgres postgres 4096 Oct 29 17:48 4
drwx------ 2 postgres postgres 4096 Oct 29 17:48 5
可以看到,PG 也没有此目录。大概翻了下代码,找到了背后原因:
/*
* Return the path of the temp directory in a given tablespace.
*/
void
TempTablespacePath(char *path, Oid tablespace)
{
/*
* Identify the tempfile directory for this tablespace.
*
* If someone tries to specify pg_global, use pg_default instead.
*/
if (tablespace == InvalidOid ||
tablespace == DEFAULTTABLESPACE_OID ||
tablespace == GLOBALTABLESPACE_OID)
snprintf(path, MAXPGPATH, "base/%s", PG_TEMP_FILES_DIR);
else
{
/* All other tablespaces are accessed via symlinks */
snprintf(path, MAXPGPATH, "pg_tblspc/%u/%s/%s",
tablespace, TABLESPACE_VERSION_DIRECTORY,
PG_TEMP_FILES_DIR);
}
}
/* Filename components */
#define PG_TEMP_FILES_DIR "pgsql_tmp"
#define PG_TEMP_FILE_PREFIX "pgsql_tmp"
临时文件就两个路径,一种情况是 base/pgsql_tmp,另外一种是指定了临时表空间时,pg_tblspc/<ts_oid>/<PG_VERSION_DIR>/pgsql_tmp。然后便是打开临时文件的逻辑
第一次创建文件时,若 base/pgsql_tmp目录还不存在,那么PathNameOpenFile()会失败;然后执行 (void) MakePGDirectory(tempdirpath);,内部就是mkdir(path, 0700);目录创建后,再次尝试创建文件
/*
* Open a temporary file in a specific tablespace.
* Subroutine for OpenTemporaryFile, which see for details.
*/
static File
OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError)
{
char tempdirpath[MAXPGPATH];
char tempfilepath[MAXPGPATH];
File file;
...
/*
* Open the file. Note: we don't use O_EXCL, in case there is an orphaned
* temp file that can be reused.
*/
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
if (file <= 0)
{
/*
* We might need to create the tablespace's tempfile directory, if no
* one has yet done so.
*
* Don't check for an error from MakePGDirectory; it could fail if
* someone else just did the same thing. If it doesn't work then
* we'll bomb out on the second create attempt, instead.
*/
(void) MakePGDirectory(tempdirpath);
file = PathNameOpenFile(tempfilepath,
O_RDWR | O_CREAT | O_TRUNC | PG_BINARY);
if (file <= 0 && rejectError)
elog(ERROR, "could not create temporary file \"%s\": %m",
tempfilepath);
}
return file;
}
复现
既然知晓了逻辑,随便跑个 SRF,超过 work_mem 即可复现:
[postgres@mypg base]$ ls -l
total 12
drwx------ 2 postgres postgres 4096 Oct 29 18:16 1
drwx------ 2 postgres postgres 4096 Oct 29 17:48 4
drwx------ 2 postgres postgres 4096 Oct 29 18:17 5
postgres=# explain analyze SELECT generate_series(1,10000000) AS id ORDER BY id DESC;
QUERY PLAN
----------------------------------------------------------------------------------------------------
--------------
Sort (cost=1486115.85..1511115.85 rows=10000000 width=4) (actual time=2829.537..3602.655 rows=1000
0000 loops=1)
Sort Key: (generate_series(1, 10000000)) DESC
Sort Method: external merge Disk: 117528kB
-> ProjectSet (cost=0.00..50000.02rows=10000000 width=4) (actual time=0.006..687.297rows=1000
0000 loops=1)
-> Result (cost=0.00..0.01rows=1 width=0) (actual time=0.003..0.004rows=1 loops=1)
Planning Time: 0.036 ms
Execution Time: 4079.191 ms
(7rows)
[postgres@mypg base]$ ls -l
total 16
drwx------ 2 postgres postgres 4096 Oct 29 18:16 1
drwx------ 2 postgres postgres 4096 Oct 29 17:48 4
drwx------ 2 postgres postgres 4096 Oct 29 18:17 5
drwx------ 2 postgres postgres 4096 Oct 29 18:21 pgsql_tmp
小结
创建时机:只有当第一次需要临时文件 (例如
work_mem不够、大排序/大哈希发生落盘、或某些临时对象需要落盘) 时,后端在打开临时文件的过程中才确保目录存在并创建文件,因此新库/新实例刚初始化时看不到此目录,PG 和 GP 同样逻辑。创建/命名/清理由
fd.c的OpenTemporaryFile*系列与 postmaster 启动清理流程共同完成。