postgresql中如何新增加一个SysCache

    xiaoxiao2021-03-25  114

    如何增加一个SysCache

    如果你阅读过Postgres源码,就肯定会看到这样的用法:GetSysCacheOid1SearchSysCache1之类的,望文生义,显然是通过系统缓存查找某些东西,如果一些常用的数据存放在内存中,肯定能极大的提高查找效率。

    SysCache中缓存的是什么东西?

        元数据,也就是系统表中数据。经常会用到的系统表,都有会缓存起来,提高性能,是不是每个系统表都有cache没仔细核查过。例如pg_class表中数据,第一次查某表,需求从磁盘上加载,第二次就直接从syscache中找到,但这只是表的属性信息,并非表的内容,如select *fromt1;  从t1这个名字找oid的过程可以从cache中找到,但t1的内容不行,t1在内容在另外的buffer中,而且该内容的share_buf是全局的,而元数据的cache是局部的。

        另外,syscahce中缓存的对象单位是tupple,或者说叫元组,通俗点说就是行,按行缓存,对普通表内容是按页缓存在buf中。

    SysCache是怎么找的?

        来看一个例子:

    Select *from t1;

    内部会通过t1从Search_path找Schema name 去一起找这个表所在oid,有这oid才能找到表数据,找这个oid就会用到syscache,会走到如下接口:

    Oid

    get_relname_relid(constchar *relname, Oid relnamespace)    //通过schema + name找oid

    {

        return GetSysCacheOid2(RELNAMENSP,

                               PointerGetDatum(relname),

                               ObjectIdGetDatum(relnamespace));

    }

    然后会进入下面函数:

    Oid

    GetSysCacheOid(intcacheId,    //对应上面的 RELNAMENSP

                  Datum key1,    //对面上面的relname

                  Datum key2,   //对面上面的relnamespace

                  Datum key3,

                  Datum key4)

    {

        HeapTuple   tuple;

        Oid         result;

     

        tuple = SearchSysCache(cacheId, key1, key2, key3,key4);   //从cache中找到tuple

        if (!HeapTupleIsValid(tuple))

            return InvalidOid;

        result = HeapTupleGetOid(tuple);           //取出元组中oid即可

        ReleaseSysCache(tuple);

        return result;

    }

     

    对于一个表来说,如果确定了模式名和表名,那显然能唯一确定一个表,对上面函数来说,RELNAMENSP已经确定了是从哪个系统表中找,并且也确定了唯一索引是什么,这个唯一索引有两个索引键,是relname和relnamespace,那么就能根据它们找出一个唯一tuple出来。

    SearchCatCache函数逻辑相对复杂,不过多深入,大致流程介绍下:首先会按hash的方式去cache中找,通过hash计算出一个位置,因为hash算法不是唯一的,所以找到的是一个链表,然后遍历这个链表找到最终值,如果要缓存的元组相对较多,建议桶多搞点,毕竟现在memory is cheap,空间换时间合算。能找到并且没有失效就返回,找不到则打开系统表去找,找到后挂到cache上并返回,下次可直接找到。这里的链表是一个有意思的结构,存的并不是直接的tuple数据,而是一个包含有这个tuple数据的一个CatCTup对象的另外一个成员的地址。CatCTup也是一个有趣的结构,内部的 CT_MAGIC宏很魔幻。

    如何增加一个SysCache?

    四个要点:

    1.   在SysCacheIdentifier中增加一个枚举值

    2.   在cacheinfo数组增加一个元素

    3.   在indexing.h中增加一个唯一索引

    4.   在修改元组的地方更新。

    实例分析:

    下面以一个postgres10的分区增加的syscache作为具体的例子来详细介绍各个细节。

    对于分区系统表pg_partitioned_table,在SysCacheIdentifier增加了枚举PARTRELID,注意它的位置,是排序的,名字自己取,注意望文生义即可。

    在cacheinfo数组中,增加了元素

    {PartitionedRelationId,    /* PARTRELID */

            PartitionedRelidIndexId,

            1,

            {

                Anum_pg_partitioned_table_partrelid,

                0,

                0,

                0

            },

            32

        },

    其中PartitionedRelationId 为要缓存的表oid, PartitionedRelidIndexId为要缓存的表上对应的唯一索引oid,1为前面的索引的索引键个数,下面的Anum_pg_partitioned_table_partrelid为对应的索引键为表中哪列,最多4个,再下面的32为hash桶个数,如果要缓存的元组多,不妨把这个桶个数设大点,提高效率。注意,这里在cacheinfo中新加的元素位置必须和SysCacheIdentifier中加的枚举位置相同,因为使用时就是以枚举作为数组下标引用。

    indexing.h中的索引为PartitionedRelidIndexId,定义如下:

    DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index,3351, onpg_partitioned_table usingbtree(partrelid oid_ops));

    #define PartitionedRelidIndexId          3351

    也就是说,这是只有一个索引键的唯一索引,对应的列为partrelid列。

    对于最后一步,在修改的地方更新cache,则在以下接口中有体现:

    StorePartitionKey 函数,插入元组后,会调用CatalogUpdateIndexes更新,而对于删除元组,在RemovePartitionKeyByRelId中,删除之后会ReleaseSysCache 更新。

    如果我们要增加一个cache,完成以上步骤即可。

    补充一点

             可能有你看到了,上面有几个字描红加大并且加粗了:没有失效,对于cache这是一个非常关键的点。假如有这样的场景:A会话中select 表t1,那么会加到cache中,B 会话中把这个表drop掉,再从A会话select这个表。显然,是查不到这个表,因为这个cache会失效。当有某个连接中有更新事务提交后,当前所有活跃连接在事务开始时以及语句开始时(注意,begin事务中每个语句开始时),都会主动去接收一个全局共享的消息队列,把一些其它连接中发生改变的对象对应的本地cache进行更新删除,再找时需要重新去表上找,cache中将不存在,保证一致性。

    转载请注明原文地址: https://ju.6miu.com/read-25670.html

    最新回复(0)