pg_orphaned扩展分析(一)

简介: pg_orphaned扩展是用于维护PostgreSQL孤儿文件的扩展,通过分析学习了查找孤儿文件的方法,同时还将学习在PostgreSQL后端(backend)如何查找指定表/视图、如何创建cache、如何使用hash表、如何使用List、如何使用正则表达式、C语言扩展如何返回结果集。

pg_orphaned扩展是用于维护PostgreSQL孤儿文件的扩展,通过分析学习了查找孤儿文件的方法,同时还将学习在PostgreSQL后端(backend)如何查找指定表/视图、如何创建cache、如何使用hash表、如何使用List、如何使用正则表达式、C语言扩展如何返回结果集。

有关孤儿文件的内容可参考:


pg_orphaned扩展实现了pg_list_orphanedpg_list_orphaned_movedpg_move_orphanedpg_remove_moved_orphanedpg_move_back_orphaned 五个函数:

CREATE FUNCTION pg_list_orphaned(  older_than interval default null,  OUT dbname text,  OUT path text,  OUT name text,  OUT size bigint,  OUT mod_time timestamptz,  OUT relfilenode bigint,  OUT reloid bigint,  OUT older bool)RETURNS SETOF RECORD
AS'MODULE_PATHNAME','pg_list_orphaned'LANGUAGE C VOLATILE;CREATE FUNCTION pg_list_orphaned_moved(  OUT dbname text,  OUT path text,  OUT name text,  OUT size bigint,  OUT mod_time timestamptz,  OUT relfilenode bigint,  OUT reloid bigint)RETURNS SETOF RECORD
AS'MODULE_PATHNAME','pg_list_orphaned_moved'LANGUAGE C VOLATILE;CREATE FUNCTION pg_move_orphaned(older_than interval default null)    RETURNS int    LANGUAGE c
AS'MODULE_PATHNAME','pg_move_orphaned';CREATE FUNCTION pg_remove_moved_orphaned()    RETURNS void
    LANGUAGE c
AS'MODULE_PATHNAME','pg_remove_moved_orphaned';CREATE FUNCTION pg_move_back_orphaned()    RETURNS int    LANGUAGE c
AS'MODULE_PATHNAME','pg_move_back_orphaned';revoke execute on function pg_list_orphaned(older_than interval)from public;revoke execute on function pg_list_orphaned_moved()from public;revoke execute on function pg_move_orphaned(older_than interval)from public;revoke execute on function pg_remove_moved_orphaned()from public;revoke execute on function pg_move_back_orphaned()from public;

pg_list_orphaned

pg_list_orphaned函数是整个扩展的核心,返回孤儿文件列表。查找孤儿文件的原理是通过遍历pg缺省表空间目录以及PG_DATA/pg_tblspc目录下的文件(仅查找全数字文件、临时表文件),并逐一在pg_class中查找该条目(通过reltablespace和relfilenode),如果pg_class中不存在该条目,则认定该文件是孤儿文件,查找过程通过 search_orphaned 函数实现。核心函数是RelidByRelfilenodeDirty

search_orphaned

/* 忽略文件名非数字的文件 */if (strstr(de->d_name, "_") ==NULL&&isdigit((unsignedchar) *(de->d_name))) {
orph=palloc(sizeof(*orph));
relfilename=strdup(de->d_name);
relfilenode= (Oid) strtoul(relfilename, &relfilename, 10);
/* 如果RelidByRelfilenodeDirty没有返回有效的oid* 我们就认为此文件是个孤儿文件*/oidrel=RelidByRelfilenodeDirty(reltablespace, relfilenode);
/*           * 如果是指定relation的第一个segment文件、大小为0并且是在上一次checkpoint之后创建的文件* 我们过滤之并不做为孤儿文件上报* due to https://github.com/postgres/postgres/blob/REL_12_8/src/backend/storage/smgr/md.c#L225*/segment_time=time_t_to_timestamptz(attrib.st_mtime);
if (!OidIsValid(oidrel) &&!(attrib.st_size==0&&strstr(de->d_name, ".") ==NULL&&segment_time>last_checkpoint_time))
            {
orph->dbname=strdup(dbname);
orph->path=strdup(dir);
orph->name=strdup(de->d_name);
orph->size= (int64) attrib.st_size;
orph->mod_time=segment_time;
orph->relfilenode=relfilenode;
orph->reloid=oidrel;
*flist=lappend(*flist, orph);
/* search for _init and _fsm */if(strstr(de->d_name, ".") ==NULL)
pgorph_add_suffix(flist, orph);
            }       
        }

