一、问题现象
使用pg_basebackup重建从库失败,报错如下:
[postgres@postgres_standby:pg11.5:6548 ~]$ pg_basebackup -D /opt/postgres/postgresql-11.5/pg11debug/data -U repuser -h 192.168.92.128 -P -v
Password:
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/2000028 on timeline 1
pg_basebackup: starting background WAL receiver
pg_basebackup: created temporary replication slot "pg_basebackup_63231"
23895012/23896025 kB (100%), 1/1 tablespace
pg_basebackup: could not get write-ahead log end position from server: ERROR: file name too long for tar format: "pg_log/elect pid,usename,query_start,wait_envents,state_query from pg_stat_activity where pid=15025"
二、定位分析
-
从报错来看,是在get wal end position时失败了, 原因为一个文件名超长,ERROR: file name too long for tar format: "pg_log/elect pid,usename,query_start,wait_envents,state_query from pg_stat_activity where pid=15025 "
-
查看主库pg_log目录确实存在这个文件,从这个诡异的文件名来看,应该是shell命令行误操作,把一条查询语句保存为text文件了
[postgres@postgres_primary:pg11.5:6548 ~]$ ll /opt/postgres/postgresql-11.5/pg11debug/data/pg_log
total 20
-rw-rw-r--. 1 postgres postgres 0 Oct 2 16:34 elect pid,usename,query_start,wait_envents,state_query from pg_stat_activity where pid=15025
-rw-------. 1 postgres postgres 188 Oct 1 23:02 postgresql-01.log
-rw-------. 1 postgres postgres 1878 Oct 2 17:41 postgresql-02.log
-rw-------. 1 postgres postgres 1615 Sep 18 20:49 postgresql-18.log
-rw-------. 1 postgres postgres 460 Oct 1 22:41 postgresql-19.log
-rw-------. 1 postgres postgres 725 Oct 1 22:43 postgresql-20.log
[postgres@postgres_primary:pg11.5:6548 ~]$
-
尝试移除这个文件后,重建成功了。接下来探究下源代码,看看到底在什么阶段抛出的报错,并且可接受的文件名最大长度是多少
-
简述下使用pg_basebackup重建的过程和原理
standby端作为client,使用pg_basebackup和primary也就是server端建立连接,通过walstream的方式对server端的所有数据文件做一个基础备份到本地。
standby:pg_basebackup进程和primary建立连接后,发送一个fastcheckpoint请求,primary做一次checkpoint刷盘,完成后pg_basebackup 进程fork出一个子进程pg_basebackup来获取备份期间primary新产生的wal,用于之后的一致性恢复;父进程同时同步data目录下除其他文件。也就是说会有两个pg_basebackup进程,为父子关系。
primary:对应standby两个pg_basebackup进程,产生两个wal sender进程,一个发送备份期间新产生的wal,一个发送数据文件。
备份期间出现任何报错,都会报错并回滚。
-
来看报错前半段pg_basebackup: could not get write-ahead log end position from server,在获取wal end position时失败了,失败的具体信息由PQerrorMessage(conn)返回
函数调用链:pg_basebackup.c: main() --> BaseBackup()
static void
BaseBackup(void)
{
/* 省略部分代码 */
/*
* Get the stop position
*/
res = PQgetResult(conn);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
fprintf(stderr,
_("%s: could not get write-ahead log end position from server: %s"),
progname, PQerrorMessage(conn));
disconnect_and_exit(1);
/* 省略部分代码 */
}
- 具体的报错来自primary调用_tarWriteHeader函数时,由于rc 为TAR_NAME_TOO_LONG,进入对应case分支,ereport抛出报错,报错同时会被记录到conn.errorMessage,standby通过PQerrorMessage(conn)获取。
函数调用链:WalSender.c:1521 --> exec_replication_command() --> SendBasebackup() --> perform_base_backup() --> sendDir() --> sendFile() --> _tarWriteHeader()
static int64
_tarWriteHeader(const char *filename, const char *linktarget,
struct stat *statbuf, bool sizeonly)
{
char h[512];
enum tarError rc;
if (!sizeonly)
{
rc = tarCreateHeader(h, filename, linktarget, statbuf->st_size,
statbuf->st_mode, statbuf->st_uid, statbuf->st_gid,
statbuf->st_mtime);
switch (rc)
{
case TAR_OK:
break;
case TAR_NAME_TOO_LONG:
ereport(ERROR,
(errmsg("file name too long for tar format: \"%s\"",
filename)));
break;
/* 省略部分代码 */
}
- rc是函数tarCreateHeader的返回值,看下如何返回的TAR_NAME_TOO_LONG,可以看到当ilename长度大于99,就会返回TAR_NAME_TOO_LONG
enum tarError
tarCreateHeader(char *h, const char *filename, const char *linktarget,
pgoff_t size, mode_t mode, uid_t uid, gid_t gid, time_t mtime)
{
if (strlen(filename) > 99)
return TAR_NAME_TOO_LONG;
/* 省略部分代码 */
}
- 写一段小程序验证下报错中的filename长度
[postgres@postgres_primary:pg11.5:6548 ~]$ cat strlen.c
#include <stdio.h>
#include <string.h>
void main() {
char filename[] = "pg_log/elect pid,usename,query_start,wait_envents,state_query from pg_stat_activity where pid=15025";
int num = 0;
num = strlen(filename);
printf("strlen(filename) = %d\n", num);
}
编译并执行,运算结果长度为100,正好大于99
[postgres@postgres_primary:pg11.5:6548 ~]$ gcc -o strlen strlen.c
[postgres@postgres_primary:pg11.5:6548 ~]$ ./strlen
strlen(filename) = 100
[postgres@postgres_primary:pg11.5:6548 ~]$
三、总结反思
由于误操作,生成一个文件名(包含pg_log目录)长度大于99的文件,导致备份失败。因此DBA同学在登录数据库后台操作时一定要谨慎,尽量不要在data路径及任何子目录下创建一些无关的文件,同时不要使用其他用户,特别是root,操作数据库相关文件。