PostgreSQL运维案例--文件名超长导致从库重建失败

一、问题现象

使用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"

二、定位分析

  1. 从报错来看,是在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 "

  2. 查看主库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 ~]$
  1. 尝试移除这个文件后,重建成功了。接下来探究下源代码,看看到底在什么阶段抛出的报错,并且可接受的文件名最大长度是多少

  2. 简述下使用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,一个发送数据文件。

    备份期间出现任何报错,都会报错并回滚。

  3. 来看报错前半段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);
/* 省略部分代码 */
	}
  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;
	/* 省略部分代码 */
}
  1. 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;
/* 省略部分代码 */
}
  1. 写一段小程序验证下报错中的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,操作数据库相关文件。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值