last_checkpoint_time在pg_build_orphaned_list函数中获取:

/* get a copy of the control file */#if PG_VERSION_NUM >= 120000ControlFile=get_controlfile(".", &crc_ok);
#elseControlFile=get_controlfile(".", NULL, &crc_ok);
#endifif (!crc_ok)
ereport(ERROR,(errmsg("pg_control CRC value is incorrect")));
/* get last checkpoint time */time_tmp= (time_t) ControlFile->checkPointCopy.time;
last_checkpoint_time=time_t_to_timestamptz(time_tmp);

RelidByRelfilenodeDirty

/** Map a relation's (tablespace, filenode) to a relation's oid and cache the* result.** This is the same as the existing RelidByRelfilenode in relfilenodemap.c but* it is done by using a DirtySnapshot as we want to see relation being created.** Returns InvalidOid if no relation matching the criteria could be found.*/OidRelidByRelfilenodeDirty(Oidreltablespace, Oidrelfilenode){...}

RelidByRelfilenodeDirty是核心函数,入口参数reltablespace、relfilenode,分别是表空间oid跟文件oid,首先在cache中查找,如果cache中没有,则去pg_class中找,没找到,则说明是孤儿文件,加入cache中,关键代码如下:

else    {
/** Not a shared table, could either be a plain relation or a* non-shared, nailed one, like e.g. pg_class.*//* check for plain relations by looking in pg_class */#if PG_VERSION_NUM >= 120000/** RelationRelationId在pg_class_d.h中定义* 值为1259,pg_class的OID* 所以这里打开pg_class*/relation=table_open(RelationRelationId, AccessShareLock);
#elserelation=heap_open(RelationRelationId, AccessShareLock);
#endif/* copy scankey to local copy, it will be modified during the scan *//* relfilenode_skey_dirty的解释见下文 */memcpy(skey, relfilenode_skey_dirty, sizeof(skey));
/* 设置扫描参数 */skey[0].sk_argument=ObjectIdGetDatum(reltablespace);
skey[1].sk_argument=ObjectIdGetDatum(relfilenode);
scandesc=systable_beginscan(relation,
ClassTblspcRelfilenodeIndexId,
true,
&DirtySnapshot,
2,
skey);
found=false;
while (HeapTupleIsValid(ntp=systable_getnext(scandesc)))
        {
#if PG_VERSION_NUM >= 120000Form_pg_classclassform= (Form_pg_class) GETSTRUCT(ntp);
found=true;
Assert(classform->reltablespace==reltablespace);
Assert(classform->relfilenode==relfilenode);
relid=classform->oid;
#elsefound=true;
relid=HeapTupleGetOid(ntp);
#endif        }
systable_endscan(scandesc);
#if PG_VERSION_NUM >= 120000table_close(relation, AccessShareLock);
#elseheap_close(relation, AccessShareLock);
#endif/* check for tables that are mapped but not shared */if (!found)
#if PG_VERSION_NUM >= 160000relid=RelationMapFilenumberToOid(relfilenode, false);
#elserelid=RelationMapFilenodeToOid(relfilenode, false);
#endif    }
/** Only enter entry into cache now, our opening of pg_class could have* caused cache invalidations to be executed which would have deleted a* new entry if we had entered it above.*/entry=hash_search(RelfilenodeMapHashDirty, (void*) &key, HASH_ENTER, &found);
if (found)
elog(ERROR, "corrupted hashtable");
entry->relid=relid;

InitializeRelfilenodeMapDirty

relfilenode_skey_dirty是个全局变是,并在InitializeRelfilenodeMapDirty函数中初始化:

/** Initialize cache, either on first use or after a reset.* Same as InitializeRelfilenodeMap in relfilenodemap.c*/staticvoidInitializeRelfilenodeMapDirty(void)
{
HASHCTLctl;
inti;
/* 确保我们已经初始化了CacheMemoryContext. */if (CacheMemoryContext==NULL)
CreateCacheMemoryContext();
/* 构造skey */MemSet(&relfilenode_skey_dirty, 0, sizeof(relfilenode_skey_dirty));
/* 我们搜索pg_class时使用了两个键值,tablespace和filenode,所以这里是2 */for (i=0; i<2; i++)
    {
/* 填充FmgrInfo结构 */fmgr_info_cxt(F_OIDEQ,
&relfilenode_skey_dirty[i].sk_func,
CacheMemoryContext);
/* 使用Btree索引相等策略 */relfilenode_skey_dirty[i].sk_strategy=BTEqualStrategyNumber;
relfilenode_skey_dirty[i].sk_subtype=InvalidOid;
relfilenode_skey_dirty[i].sk_collation=InvalidOid;
    }
/* 设置查找键值 */relfilenode_skey_dirty[0].sk_attno=Anum_pg_class_reltablespace;
relfilenode_skey_dirty[1].sk_attno=Anum_pg_class_relfilenode;
/* 初始化hash表 */MemSet(&ctl, 0, sizeof(ctl));
ctl.keysize=sizeof(RelfilenodeMapKeyDirty);
ctl.entrysize=sizeof(RelfilenodeMapEntryDirty);
/* hash表位于CacheMemoryContext */ctl.hcxt=CacheMemoryContext;
/** Only create the RelfilenodeMapHashDirty now, so we don't end up partially* initialized when fmgr_info_cxt() above ERRORs out with an out of memory* error.* Note that the hash table is not created in shared memory but in* private memory.*/RelfilenodeMapHashDirty=hash_create("RelfilenodeMap cache", 64, &ctl,
HASH_ELEM|HASH_BLOBS|HASH_CONTEXT);
/* Watch for invalidation events. */CacheRegisterRelcacheCallback(RelfilenodeMapInvalidateCallbackDirty,
                                    (Datum) 0);
}

这里hash表用到了RelfilenodeMapKeyDirty、RelfilenodeMapEntryDirty两个结构,分别定义如下:

typedefstruct{
Oidreltablespace;
Oidrelfilenode;
} RelfilenodeMapKeyDirty;
typedefstruct{
RelfilenodeMapKeyDirtykey;          /* lookup key - must be first */Oidrelid;                  /* pg_class.oid */} RelfilenodeMapEntryDirty;

后端hash表的使用

pg_orphaned扩展使用pg后端的hash表实现cache,这里简单总结下如何使用后端提供的hash函数。相关代码在源码的src/backend/utils/hash/dynahash.c中:

HTAB*hash_create(constchar*tabname, longnelem, constHASHCTL*info, intflags);
void*hash_search(HTAB*hashp,
constvoid*keyPtr,
HASHACTIONaction,
bool*foundPtr)

hash_create的flag参数常用的值有:

HASH_ELEM - 必须包含此值

HASH_STRINGSHASH_BLOBSHASH_FUNCTION - 三都必须包含其一

HASH_CONTEXT - 如果包含此值,表示将hash表分配到info->hcxt指定的内存上下文中,缺省分配hash表到TopMemoryContext


hash_search的HASHACTION常用的值有:

*      HASH_FIND: 在表中查找key

*      HASH_ENTER: 在表中查找key,如果key不存在则创建

*      HASH_ENTER_NULL: 同上,如果内存不足返回NULL

*      HASH_REMOVE: 从表中删除指定key


用法见下面的代码片断:

/* Hash table for information about each relfilenode <-> oid pair */staticHTAB*RelfilenodeMapHashDirty=NULL;
HASHCTLctl;
/* 初始化hash表 */MemSet(&ctl, 0, sizeof(ctl));
ctl.keysize=sizeof(RelfilenodeMapKeyDirty);
ctl.entrysize=sizeof(RelfilenodeMapEntryDirty);
/* hash表位于CacheMemoryContext */ctl.hcxt=CacheMemoryContext;
RelfilenodeMapHashDirty=hash_create("RelfilenodeMap cache", 64, &ctl,
HASH_ELEM|HASH_BLOBS|HASH_CONTEXT);
/* hash_search */RelfilenodeMapEntryDirty*entry;
entry=hash_search(RelfilenodeMapHashDirty, (void*) &key, HASH_FIND, &found);



相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
存储 NoSQL 关系型数据库
PostgreSQL列存扩展hydra简单测试
Hydra是一款PostgreSQL的扩展,为PostgreSQL增加了列存引擎,使得PostgreSQL的olap性能大幅提升,本文介绍Hydra基本的使用方法。
|
关系型数据库 PostgreSQL
PostgreSQL pg_orphaned扩展
由于种种原因,PostgreSQL可能会产生一些孤儿文件,这些文件会占用磁盘空间,手工查找费时费力还容易出错,pg_orphaned扩展很好的解决了这个问题。
|
9月前
|
安全 Unix Linux
VMware Workstation 17.6.3 发布下载,现在完全免费无论个人还是商业用途
VMware Workstation 17.6.3 发布下载,现在完全免费无论个人还是商业用途
76795 65
|
搜索推荐 机器人 云计算
纳米机器人:医疗领域的微型革命与精准治疗
【9月更文挑战第16天】随着科技的飞速发展,纳米技术成为推动多个领域变革的重要力量。在医疗领域,纳米机器人以其独特优势引领着微型革命与精准治疗新时代。本文探讨其在药物输送、癌症治疗、手术辅助及疾病诊断中的应用,并分析其小型化、精准化、智能化与综合化的优势。尽管面临制造技术、体内控制等挑战,但随着科技的进步,纳米机器人有望成为人类健康的重要保障。
958 10
|
tengine 负载均衡 应用服务中间件
Nginx+Keepalived高可用集群部署详细文档
Nginx+Keepalived高可用集群部署详细文档
778 0
|
存储 关系型数据库 数据库
PostgreSQL孤儿文件
与所有其他关系数据库系统一样,PostgreSQL需要通过写入wal日志或在Checkpoint时同步数据到数据文件来持久化数据到磁盘上。对于数据文件,一旦Relation达到SEGMENT_SIZE(默认1GB),PostgreSQL就会创建一个新的数据文件。因此如果Relation持续增长,则该Relation可能会由多个文件组成。在这篇文章中想要考虑的问题是,是否可能存在孤儿文件。
confluence使用Markdown编辑器
confluence使用Markdown编辑器
890 0
|
敏捷开发 运维 数据可视化
软件工程基础知识总结
简单来说就是多人参与、有计划有步骤的构造一个符合质量标准的软件产品,这个过程称之为软件工程。我们都知道,参与人越多、产品越复杂、流程越繁琐,最终构造的软件产品就越可能出现问题。
软件工程基础知识总结
|
图形学
【Three.js入门】纹理加载进度、环境贴图、经纬线映射贴图与高动态范围成像HDR
【Three.js入门】纹理加载进度、环境贴图、经纬线映射贴图与高动态范围成像HDR
826 0
|
人工智能 Python
Python 蓝桥杯 动态规划 2道例题+配套1道历年真题
Python 蓝桥杯 动态规划 2道例题+配套1道历年真题
280 0
Python 蓝桥杯 动态规划 2道例题+配套1道历年真题