PostgreSQL码农集散地

用AI修程序BUG|几下就解决了pg_bulkload 不兼容PFS接口问题

参考文档点击文末阅读原文打开; 推荐《最好的PostgreSQL学习镜像》;


PolarDB 100 问 | PolarDB 11 使用 pg_bulkload 插件导入数据报错

在此之前, 使用这篇文章提到的方法解决了pg_bulkload编译安装的问题:《PolarDB 100 问 | PolarDB 11 编译 pg_bulkload 插件报错》

随之而来的是在使用pg_bulkload导入数据的时候遇到报错.

下面记录一下整个排错过程, 期间使用了AI, 给予了很大帮助. DBA也能轻松fix 源码bug了!

问题: PolarDB 11 使用 pg_bulkload 插件导入数据报错

pg_bulkload往PolarDB表中导入数据时报如下错误:

pg_bulkload -i ./foo.csv -O foo -l test_foo.log -p 5432 -o "TYPE=csv" -o "DELIMITER=," -d postgres -U postgres  

NOTICE: BULK LOAD START  
ERROR: query failed: ERROR:  could not open data file: No such file or directory  
DETAIL: query was: SELECT * FROM pgbulkload.pg_bulkload($1)  

复现方法

1、首先使用《PolarDB 100 问 | PolarDB 11 编译 pg_bulkload 插件报错》这篇文章提到的方法解决pg_bulkload安装的问题.

2、下面可以参考满哥的这篇文章( https://www.modb.pro/db/1754101551677394944 ), 创建一张foo表, 生成一些测试数据, 并使用pg_bulkload命令行往PolarDB foo表进行导入.

2.1、在PolarDB中建表

create table foo(a int, b varchar);  

2.2、生成点数据

cd /tmp/pg_bulkload  

seq 100000| awk '{print $0",foo"}' > foo.csv  

2.3、使用pg_bulkload命令行往PolarDB foo表进行导入, 报错如下

pg_bulkload -i ./foo.csv -O foo -l test_foo.log -p 5432 -o "TYPE=csv" -o "DELIMITER=," -d postgres -U postgres  

NOTICE: BULK LOAD START  
ERROR: query failed: ERROR:  could not write loadstatus file "pg_bulkload/13164.17715.loadstatus": Invalid argument  
DETAIL: query was: SELECT * FROM pgbulkload.pg_bulkload($1)  

3、搜索could not write loadstatus file报错相关的代码:

postgres@8b0c41bd948e:/tmp/pg_bulkload$ grep -r "could not write loadstatus file" *  
lib/writer_direct.c:   errmsg("could not write loadstatus file \"%s\": %m", self->lsf_path)));  

分析了这部分代码, 猜测可能和BasicOpenFilePerm 传入了polar_vfs = true有关:

pg_bulkload: lib/writer_direct.c  

#if PG_VERSION_NUM >= 110000  
       self->lsf_fd = BasicOpenFilePerm(self->lsf_path,  // 后面的报错都提到了 self 变量  
               O_CREAT | O_EXCL | O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR, true);  // 可能和 BasicOpenFilePerm 传入了polar_vfs = true有关    
#else  
       self->lsf_fd = BasicOpenFile(self->lsf_path,  
               O_CREAT | O_EXCL | O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);  
#endif  
if (self->lsf_fd == -1)  
               ereport(ERROR, (errcode_for_file_access(),  
                       errmsg("could not create loadstatus file \"%s\": %m", self->lsf_path)));  

if (write(self->lsf_fd, ls, sizeof(LoadStatus)) != sizeof(LoadStatus) ||  
               pg_fsync(self->lsf_fd) != 0)  
       {  
               UnlinkLSF(self);  
               ereport(ERROR, (errcode_for_file_access(),  
                       errmsg("could not write loadstatus file \"%s\": %m", self->lsf_path)));  
       }  

4、将《PolarDB 100 问 | PolarDB 11 编译 pg_bulkload 插件报错》这篇文章解决pg_bulkload编译错误问题时设置的polar_vfs = true全部改成false.

