pg_orphaned扩展分析(一)

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: 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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
3月前
|
监控 Oracle 关系型数据库
"深度剖析:Oracle SGA大小调整策略——从组件解析到动态优化,打造高效数据库性能"
【8月更文挑战第9天】在Oracle数据库性能优化中,系统全局区(SGA)的大小调整至关重要。SGA作为一组共享内存区域,直接影响数据库处理能力和响应速度。本文通过问答形式介绍SGA调整策略:包括SGA的组成(如数据缓冲区、共享池等),如何根据负载与物理内存确定初始大小,手动调整SGA的方法(如使用`ALTER SYSTEM`命令),以及利用自动内存管理(AMM)特性实现智能调整。调整过程中需注意监控与测试,确保稳定性和性能。
294 2
|
6月前
|
SQL 关系型数据库 数据库
一文熟悉PolarDB-PG 分区表核心特性
在 PolarDB-PG 数据库中,分区表 (Partitioned Table) 使您能够将非常大的表分解为更小且更易于管理的部分,这个部分称为分区 (Partition) 。 每个分区都是一个独立的对象,具有自己的名称和可选的存储特性。本文首先简单的介绍了分区表策略以及它的优势特点,然后介绍了PolarDB-PG 分区表支持的查询优化特性,最后介绍了分区表上的本地索引和全局索引,从而帮助用户对PolarDB-PG 分区表有一个全面的了解。
|
6月前
|
关系型数据库 C语言 PostgreSQL
pg_orphaned扩展分析(二)
pg_orphaned扩展是用于维护PostgreSQL孤儿文件的扩展,通过分析学习了查找孤儿文件的方法,同时还将学习在PostgreSQL后端(backend)如何查找指定表/视图、如何创建cache、如何使用hash表、如何使用List、如何使用正则表达式、C语言扩展如何返回结果集。
|
SQL 关系型数据库
[翻译]利用pg_stat_statments分析业务瓶颈
[翻译]利用pg_stat_statments分析业务瓶颈
106 0
|
SQL 监控 网络协议
优化PG查询:一问一答
优化PG查询:一问一答
126 0
|
Oracle 安全 关系型数据库
|
SQL 弹性计算 关系型数据库
PostgreSQL 12 preview - CTE 增强,支持用户语法层控制 materialized 优化
标签 PostgreSQL , CTE , materialized , not materialized , push down 背景 PostgreSQL with 语法,能跑非常复杂的SQL逻辑,包括递归,多语句物化计算等。 在12以前的版本中,WITH中的每一个CTE(common table express),都是直接进行物化的,也就是说外层的条件不会推到CTE(物化节点)里
1000 0
|
6月前
|
存储 并行计算 关系型数据库
PolarDB 开源版通过pg_rational插件支持Stern-Brocot trees , 实现高效自定义顺序和调整顺序需求
背景PolarDB 的云原生存算分离架构, 具备低廉的数据存储、高效扩展弹性、高速多机并行计算能力、高速数据搜索和处理; PolarDB与计算算法结合, 将实现双剑合璧, 推动业务数据的价值产出, 将数据变成生产力.本文将介绍PolarDB 开源版通过pg_rational插件支持Stern-Bro...
94 0
|
SQL 存储 关系型数据库
【DB吐槽大会】第61期 - PG 审计功能有巨大增强空间
大家好,这里是DB吐槽大会,第61期 - PG 审计功能有巨大增强空间
|
关系型数据库 Shell 调度