vi lib/writer_direct.c: self->lsf_fd = BasicOpenFilePerm(self->lsf_path,  ... false
vi lib/writer_direct.c: fd = BasicOpenFilePerm(fname, O_CREAT | O_WRONLY | PG_BINARY, S_IRUSR | S_IWUSR, false);  
vi lib/writer_binary.c: fd = BasicOpenFilePerm(fname, O_WRONLY | O_CREAT | O_EXCL | PG_BINARY,  ... false

重新安装pg_bulkload

USE_PGXS=1 make install  

5、再次导入, 报错变成了这样:

pg_bulkload -i ./foo.csv -O foo -l test_foo.log -p 5432 -o "TYPE=csv" -o "DELIMITER=," -d postgres -U postgres  

NOTICE: BULK LOAD START  
ERROR: query failed: ERROR:  could not open data file: No such file or directory  
DETAIL: query was: SELECT * FROM pgbulkload.pg_bulkload($1)  

报错和这个issue ( https://github.com/ApsaraDB/PolarDB-for-PostgreSQL/issues/532 ) 遇到的问题一样:

\! pg_bulkload -d contrib_regression data/bin1.ctl -i data/data1.bin -l results/bin1.log -P results/bin1.prs -u results/bin1.dup  
 NOTICE: BULK LOAD START  
! ERROR: query failed: ERROR:  could not open data file: No such file or directory  
! DETAIL: query was: SELECT * FROM pgbulkload.pg_bulkload($1)  
 \! awk -f data/adjust.awk results/bin1.log  

解决办法

1、分析这个报错的代码出处

搜索pg_bulkload中包含could not open data file错误的代码

postgres@8b0c41bd948e:/tmp/pg_bulkload$ grep -r "could not open data file" *  

bin/recovery.c:    "could not open data file \"%s\": %s",  
bin/recovery.c:      "could not open data file \"%s\": %s",  
lib/writer_direct.c:      errmsg("could not open data file: %m")));  

看了pg_bulkload的手册, 导入和recovery无关, 所以重点分析lib/writer_direct.c:

#if PG_VERSION_NUM >= 110000  
       // 通过BasicOpenFilePerm获取文件描述符  
       fd = BasicOpenFilePerm(fname, O_CREAT | O_WRONLY | PG_BINARY, S_IRUSR | S_IWUSR, true);  
#else  
       fd = BasicOpenFile(fname, O_CREAT | O_WRONLY | PG_BINARY, S_IRUSR | S_IWUSR);  
#endif  
       // 说明通过BasicOpenFilePerm获取文件描述符失败了  
if (fd == -1)  
               ereport(ERROR, (errcode_for_file_access(),  
                                               errmsg("could not open data file: %m")));  

从代码可以看出 通过BasicOpenFilePerm获取文件描述符失败了,BasicOpenFilePerm的其他参数都没问题, 那就只能是fname有问题了.

再往前翻一下, fname是这样得到的:

        fname = relpath(bknode, MAIN_FORKNUM);    

2、打印看看fname到底是什么?

使用ereport把fname打印到PolarDB的日志中, 把下面2行放到上面这段fname代码的下面:

        ereport(LOG,  
          (errmsg("fname: %s \n", fname)));  

3、重新编译pg_bulkload, 执行导入, 查看PolarDB日志:

USE_PGXS=1 make install  
pg_bulkload -i ./foo.csv -O foo -l test_foo.log -p 5432 -o "TYPE=csv" -o "DELIMITER=," -d postgres -U postgres  

PolarDB日志在master节点的pg_log目录中, 如下:

postgres@8b0c41bd948e:/tmp/PolarDB-for-PostgreSQL$ cd ~/tmp_master_dir_polardb_pg_1100_bld/pg_log  

postgres@8b0c41bd948e:~/tmp_master_dir_polardb_pg_1100_bld/pg_log$ ll  
total 264  
drwx------  2 postgres postgres   4096 Dec 10 00:00 ./  
drwx------ 24 postgres postgres   4096 Dec 10 11:13 ../  
-rw-------  1 postgres postgres   1033 Dec  9 17:48 postgresql-2024-12-09_105859_0_audit.log  
-rw-------  1 postgres postgres  45709 Dec  9 23:55 postgresql-2024-12-09_105859_error.log  
-rw-------  1 postgres postgres      0 Dec  9 10:58 postgresql-2024-12-09_105859_slow.log  
-rw-------  1 postgres postgres      0 Dec 10 00:00 postgresql-2024-12-10_000000_0_audit.log  
-rw-------  1 postgres postgres    510 Dec 10 10:36 postgresql-2024-12-10_000000_1_audit.log  
-rw-------  1 postgres postgres    958 Dec 10 11:04 postgresql-2024-12-10_000000_2_audit.log  
-rw-------  1 postgres postgres 188963 Dec 10 12:16 postgresql-2024-12-10_000000_error.log  
-rw-------  1 postgres postgres      0 Dec 10 00:00 postgresql-2024-12-10_000000_slow.log  

postgres@8b0c41bd948e:~/tmp_master_dir_polardb_pg_1100_bld/pg_log$ less postgresql-2024-12-10_000000_error.log  

找到 fname:  

2024-12-10 12:15:51.462 CST [20323] [20323] LOG:  fname: file-dio:///home/postgres/tmp_datadir_polardb_pg_1100_bld/base/13164/17715  

file name路径中为什么会包含file-dio://呢?

不应该是这样的吗?

postgres=# select pg_relation_filepath(oid) from pg_class where relname='foo';  
                           pg_relation_filepath                              
----------------------------------------------------------------------------  
file-dio:///home/postgres/tmp_datadir_polardb_pg_1100_bld/base/13164/17715  
(1 row)  

4、PolarDB的file name路径中为什么要以file-dio://开头?

老规矩, 搜一下PolarDB v11的代码:

postgres@8b0c41bd948e:~$ cd /tmp/PolarDB-for-PostgreSQL/  
postgres@8b0c41bd948e:/tmp/PolarDB-for-PostgreSQL$ grep -r file-dio *  
polardb_build.sh:  polar_datadir = 'file-dio://${pg_bld_data_dir}'" >> $pg_bld_master_dir/postgresql.conf  
src/include/storage/polar_fd.h:#define POLAR_VFS_PROTOCAL_LOCAL_DIO "
file-dio://"  
src/test/polar_pl/t/013_polar_shared_storage_double_rw_detection.pl:$new_node_master->append_conf('postgresql.conf', "
polar_datadir=\'file-dio://$master_polar_datadir\'");  
src/test/polar_pl/t/013_polar_shared_storage_double_rw_detection.pl:$node_standby1->append_conf('postgresql.conf', "
polar_datadir=\'file-dio://$master_polar_datadir\'");  
src/test/polar_pl/t/013_polar_shared_storage_double_rw_detection.pl:$node_standby1->append_conf('postgresql.conf', "
polar_datadir=\'file-dio://$node_standby1_polar_datadir\'");  
src/test/polar_pl/t/008_polar_directio_twophase.pl:       q[select 1 from pg_settings where name = 'polar_datadir' and setting like 'file-dio://%';]),  
src/test/polar_pl/t/014_polar_basebackup_and_restore_test.pl:$node_master->append_conf('postgresql.conf', 'polar_datadir=\'file-dio://./polar_shared_data/\'');  
src/test/perl/PostgresNode.pm: $self->append_conf('postgresql.conf', "
polar_datadir=\'file-dio://$polar_datadir\'");  
src/test/perl/PostgresNode.pm:  $self->append_conf('postgresql.conf', "
polar_datadir=\'file-dio://$dst_polar_datadir\'");  
src/test/perl/PostgresNode.pm: polar_datadir='file-dio://$polar_datadir'  
src/test/perl/PostgresNode.pm: polar_datadir='file-dio://$polar_datadir'  

是POLAR_VFS_PROTOCAL_LOCAL_DIO这个宏定义的, 那么当PolarDB部署在本地环境中(不使用共享存储和PFS时), 要解决pg_bulkload以上报错, 去掉路径中的file-dio://应该就可以了.

5、把fname头部的file-dio://去掉, 重新编译, 测试导入.

修改相关代码, 最终代码如下. 这部分使用了chatgpt, 后来在释放新增变量内存空间时遇到一个问题产生了coredump, 分析core时也用到了chatgpt.

vi lib/writer_direct.c  

/**  
* @brief Open the next data file and returns its descriptor.  
* @param rnode  [in] RelFileNode of target relation.  
* @param blknum [in] Block number to seek.  
* @return File descriptor of the last data file.  
*/  
static int  
open_data_file(  
#if PG_VERSION_NUM >= 160000  
   RelFileLocator rLocator,  
#else  
   RelFileNode rnode,  
#endif  
   bool istemp, BlockNumber blknum)  
{  
int   fd = -1;  
int   ret;  
BlockNumber segno;  
char    *fname = NULL;  
// 新增2个变量  
int len;  
char *pg_fname = NULL;  

#if PG_VERSION_NUM >= 90100  
#if PG_VERSION_NUM >= 160000  
RelFileLocatorBackend bknode;  
bknode.locator = rLocator;  
#else  
RelFileNodeBackend bknode;  
bknode.node = rnode;  
#endif  
bknode.backend = istemp ? MyBackendId : InvalidBackendId;  
fname = relpath(bknode, MAIN_FORKNUM);  
#else  
fname = relpath(rnode, MAIN_FORKNUM);  
#endif  

   // 新增以下5行代码, 删除fname的前11个字符串: file-dio://  
len = strlen(fname);  
pg_fname = (char *)palloc(len - 11 + 1);  
strcpy(pg_fname, fname + 11);  
ereport(LOG,  
          (errmsg("fname: %s, pg_fname: %s \n", fname, pg_fname)));  

segno = blknum / RELSEG_SIZE;  
if (segno > 0)  
{  
 /*  
  * The length `+ 12' is taken from _mdfd_openmesg() in backend/storage/smgr/md.c.  
  */  
 char    *tmp = palloc(strlen(fname) + 12);  

 sprintf(tmp, "%s.%u", fname, segno);  
 pfree(fname);  
 fname = tmp;  

 // 新增以下4行代码, 删除fname的前11个字符串: file-dio://  
 pfree(pg_fname);  
 len = strlen(fname);  
 pg_fname = (char *)palloc(len - 11 + 1);  
 strcpy(pg_fname, fname + 11);  
}  
#if PG_VERSION_NUM >= 110000  
fd = BasicOpenFilePerm(pg_fname, O_CREAT | O_WRONLY | PG_BINARY, S_IRUSR | S_IWUSR, false);  
#else  
fd = BasicOpenFile(pg_fname, O_CREAT | O_WRONLY | PG_BINARY, S_IRUSR | S_IWUSR);  
#endif  
if (fd == -1)  
 ereport(ERROR, (errcode_for_file_access(),  
     errmsg("could not open data file: %m")));  
ret = lseek(fd, BLCKSZ * (blknum % RELSEG_SIZE), SEEK_SET);  
if (ret == -1)  
{  
 close(fd);  
 ereport(ERROR, (errcode_for_file_access(),  
     errmsg  
     ("could not seek the end of the data file: %m")));  
}  

pfree(fname);  

// 新增以下1行代码, 释放pg_fname内存空间  
pfree(pg_fname);  

return fd;  
}  

重新编译, 测试导入

USE_PGXS=1 make install  

pg_bulkload -i ./foo.csv -O foo -l test_foo.log -p 5432 -o "TYPE=csv" -o "DELIMITER=," -d postgres -U postgres  

NOTICE: BULK LOAD START  
NOTICE: BULK LOAD END  
0 Rows skipped.  
100000 Rows successfully loaded.  
0 Rows not loaded due to parse errors.  
0 Rows not loaded due to duplicate errors.  
0 Rows replaced with new rows.  

PolarDB 数据库日志显示正常

2024-12-10 14:36:07.289 CST [21385] [21385] LOG:  fname: file-dio:///home/postgres/tmp_datadir_polardb_pg_1100_bld/base/13164/24619, pg_fname: /home/postgres/tmp_datadir_polardb_pg_1100_bld/base/13164/24619   

2024-12-10 14:36:07.289 CST [21385] [21385] STATEMENT:  SELECT * FROM pgbulkload.pg_bulkload($1)  

pg_bulkload 日志显示正常

less test_foo.log  

pg_bulkload 3.1.21 on 2024-12-10 14:46:53.913059+08  

INPUT = /tmp/pg_bulkload/foo.csv  
PARSE_BADFILE = /home/postgres/tmp_master_dir_polardb_pg_1100_bld/pg_bulkload/20241210144653_postgres_public_foo.prs.csv  
LOGFILE = /tmp/pg_bulkload/test_foo.log  
LIMIT = INFINITE  
PARSE_ERRORS = 0  
CHECK_CONSTRAINTS = NO  
TYPE = CSV  
SKIP = 0  
DELIMITER = ,  
QUOTE = "\""
ESCAPE = "\""
NULL =  
OUTPUT = public.foo  
MULTI_PROCESS = NO  
VERBOSE = NO  
WRITER = DIRECT  
DUPLICATE_BADFILE = /home/postgres/tmp_master_dir_polardb_pg_1100_bld/pg_bulkload/20241210144653_postgres_public_foo.dup.csv  
DUPLICATE_ERRORS = 0  
ON_DUPLICATE_KEEP = NEW  
TRUNCATE = NO  


 0 Rows skipped.  
 100000 Rows successfully loaded.  
 0 Rows not loaded due to parse errors.  
 0 Rows not loaded due to duplicate errors.  
 0 Rows replaced with new rows.  

Run began on 2024-12-10 14:46:53.913059+08  
Run ended on 2024-12-10 14:46:53.95547+08  

CPU 0.01s/0.02u sec elapsed 0.04 sec  

注意这个文档仅供学习交流, 很多pg_bulkload的功能也没有测试是否正常, 解决方法也只适合PolarDB使用本地磁盘的情况, 对于PolarDB使用共享存储时, 可能要适配pfs接口进行写入.

延展问题

pg_bulkload导入PolarDB后, 重启数据库前“查不到数据”

虽然上面导入日志都正常, 但是“查不到数据”.

导入完成后迅速查询一下数据有没有导入进去?

postgres=# select count(*) from foo;  
count    
--------  
0  
(1 row)  

奇怪了, 数据文件都已经有3.6MB了, 显然数据已经进去了, 但是查不到.

postgres=# select pg_table_size('foo');  
?column?  
----------  
 3633152  
(1 row)  

postgres=# vacuum foo; -- vacuum 后还是查不到, 而且数据文件的大小也没变化, 说明里面是有数据的.    

postgres=# select count(*) from foo;  
count    
--------  
0  
(1 row)  

postgres=# select pg_table_size('foo');  
?column?  
----------  
 3633152  
(1 row)  

使用pageinspect看一看foo表的数据块, 居然报错

postgres=# create extension pageinspect;  

postgres=# SELECT * FROM page_header(get_raw_page('foo', 0));  
ERROR:  block number 0 is out of range for relation "foo"

顺着PolarDB pageinspect插件代码往下查, 有可能和PolarDB table size cache管理有一定关系:

contrib/pageinspect/rawpage.c

if (blkno >= RelationGetNumberOfBlocksInFork(rel, forknum))  
               ereport(ERROR,  
                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),  
                                errmsg("block number %u is out of range for relation \"%s\"",  
                                               blkno, RelationGetRelationName(rel))));  

src/backend/storage/buffer/bufmgr.c

/*  
* RelationGetNumberOfBlocksInFork  
*              Determines the current number of pages in the specified relation fork.  
*/  
BlockNumber  
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)  
{  
       /* Open it at the smgr level if not already done */  
       RelationOpenSmgr(relation);  

return smgrnblocks(relation->rd_smgr, forkNum);  
}  

src/backend/storage/smgr/smgr.c

/*  
*      smgrnblocks() -- Calculate the number of blocks in the  
*                                       supplied relation.  
* polar_enabled_nblock_cache_all() means enable smgr cache and the mode is  
* cache all.  
*/  
BlockNumber  
smgrnblocks(SMgrRelation reln, ForkNumber forknum)  
{  
       BlockNumber result;  

if (polar_enabled_nblock_cache_all())  
               result = polar_nblocks_cache_search_and_update(reln, forknum, true);  
else
               result = smgrsw[reln->smgr_which].smgr_nblocks(reln, forknum, false);  

return result;  
}  

...  

/* POLAR: smgr cache search and update */  
BlockNumber  
polar_nblocks_cache_search_and_update(SMgrRelation reln, ForkNumber forknum, bool need_update)  
{  
       BlockNumber result;  

       /* POLAR: For some scenes, we don't support nblocks cache. */  
       if (polar_nouse_nblocks_cache(reln, forknum))  
       {  
               result = smgrsw[reln->smgr_which].smgr_nblocks(reln, forknum, false);  
               return result;  
       }  

       /* Can we get the answer from shared memory without locking? */  
       result = smgrnblocks_fast(reln, forknum);  
       if (result != InvalidBlockNumber)  
               return result;  

       /* Can we get the answer from shared memory with only a share lock? */  
       result = smgrnblocks_shared(reln, forknum);  
       if (result != InvalidBlockNumber)  
               return result;  

       /* Ask the kernel. */  
       result = smgrsw[reln->smgr_which].smgr_nblocks(reln, forknum, false);  

       /* Update the value in shared memory for faster service next time. */  
       if (need_update)  
               smgrnblocks_update(reln, forknum, result, false);  

       return result;  
}  

相关参数

postgres=# select * from pg_settings where name ~ 'cache';  
                  name                   | setting | unit |               category                |                               short_desc                               |                              
                                                extra_desc                                                                               |  context   | vartype |       source       | min_val |  max_val  
  |         enumvals          | boot_val | reset_val |                            sourcefile                             | sourceline | pending_restart  
------------------------------------------+---------+------+---------------------------------------+------------------------------------------------------------------------+-----------------------------  
------------------------------------------------------------------------------------------------------------------------------------------+------------+---------+--------------------+---------+---------  
---+---------------------------+----------+-----------+-------------------------------------------------------------------+------------+-----------------  
polar_nblocks_cache_mode                 | all     |      | Ungrouped                             | Number of Blocks cache mode                                            |                              
                                                                                                                                         | sighup     | enum    | configuration file |         |          
  | {all,bitmapscan,scan,off} | scan     | all       | /home/postgres/tmp_master_dir_polardb_pg_1100_bld/postgresql.conf |        733 | f  
polar_px_cached_px_workers               | 0       |      | PostgreSQL / Array Tuning             | Sets the maximum number of px workers to cache between statements.     |                              
                                                                                                                                         | user       | integer | default            | 0       | 21474836  
47 |                           | 0        | 0         |                                                                   |            | f  
polar_rel_size_cache_blocks              | 2       |      | Ungrouped                             | Set the number of blocks to record relation size cache                 |                              
                                                                                                                                         | postmaster | integer | default            | 0       | 10737418  
23 |                           | 2        | 2         |                                                                   |            | f  
polar_vfs.enable_file_size_cache         | off     |      | Customized Options                    | enable file size cache                                                 |                              
                                                                                                                                         | postmaster | bool    | default            |         |          
  |                           | on       | on        |                                                                   |            | f  
(10 rows)  

postgres=# show polar_nblocks_cache_mode;  
polar_nblocks_cache_mode  
--------------------------  
all  
(1 row)  

postgres=# show polar_px_cached_px_workers;  
polar_px_cached_px_workers  
----------------------------  
0  
(1 row)  

postgres=# show polar_rel_size_cache_blocks;  
polar_rel_size_cache_blocks  
-----------------------------  
2  
(1 row)  

postgres=# show polar_vfs.enable_file_size_cache;  
polar_vfs.enable_file_size_cache  
----------------------------------  
off  
(1 row)  

目前通过pg_bulkload direct模式导入到PolarDB的数据, 需要重启数据库, 才能查到数据

$ pg_ctl restart -m fast -D ~/tmp_master_dir_polardb_pg_1100_bld    

$ psql  

postgres=# select count(*) from foo;  
count    
--------  
100000  
(1 row)  

把polar_nblocks_cache_mode设置为scan, pg_bulkload导入后可以直接看到数据.

# 修改polar_nblocks_cache_mode参数  
postgres=# alter system set polar_nblocks_cache_mode='scan';  
ALTER SYSTEM  

# 重启PolarDB  
$ pg_ctl restart -m fast -D ~/tmp_master_dir_polardb_pg_1100_bld  

现在pg_bulkload导入后, 可以在PolarDB中直接看到数据, 不需要重启PolarDB了!

postgres@8b0c41bd948e:/tmp/pg_bulkload$ psql -c "select count(*) from foo;"
count    
--------  
500000  
(1 row)  

postgres@8b0c41bd948e:/tmp/pg_bulkload$ pg_bulkload -i ./foo.csv -O foo -l test_foo.log -p 5432 -o "TYPE=csv" -o "DELIMITER=," -d postgres -U postgres  
NOTICE: BULK LOAD START  
NOTICE: BULK LOAD END  
0 Rows skipped.  
100000 Rows successfully loaded.  
0 Rows not loaded due to parse errors.  
0 Rows not loaded due to duplicate errors.  
0 Rows replaced with new rows.  
postgres@8b0c41bd948e:/tmp/pg_bulkload$ psql -c "select count(*) from foo;"
count    
--------  
600000  
(1 row)  

postgres@8b0c41bd948e:/tmp/pg_bulkload$ pg_bulkload -i ./foo.csv -O foo -l test_foo.log -p 5432 -o "TYPE=csv" -o "DELIMITER=," -d postgres -U postgres  
NOTICE: BULK LOAD START  
NOTICE: BULK LOAD END  
0 Rows skipped.  
100000 Rows successfully loaded.  
0 Rows not loaded due to parse errors.  
0 Rows not loaded due to duplicate errors.  
0 Rows replaced with new rows.  
postgres@8b0c41bd948e:/tmp/pg_bulkload$ psql -c "select count(*) from foo;"
count    
--------  
700000  
(1 row)  

PolarDB 表大小缓存功能文档: https://apsaradb.github.io/PolarDB-for-PostgreSQL/zh/features/v11/performance/rel-size-cache.html

chatgpt 在解决问题过程中的使用 1

删除fname 字符串的file-dio://

如果你希望删除一个字符串char *fname前N个字节,并且得到一个新的字符串,可以通过以下步骤来实现:

  1. 确保N不超过原字符串的长度。
  2. 通过调整指针偏移来跳过前N个字节。
  3. 分配新的内存来存储剩余部分并复制数据。
  4. 确保新字符串以'\0'结束。

示例代码:

#include<stdio.h>  
#include<string.h>  
#include<stdlib.h>  

intmain(){  
char *fname = "Hello, World!";  
int N = 5;  // 删除前N个字节  

// 获取原始字符串的长度  
int len = strlen(fname);  

if (N >= len) {  
printf("删除的字节数大于或等于字符串长度,无法删除\n");  
return-1;  
   }  

// 创建一个新字符串来存储剩余的部分  
char *new_str = (char *)malloc(len - N + 1);  // +1 是为了存储 '\0'  

if (new_str == NULL) {  
printf("内存分配失败\n");  
return-1;  
   }  

// 使用 strncpy 拷贝从位置 N 开始的剩余部分  
strcpy(new_str, fname + N);  

// 输出新字符串  
printf("删除前 %d 个字节后的字符串: %s\n", N, new_str);  

// 释放动态分配的内存  
free(new_str);  

return0;  
}  

解释:

  1. 检查 N 的大小:首先,通过strlen(fname)获取原始字符串的长度,并确保N小于字符串的长度。如果N大于或等于字符串长度,直接输出错误信息,因为删除的字节数超过了字符串本身。

  2. 内存分配:使用malloc(len - N + 1)为新字符串分配内存。len - N是剩余字符串的长度,+1用于存储字符串结束符'\0'。

  3. 指针偏移:fname + N使得指针跳过字符串的前N个字节,指向剩余的部分。然后,使用strcpy将剩余的部分拷贝到新字符串new_str。

  4. 释放内存:free(new_str)在使用完动态分配的内存后释放。

注意事项:

  • 如果N超过了原字符串的长度,程序会提前返回并输出错误信息。
  • strcpy会复制以'\0'结尾的字符串,因此新字符串会自动终止。
  • malloc动态分配内存时,请确保内存分配成功。

chatgpt 在解决问题过程中的使用 2

ereport的使用

在 PostgreSQL 中,ereport是一个宏,用于生成错误报告和日志消息。它是 PostgreSQL 的内部机制之一,广泛用于数据库引擎的代码中,特别是在执行过程中出现错误时。

ereport的主要作用是根据日志的严重性级别生成错误消息,并且根据需要抛出异常。这个宏比标准的elog宏更为强大,能够处理不同的日志级别、格式化消息等。

ereport的基本语法:

ereport(log_level, (errcode(ERRCODE_...), errmsg("message")));  
  • log_level:指示错误的严重性级别。它通常是以下几种之一:

    • LOG:一般的信息日志。
    • INFO:信息性消息。
    • WARNING:警告消息。
    • ERROR:错误消息。
    • FATAL:致命错误,导致进程退出。
    • PANIC:非常严重的错误,通常会导致数据库进程崩溃。
  • errcode:错误代码,可以用errcode()宏来定义,通常是 SQL 错误代码。

  • errmsg:错误消息,是对错误的具体描述,通常为一个字符串。

示例:

ereport(ERROR,  
   (errcode(ERRCODE_DIVISION_BY_ZERO),  
    errmsg("division by zero")));  

这个例子表示当出现除以零错误时,生成一个错误消息并抛出异常。ERROR表示这是一个错误级别的消息,errmsg描述了错误的具体内容。

ereport的更多参数:

ereport可以接受更多的参数来提供额外的上下文信息,比如错误的位置、文件名等。这些参数被包装在errdetail、errhint、errcontext等字段中。

ereport(ERROR,  
   (errcode(ERRCODE_DIVISION_BY_ZERO),  
    errmsg("division by zero"),  
    errdetail("The denominator was zero."),  
    errhint("Check the divisor value before performing the division.")));  
  • errdetail:提供错误的详细信息。
  • errhint:提供修复错误的提示。
  • errcontext:给出发生错误的上下文,通常是函数或语句位置的详细信息。

常见错误代码(errcode):

errcode是一个宏,它定义了错误类型的 SQL 错误代码。以下是一些常见的错误代码:

  • ERRCODE_DIVISION_BY_ZERO:除以零错误。
  • ERRCODE_UNDEFINED_TABLE:表未定义。
  • ERRCODE_INVALID_PARAMETER_VALUE:无效的参数值。
  • ERRCODE_SYNTAX_ERROR:语法错误。
  • ERRCODE_INSUFFICIENT_PRIVILEGE:权限不足。
  • ERRCODE_DUPLICATE_OBJECT:重复的对象。

使用场景:

ereport经常出现在 PostgreSQL 的扩展、函数、触发器、内存管理以及多线程相关代码中。例如:

1. 检查条件并抛出错误:

if (some_value == NULL) {  
   ereport(ERROR,  
           (errmsg("some_value cannot be NULL")));  
}  

2. 捕获致命错误并退出:

ereport(FATAL,  
       (errmsg("This is a fatal error")));  

3. 错误级别日志记录:

ereport(LOG,  
       (errmsg("This is a log message")));  

错误处理的结构:

ereport会根据提供的日志级别,生成不同类型的输出。例如,ERROR会抛出一个异常,通常会导致函数返回并且会向客户端报告错误信息,而LOG或INFO类别的消息则不会影响数据库的执行,只是记录到日志中。

小结:

  • ereport是 PostgreSQL 内部用于处理错误、警告和日志的宏。
  • 它允许在错误发生时生成格式化的消息,并根据严重性级别抛出异常或记录日志。
  • 可以通过errcode、errmsg等参数定制错误消息的内容和上下文信息。
  • ereport适用于复杂的错误处理和日志记录,是 PostgreSQL 中非常重要的调试和错误报告工具。

如果你需要处理复杂的错误场景,ereport提供了更为灵活和全面的支持。

chatgpt 在解决问题过程中的使用 3

错误的使用pfree, 导致了数据库core

postgres@8b0c41bd948e:/tmp/pg_bulkload$ pg_bulkload -i ./foo.csv -O foo -l test_foo.log -p 5432 -o "TYPE=csv" -o "DELIMITER=," -d postgres -U postgres  

NOTICE: BULK LOAD START  
ERROR: query failed: server closed the connection unexpectedly  
This probably means the server terminated abnormally  
before or while processing the request.  
DETAIL: query was: SELECT * FROM pgbulkload.pg_bulkload($1)  

查询core文件位置

postgres=# select * from pg_settings where name ~ 'core';  
                 name                  | setting | unit |      category      |            short_desc             |        extra_desc        | context | vartype | source  | min_val | max_val | enumvals  
| boot_val | reset_val | sourcefile | sourceline | pending_restart  
----------------------------------------+---------+------+--------------------+-----------------------------------+--------------------------+---------+---------+---------+---------+---------+----------  
+----------+-----------+------------+------------+-----------------  
polar_worker.core_file_outdate_time    | -1      |      | Customized Options | outdate time of core file.        |                          | sighup  | integer | default | -1      | 2147483 |            
| -1       | -1        |            |            | f  
polar_worker.core_file_path            | .       |      | Customized Options | path of core file                 | path of core file        | sighup  | string  | default |         |         |            
| .        | .         |            |            | f  
polar_worker.core_name_suffix          | core    |      | Customized Options | To assign corefile name.          | To assign corefile name. | sighup  | string  | default |         |         |            
| core     | core      |            |            | f  
polar_worker.num_corefile_reserved_new | 32      |      | Customized Options | num of newest reserverd corefile. |                          | sighup  | integer | default | 0       | 2147483 |            
| 32       | 32        |            |            | f  
polar_worker.num_corefile_reserved_old | 32      |      | Customized Options | num of oldest reserverd corefile. |                          | sighup  | integer | default | 0       | 2147483 |            
| 32       | 32        |            |            | f  
(5 rows)  

打印core文件backtrace

$ cd /home/postgres/tmp_master_dir_polardb_pg_1100_bld  
$ ll  
-rw------- 1 postgres postgres 4669796352 Dec 10 10:42 core  


$ gdb -h  
This is the GNU debugger.  Usage:  

   gdb [options] [executable-file [core-file or process-id]]  
   gdb [options] --args executable-file [inferior-arguments ...]  

# 使用PolarDB当前二进制文件解释core文件  
$ gdb --exec=/home/postgres/tmp_basedir_polardb_pg_1100_bld/bin/postgres --core=core  

GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1  
Copyright (C) 2022 Free Software Foundation, Inc.  
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>  
This is free software: you are free to change and redistribute it.  
There is NO WARRANTY, to the extent permitted by law.  
Type "show copying" and "show warranty"for details.  
This GDB was configured as "aarch64-linux-gnu".  
Type "show configuration"for configuration details.  
For bug reporting instructions, please see:  
<https://www.gnu.org/software/gdb/bugs/>.  
Find the GDB manual and other documentation resources online at:  
   <http://www.gnu.org/software/gdb/documentation/>.  

For help, type"help".  
Type "apropos word" to search for commands related to "word"...  
Reading symbols from /home/postgres/tmp_basedir_polardb_pg_1100_bld/bin/postgres...  

warning: Can't open file /dev/zero (deleted) during file-backed mapping note processing  

warning: Can'
t open file /dev/shm/PostgreSQL.442248051 during file-backed mapping note processing  

warning: Can't open file /SYSV0052e2c1 (deleted) during file-backed mapping note processing  
[New LWP 19269]  
[Thread debugging using libthread_db enabled]  
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".  
Core was generated by `postgres(5432): postgres postgres 127.0.0.1(36554)SELECT                      '
.  
Program terminated with signal SIGSEGV, Segmentation fault.  
#0  __pthread_kill_implementation (threadid=281473146004608, signo=signo@entry=11, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44  
44 ./nptl/pthread_kill.c: No such file or directory.  
(gdb) bt  
#0  __pthread_kill_implementation (threadid=281473146004608, signo=signo@entry=11, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44  
#1  0x0000ffff9216f254 in __pthread_kill_internal (signo=11, threadid=<optimized out>) at ./nptl/pthread_kill.c:78  
#2  0x0000ffff9212a67c in __GI_raise (sig=11) at ../sysdeps/posix/raise.c:26  
#3  <signal handler called>  
#4  pfree (pointer=0xaaaae5844500) at mcxt.c:1231  
#5  0x0000fffe7aa17538 in open_data_file (blknum=<optimized out>, istemp=<optimized out>, rnode=...) at writer_direct.c:723  
#6  flush_pages (loader=0xaaaae584a410) at writer_direct.c:580  
#7  0x0000fffe7aa17bd4 in DirectWriterClose (self=0xaaaae584a410, onError=false) at writer_direct.c:341  
#8  0x0000fffe7aa10540 in pg_bulkload (fcinfo=0xfffff7968830) at pg_bulkload.c:304  
#9  0x0000aaaacb117a38 in ExecMakeTableFunctionResult (setexpr=0xaaaae5847d50, econtext=0xaaaae5847880, argContext=<optimized out>, expectedDesc=0xaaaae584dc60, randomAccess=false) at execSRF.c:231  
#10 0x0000aaaacb124d24 in FunctionNext (node=0xaaaae5847768) at nodeFunctionscan.c:94  
#11 0x0000aaaacb10bcf0 in ExecProcNode (node=<optimized out>) at ../../../src/include/executor/executor.h:288  
#12 ExecProcNode (node=0xaaaae5847768) at ../../../src/include/executor/executor.h:279  
#13 ExecutePlan (estate=estate@entry=0xaaaae5847550, planstate=0xaaaae5847768, use_parallel_mode=<optimized out>, operation=operation@entry=CMD_SELECT, sendTuples=sendTuples@entry=true,  
   numberTuples=numberTuples@entry=0, direction=direction@entry=ForwardScanDirection, dest=dest@entry=0xaaaae56bfa88, execute_once=execute_once@entry=true) at execMain.c:1863  
#14 0x0000aaaacb10c55c in standard_ExecutorRun_NonPX (execute_once=true, count=0, direction=ForwardScanDirection, queryDesc=0xaaaae5846c10) at execMain.c:427  
#15 standard_ExecutorRun (queryDesc=0xaaaae5846c10, direction=ForwardScanDirection, count=0, execute_once=<optimized out>) at execMain.c:367  
#16 0x0000ffff8ed63228 in pgss_ExecutorRun (queryDesc=0xaaaae5846c10, direction=ForwardScanDirection, count=0, execute_once=true) at pg_stat_statements.c:932  
#17 0x0000ffff8ed42934 in polar_stat_ExecutorRun (queryDesc=0xaaaae5846c10, direction=ForwardScanDirection, count=0, execute_once=true) at polar_stat_sql.c:1128  
#18 0x0000ffff8ece1834 in explain_ExecutorRun (queryDesc=0xaaaae5846c10, direction=ForwardScanDirection, count=0, execute_once=true) at auto_explain.c:299  
#19 0x0000aaaacb2efa0c in PortalRunSelect (portal=portal@entry=0xaaaae57ded70, forward=forward@entry=true, count=0, count@entry=9223372036854775807, dest=dest@entry=0xaaaae56bfa88) at pquery.c:1020  
#20 0x0000aaaacb2f1358 in PortalRun (portal=portal@entry=0xaaaae57ded70, count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true, run_once=<optimized out>,  
   dest=dest@entry=0xaaaae56bfa88, altdest=altdest@entry=0xaaaae56bfa88, completionTag=completionTag@entry=0xfffff7969568 "") at pquery.c:858  
#21 0x0000aaaacb2e9d2c in exec_execute_message (portal_name=0xaaaae56bf670 "", max_rows=9223372036854775807) at postgres.c:2232  
#22 0x0000aaaacb2ed0a0 in PostgresMain (argc=<optimized out>, argv=argv@entry=0xaaaae579a7f0, dbname=<optimized out>, username=<optimized out>) at postgres.c:4757  
#23 0x0000aaaacb22d72c in BackendRun (port=0xaaaae578c710) at postmaster.c:5481  
#24 BackendStartup (port=port@entry=0xaaaae578c710) at postmaster.c:5114  
#25 0x0000aaaacb22db6c in ServerLoop () at postmaster.c:1948  
#26 0x0000aaaacb22f62c in PostmasterMain (argc=argc@entry=3, argv=argv@entry=0xaaaae56b9cd0) at postmaster.c:1535  
#27 0x0000aaaacaf10a0c in main (argc=3, argv=0xaaaae56b9cd0) at main.c:235  
(gdb) quit  

分析以上backtrace, 可以看到是在修改pg_bulkload: lib/writer_direct.c后导致的,

pg_name没有使用palloc申请内存, 但是使用了pfree(pg_name)释放内存, 所以core了:

#3  <signal handler called>  
#4  pfree (pointer=0xaaaae5844500) at mcxt.c:1231  

直接丢给chatgpt, 一下就解释通了.

在 PostgreSQL 中,free和pfree都是用来释放内存的函数,但它们有不同的实现和适用场景。pfree是 PostgreSQL 内部的内存释放函数,而free是标准 C 库的内存释放函数。如果你在 PostgreSQL 的上下文中使用了pfree来释放内存,你应该确保你没有混合使用 C 标准库的free和 PostgreSQL 的pfree。这通常是问题的根源。

1. 错误分析

错误信息中的#3 <signal handler called>和#4 pfree (pointer=0xaaaae5844500) at mcxt.c:1231指示问题发生在尝试释放内存时,且是在 PostgreSQL 内部的内存管理模块(如mcxt.c)中触发的。

这是因为 PostgreSQL 内存管理系统使用了自己的一套内存池和上下文(memory context)。如果你在 PostgreSQL 的内存上下文内使用了free来释放内存,而不是使用pfree,可能会导致内存管理系统的冲突,进而引发错误。

2.pfree和free的区别

  • **free**:标准 C 库的函数,适用于动态分配的内存(如malloc或calloc)释放。
  • **pfree**:PostgreSQL 提供的内存释放函数,专门用于释放由 PostgreSQL 内存管理系统分配的内存(通常是palloc、MemoryContextAlloc等函数分配的内存)。

PostgreSQL 使用自定义的内存管理机制,将内存划分为不同的内存上下文。每个内存上下文都有一个专门的内存池,所有的内存分配和释放都需要通过这个机制来进行管理。

3. 如何解决问题

确保在 PostgreSQL 内部使用pfree来释放内存,而不是free。如果你通过 PostgreSQL 的内存管理系统(如palloc、MemoryContextAlloc等)分配了内存,那么应该使用pfree来释放。

示例代码:

#include"postgres.h"
#include"utils/elog.h"
#include"utils/memutils.h"

voidexample_function(){  
// 在 PostgreSQL 内存上下文中分配内存  
char *new_str = (char *)palloc(100 * sizeof(char));  // 使用 palloc 分配内存  

// 使用 new_str 进行操作...  

// 正确释放内存  
   pfree(new_str);  // 使用 pfree 释放内存  
}  

4. 常见的错误做法(避免)

#include<stdlib.h>  // 错误的地方:使用标准库的 free  

voidexample_function(){  
char *new_str = (char *)malloc(100 * sizeof(char));  // 使用 malloc 分配内存  

// 使用 new_str 进行操作...  

// 错误的释放方式:不使用 pfree,而是使用 free  
free(new_str);  // 错误:在 PostgreSQL 内存管理环境中,应该使用 pfree  
}  

5. 使用pfree时的注意事项

  • 内存上下文:pfree适用于通过 PostgreSQL 内存管理机制分配的内存。确保你使用palloc或相关函数分配内存,并通过pfree来释放。

  • 调试:如果你在内存管理时遇到问题,可以启用 PostgreSQL 的内存调试功能,查看内存上下文的分配和释放情况,或者查阅 PostgreSQL 的日志,了解具体错误原因。

总结

  • 问题的根源:你可能在 PostgreSQL 内存上下文中使用了free来释放内存,而不是pfree。
  • 解决方法:如果你使用 PostgreSQL 内存管理函数(如palloc),请确保使用pfree来释放内存,而不是free。
  • 调试和错误报告:使用pfree可以避免因内存上下文管理错误而导致的崩溃。

参考

https://ossc-db.github.io/pg_bulkload/pg_bulkload.html

https://www.modb.pro/db/1754101551677394944

修改后的pg_bulkload放到了这个分支:

  • https://github.com/digoal/pg_bulkload/tree/polardb_v11

PolarDB 表大小缓存功能文档:

  • https://apsaradb.github.io/PolarDB-for-PostgreSQL/zh/features/v11/performance/rel-size-cache.html

文末彩蛋:国产数据库周边生态

当然一款数据库要流行起来, 除了自己要强大, 还离不开生态. 用好周边生态工具, 管理水平战胜90%老司机!!! 下面简单介绍一下国产数据库周边生态.

1、管控软件

鸣嵩(前阿里云数据库总经理 / 研究员)等大佬们创业创办的云猿生, 核心产品是KubeBlocks. 他们的理念是让管理数据库和搭积木一样简单, 如果你要管理很多套并且种类(OLTP\OLAP\NoSQL\KV\TS\MQ等)很多的数据库产品, 推荐首选.

  • https://github.com/apecloud/kubeblocks

PG中文社区核心委员唐成老师的公司乘数开源的Clup, 专用管理PostgreSQL和PolarDB的集群管理软件, 如果你要管理很多套数据库, 推荐选择. 并且Clup还提供了企业版、自研的连接池、分布式存储、一体机、备份平台等, 是企业用户推荐之选.

  • https://www.csudata.com/

若航老司机开源的pigsty, 集成了300多个PG插件的PG集群和PolarDB集群管理软件, 如果你要管理很多套PG或PolarDB数据库, 且对插件有特别多的需求, 推荐选择.

  • https://pigsty.cc/zh/

2、审计监控诊断优化

翟总(曾经是我背后的男人)到海信聚好看后研发的 DBdoctor, 采用ebpf技术, 在对数据库几乎没有影响的情况下实时监控数据库和服务器的各项指标, 发现和诊断问题根因非常方便.

  • https://www.dbdoctor.cn/

天舟老哥的核心产品Bytebase 是位于您和数据库之间的中间件。它是数据库 DevOps 的 GitLab/GitHub,专为开发人员、DBA 和平台工程师打造。

  • https://bytebase.cc/docs/introduction/what-is-bytebase/

PawSQL, SQL优化和诊断产品.  

D-Smart, Oracle老前辈白老大出品, 专注企业级市场, 将业界顶级DBA经验的产品化作品, 产品功能包括数据库监控、诊断、优化等.

  • https://www.modb.pro/db/567140

3、国产数据库IDE

IDE是开发者的必备工具,例如社区有pgAdmin, 国产IDE则可以看看老程序猿达刚老师的DeskUI:

  • https://www.deskui.com

4、数据同步&迁移&备份恢复

NineData, 老领导出去创业做的产品, 产品涵盖了数据同步、迁移、备份、比对、devops、chatDBA等.

  • https://www.ninedata.cloud/home

DSG, 非常老牌的数据库同步迁移企业级产品, 支持各种数据库的异构和同构迁移, 用他们的话说, 没有dsg搞不定的迁移, 比goldengate还牛.

  • https://www.dsgdata.com/

公开课

如果你对PolarDB学习感兴趣可以阅读这个公开课系列:

除了PolarDB还非常值得关注的几款PG栈国产数据库:

  • HaloDB(基于PG兼容PostgreSQL、Oracle、MySQL. http://www.halodbtech.com/ )、
  • IvorySQL(基于开源PG兼容PG、Oracle. https://www.ivorysql.org/zh-cn/ )、
  • ProtonBase(云原生分布式数仓. https://protonbase.com/ )、
  • 成都文武数据库(https://ww-it.cn)

参考文档点击阅读原文获得


感谢关注我的github (https://github.com/digoal/blog) 及视频号:

Image