SLRU
openGauss使用SLRU来管理事务相关日志的缓冲区,包括CLOG、CSNLOG、multixact log。SLRU是简化版的LRU,使用数组的方式存储元素,每次换入换出是会遍历数组找到lru count最小的buffer
主要接口
SimpleLruInit
初始化缓冲区
SlruSelectLRUPage
用于选择一个可以重用的缓冲区槽位,当需要一个缓冲区slot用于度入新页面时,需要调用该函数获取一个可用的缓冲区slot。
其执行流程如下所示:
- 首先逐个比较缓冲池中的缓冲区,如果所需要的页面已经在一个缓冲区中,那么直接返回该缓冲区即可
- 如果能找到一个状态为“空”的缓冲区,那么直接返回该缓冲区以供使用。
- 如果依然没有一个可用的缓冲区,那么在所有缓冲区中找到一个LRU值最大的缓冲区(需要注意的是,当前页面号最大的页面并不参与到这个LRU选择过程中)
- 然后判断找到的缓冲区的状态,如果为“干净”,那么直接返回该缓冲区;如果该缓冲区状态为“正在读入”,那么调用SimpleLruReadPage函数继续完成读入动作;如果缓冲区状态为“脏”或“正在写人”,那么调用 SimpleLruWritePage 函数继续完成写人动作。
- 最后返回第一步,继续完成选择缓冲区的任务,直至选出一个可供重用的缓冲区为止。
SimpleLruZeroPage
初始化一个缓冲区槽位。
- 调用 SlruSelectLRUPage 函数以获取一个可用的缓冲区。
- 然后将该缓冲区页面号设置为指定页面号,并设置其状态为“脏”,同时设置其为最近使用的页面,即该页面的 LRU值初始化为0,而其他页面的LRU值增一。
- 最后初始化该缓冲区为全0,并设置当前最大页面号为指定的页面号。最后返回所选择的
缓冲区号。 - 缓冲池页面的读操作
SimpleLruReadPage
从缓冲池中读取指定的buffer。执行该函数前,线程持有control_lock的x锁。如果指定页面已经在缓冲池中,那么直接返回其所在的缓冲区;否则调用
- 调用SlruSelectLRUPage从缓冲池中寻找指定页面,找不到就返回一个可以淘汰的槽位。
- 占用该槽位
shared->page_number[slotno] = pageno
,并标记为shared->page_status[slotno] = SLRU_PAGE_READ_IN_PROGRESS
,即为该槽位正在读 - 加buffer锁的x锁
(void)LWLockAcquire(shared->buffer_locks[slotno], LW_EXCLUSIVE);
- 释放控制锁
LWLockRelease(shared->control_lock);
- 读页面
SlruPhysicalReadPage
- 加控制锁的x锁
(void)LWLockAcquire(shared->control_lock, LW_EXCLUSIVE)
- 如果成功了
shared->page_status[slotno] = SLRU_PAGE_VALID
- 释放buffer锁
LWLockRelease(shared->buffer_locks[slotno]);
SimpleLruWritePage
- SimpleLruWritePage
- SlruInternalWritePage
- 如果槽位状态为
SLRU_PAGE_WRITE_IN_PROGRESS
,说明其他线程正在写该页面,等页面写完。 - 如果页面非脏、页面在读或写、页面被替换,直接返回。
- 槽位状态置为
SLRU_PAGE_WRITE_IN_PROGRESS
- 加buffer锁的x锁
- 写页面
ok = SlruPhysicalWritePage(ctl, pageno, slotno, fdata);
- 状态置为有效
shared->page_status[slotno] = SLRU_PAGE_VALID;
- 释放buffer锁
LWLockRelease(shared->buffer_locks[slotno]);
CLOG
CLOG中事务的四种状态
#define CLOG_XID_STATUS_IN_PROGRESS 0x00
#define CLOG_XID_STATUS_COMMITTED 0x01
#define CLOG_XID_STATUS_ABORTED 0x02
#define CLOGDIR (g_instance.datadir_cxt.clogDir)
/*
* A "subcommitted" transaction is a committed subtransaction whose parent
* hasn't committed or aborted yet.
*/
#define CLOG_XID_STATUS_SUB_COMMITTED 0x03
由于CLOG只记录事务的四个状态,因此一个事务状态信息的CLOG日志只需要2个比特,一个byte存储4个事务的CLOG,一个8k页面存储215个事务的CLOG。一个段文件(32个页面组成)可记录的CLOG日志记录数为220个。CLOG统一保存在$PGDATA/pg_clog目录下。
通过一个4元组<segmentno, pageno, byte, bindex>可以定位一条CLOG日志记录。其中Segmentno为段号,即实际的段文件名称。pageno为日志记录所在的段内的页偏移;byte为页面便宜;bindex为字节内偏移。
通过事务ID获取其日志记录对应的4元组。
#define TransactionIdToPage(xid) ((xid) / (TransactionId)CLOG_XACTS_PER_PAGE)
#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId)CLOG_XACTS_PER_PAGE)
#define TransactionIdToByte(xid) (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)
#define TransactionIdToBIndex(xid) ((xid) % (TransactionId)CLOG_XACTS_PER_BYTE)
#define PAGE_TO_TRANSACTION_ID(pageno) ((pageno) * (TransactionId)CLOG_XACTS_PER_PAGE)
CLOG日志缓冲池就是一个SLRU缓冲池,注册在共享内存中,其名称为“CLOG Ctl”,openGauss对CLOG做了分区处理
/* CLog lwlock partition*/
#define CBufHashPartition(hashcode) \
((hashcode) % NUM_CLOG_PARTITIONS)
#define CBufMappingPartitionLock(hashcode) \
(&t_thrd.shemem_ptr_cxt.mainLWLockArray[FirstCBufMappingLock + CBufHashPartition(hashcode)].lock)
#define CBufMappingPartitionLockByIndex(i) \
(&t_thrd.shemem_ptr_cxt.mainLWLockArray[FirstCBufMappingLock + i].lock)
CLOG日志的写操作
函数:CLogSetTreeStatus
void CLogSetTreeStatus(TransactionId xid, int nsubxids, TransactionId *subxids, CLogXidStatus status, XLogRecPtr lsn)
将事务和事务的子事务状态置为CLOG_XID_STATUS_COMMITTED或CLOG_XID_STATUS_ABORTED
-
CLogSetTreeStatus,为保证事务原子性
记录提交日志中一个事务及其子事务树的事务条目的最终状态。确保这一过程高效且尽可能原子化。 xid 是要设置状态的单个 xid。通常这是顶层提交或中止的顶层 transactionid。它也可以是子事务,当我们记录事务中止时。 subxids 是一个长度为 nsubxids 的 xids 数组,表示 xid 树中的子事务。在某些情况下,nsubxids 可能为零。 lsn 必须是记录异步提交时提交记录的 WAL 位置。对于同步提交,它可以是 InvalidXLogRecPtr,因为调用者保证在这种情况下提交记录已经刷新。对于中止情况,它也应该是 InvalidXLogRecPtr。 在提交的情况下,原子性受限于所有 subxids 是否在与 xid 相同的 CLOG 页面上。如果它们都在同一页面上,则只需获取一次锁,并直接将状态设置为已提交。否则,我们必须: 1. 将所有不在主 xid 同一页面上的 subxids 设置为子已提交 2. 原子性地将主 xid 和同一页面上的 subxids 设置为已提交 3. 再次遍历第一批,将它们设置为已提交 注意,就并发检查者而言,主事务的提交整体上仍然是原子的。 示例: TransactionId t 提交并具有子 xids t1、t2、t3、t4 t 在页面 p1,t1 也在 p1,t2 和 t3 在 p2,t4 在 p3 1. 更新页面 2-3: 页面 2:将 t2、t3 设置为子已提交 页面 3:将 t4 设置为子已提交 2. 更新页面 1: 将 t1 设置为子已提交, 然后将 t 设置为已提交, 然后将 t1 设置为已提交 3. 更新页面 2-3: 页面 2:将 t2、t3 设置为已提交 页面 3:将 t4 设置为已提交 注意:这是一个低级例程,并不是大多数使用的首选入口点;transam.c 中的函数是预期的调用者。 XXX 考虑对我们需要但尚未在缓存中的页面发出 FADVISE_WILLNEED,以及提示页面不要过早从缓存中消失。
-
CLogSetPageStatus,更新前某一CLOG页面中的的事务状态前,调用
LWLockConditionalAcquire(ClogCtl(pageno)->shared->control_lock, LW_EXCLUSIVE)
,加对应分区SLRU的x锁。 -
CLogSetPageStatusInternal,先调用SimpleLruReadPage方法读取指定页面,再调用CLogSetStatusBit将该页面上所有的子事务状态置为CLOG_XID_STATUS_SUB_COMMITTED,再将主事务的状态置为status,再将该页面上所有子事务状态置为status
CLOG日志页面的初始化
ZeroCLOGPage
CLOG日志端的创建
BootStrapCLOG
multixact
之前一直认为Postgres中tuple头部信息的xmax只记录了删除/更新这条记录的事务号,为PG的MVCC机制服务。调试了一下PG行锁的实现机制,发现xmax所包含的信息没有那么简单,它还负责记录该tuple上的行锁以及行锁的模式信息(EXCLUDE/SHARE),以及持有该锁的事务号; 另外,因为多个事务可以持有某个行的SHARE锁,PG不可能将所有持有这个行锁的事务的事务号信息都放在tuple的头部,所以又引入了一个multixact机制,将这多个事务号”合并”起来放在共享内存里,tuple头部信息保存一个映射指向共享内存里这个”合并”的多个事务号即可。
multixact日志是PostgreSQL系统用来记录组合事务ID的一种日志。由于openGauss采用了多版本并发控制,因此同一个元组相关联的事务ID可能有多个,为了在加锁(行共享锁)的时候统一操作,openGauss将与该元组相关联的多个事务ID组合起来用一个multixactId代替管理。
multiXactID是一个多对一的映射关系(多个事务ID映射到一个multixactID),需要在事务ID数组标记哪一段映射到一个multiXacTID。openGauss用一个事务ID队列来存储multixactID的映射关系,具体而言openGauss将multixact的存储方式拆成offset和member两个部分:
-
multixactoffset存储的时某个multixact id对应的offset,可以通过offset去multixactmember中找该multixact id映射哪些事务id。
-
multixactmember存储的时所有multixact对应的事务id的列表,通过offset作为列表的其实点,两个相邻的offset之间的事务id就是该offset对应事务id列表。
multixact日志管理器
multixact日志管理器是基于SLRU缓冲池实现的,只不过需要两个缓冲池,MultiXactOffsetCtl和MultiXactMemberCtl
multixact存储格式
- offset日志:每个日志文件相当于一个段,每个段32个页,由于每个offset是64位即8字节,所以每页可以存8K/8=1K个,每个文件可以存32*1K=32K个。
- members文件:每个日志相当于一个段,每个段32个页,由于每个TransactionId是64位即8字节,所以每页可以存8K/8=1K个,每个文件可以存32*1K=64K个。
#define MultiXactIdToOffsetPage(xid) ((xid) / (MultiXactOffset)MULTIXACT_OFFSETS_PER_PAGE)
#define MultiXactIdToOffsetEntry(xid) ((xid) % (MultiXactOffset)MULTIXACT_OFFSETS_PER_PAGE)
#define MXOffsetToMemberPage(xid) ((xid) / (TransactionId)MULTIXACT_MEMBERS_PER_PAGE)
#define MXOffsetToMemberEntry(xid) ((xid) % (TransactionId)MULTIXACT_MEMBERS_PER_PAGE)
offset可以通过三元组定位<segno, pageno, offno>,其中
- segno: 段号
- 段内页码
- 页内偏移
给定一个MultiXactID可以通过三元组计算出offset的位置,其公式为:
segno = mxid / 32768
pageno = mxid / 1024
offno = mxid % 1024 * 8
member也可以通过三元组来定位其位置,给定一个offset,其计算公式为
segno = offset / 32768
pageno = offset / 1024
offno = offset % 1024 * 8
例如,mxid为2050, offset位置为<0,1,8>, 假如得出其中一个的offset值为1836,其member位置<0,1,1004>
Multixact一般包含多个member,其他的members的位置一般紧邻着第一个members存放,所以只要知道第一个members的位置和member的数量即可,数量可以通过mxid+1的offset值减去mxid的offset值获得。
部分函数
MultiXactShmemInit
初始化缓冲池
CreateMultiXactId
创建一个新的multixactid
RecordNewMultiXact
将一个新的multixact分别记录到MultiXactOffsetCtl和MultiXactMemberCtl。
GetMultiXactIdMembers
通过multixactId获取映射的事务id列表
TruncateMultiXact
vacuum时调用,清理掉没用的MultiXactOffset 和 MultiXactMember 段文件
测试
创建表t5,插入数据,查询元组内容。
openGauss=# create table t5(id int,name varchar(60));
CREATE TABLE
openGauss=# insert into t5 values (1, 'test1'), (2, 'test2'), (3, 'test3'), (4, 'test4'), (5, 'test5');
INSERT 0 5
openGauss=# insert into t5 values (1, 'test1'), (2, 'test^C), (3, 'test3'), (4, 'test4'), (5, 'test5');
openGauss=# select * from heap_page_items(get_raw_page('T5',0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits |
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-
1 | 8152 | 1 | 34 | 14225 | 0 | 0 | (0,1) | 2 | 2050 | 24 | |
2 | 8112 | 1 | 34 | 14225 | 0 | 0 | (0,2) | 2 | 2050 | 24 | |
3 | 8072 | 1 | 34 | 14225 | 0 | 0 | (0,3) | 2 | 2050 | 24 | |
4 | 8032 | 1 | 34 | 14225 | 0 | 0 | (0,4) | 2 | 2050 | 24 | |
5 | 7992 | 1 | 34 | 14225 | 0 | 0 | (0,5) | 2 | 2050 | 24 | |
(5 rows)
会话一创建事务,执行select for share,发现,tuple 1的xmax变成14233即当前事务xid,infomask=386(HEAP_XMIN_COMMITTED, HEAP_XMAX_SHARED_LOCK, HEAP_HASVARWIDTH),infomask2=2050(HEAP_ONLY_TUPLE, )
openGauss=# begin;
BEGIN
openGauss=# select pg_backend_pid(),txid_current();
pg_backend_pid | txid_current
-----------------+--------------
281443410172352 | 14233
(1 row)
openGauss=# select id,name from t5 where id=1 for share;
id | name
----+-------
1 | test1
(1 row)
openGauss=# select * from heap_page_items(get_raw_page('T5',0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits |
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-
1 | 8152 | 1 | 34 | 14232 | 14233 | 0 | (0,1) | 2050 | 386 | 24 | |
2 | 8112 | 1 | 34 | 14232 | 0 | 0 | (0,2) | 2 | 2306 | 24 | |
3 | 8072 | 1 | 34 | 14232 | 0 | 0 | (0,3) | 2 | 2306 | 24 | |
4 | 8032 | 1 | 34 | 14232 | 0 | 0 | (0,4) | 2 | 2306 | 24 | |
5 | 7992 | 1 | 34 | 14232 | 0 | 0 | (0,5) | 2 | 2306 | 24 | |
(5 rows)
会话二创建事务,执行select for update,tuple 1的xmax变成2,infomask变成4482(0x1182 新增了HEAP_XMAX_IS_MULTI,意为xmax是multixact)
openGauss=# begin;
BEGIN
openGauss=# select pg_backend_pid(),txid_current();
pg_backend_pid | txid_current
-----------------+--------------
281443234011584 | 14234
(1 row)
openGauss=# select id,name from t5 where id=1 for share;
id | name
----+-------
1 | test1
(1 row)
openGauss=# select * from heap_page_items(get_raw_page('T5',0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits |
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-
1 | 8152 | 1 | 34 | 14232 | 2 | 0 | (0,1) | 2050 | 4482 | 24 | |
2 | 8112 | 1 | 34 | 14232 | 0 | 0 | (0,2) | 2 | 2306 | 24 | |
3 | 8072 | 1 | 34 | 14232 | 0 | 0 | (0,3) | 2 | 2306 | 24 | |
4 | 8032 | 1 | 34 | 14232 | 0 | 0 | (0,4) | 2 | 2306 | 24 | |
5 | 7992 | 1 | 34 | 14232 | 0 | 0 | (0,5) | 2 | 2306 | 24 | |
(5 rows)
会话三再创建事务再执行select for update,发现multixact又涨了,说明如果再有事务对同一个页面加锁,会生成新的multixact,不会修改已有的multixact对应事务列表
openGauss=# begin;
BEGIN
openGauss=# select id,name from t5 where id=1 for share;
id | name
----+-------
1 | test1
(1 row)
openGauss=# select * from heap_page_items(get_raw_page('T5',0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------
1 | 8152 | 1 | 34 | 14232 | 4 | 0 | (0,1) | 2050 | 4482 | 24 | |
2 | 8112 | 1 | 34 | 14232 | 0 | 0 | (0,2) | 2 | 2306 | 24 | |
3 | 8072 | 1 | 34 | 14232 | 0 | 0 | (0,3) | 2 | 2306 | 24 | |
4 | 8032 | 1 | 34 | 14232 | 0 | 0 | (0,4) | 2 | 2306 | 24 | |
5 | 7992 | 1 | 34 | 14232 | 0 | 0 | (0,5) | 2 | 2306 | 24 | |
(5 rows)
下面是 pg_multixact/offsets的文件内容,按照multixact的顺序排列。保存的是每个multixact的MultiXactOffset,MultiXactOffset为uint64,即每8个字节一个MultiXactOffset,其中multixact = 4时,对应member为(0500 0000 0000 0000),即5,即 pg_multixact/members中从位置5开始到下一个multixact的offset位置,都是这个multixact对应的事务id。
[zhoucong_normal@openGauss111 pg_multixact]$ xxd offsets/000000000000 |more
00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000010: 0100 0000 0000 0000 0300 0000 0000 0000 ................
00000020: 0500 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000100: 0000 0000 0000 0000 0000 0000 0000 0000 ................
下面是 pg_multixact/members的文件内容,按照offset的循序排列。保存的是每个offset的TransactionId列表,TransactionId为uint64,即每8个字节一个TransactionId,其中offset = 5时,对应的事务id为(9b37 0000 0000 0000,9c37 0000 0000 000 , 9d37 0000 0000 0000),即(0x379b, 0x379c, 0x379d),即multixact = 4对应三个事务id(14235, 14236, 14237)。
[zhoucong_normal@openGauss111 members]$ xxd 000000000000 | more
00000000: 0000 0000 0000 0000 9937 0000 0000 0000 .........7......
00000010: 9a37 0000 0000 0000 9b37 0000 0000 0000 .7.......7......
00000020: 9c37 0000 0000 0000 9b37 0000 0000 0000 .7.......7......
00000030: 9c37 0000 0000 0000 9d37 0000 0000 0000 .7.......7......
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000100: 0000 0000 0000 0000 0000 0000 0000 0000 ................
infomask
infomask相关的标记
其中0x1000表示max是multixact
/*
* information stored in t_infomask:
*/
#define HEAP_HASNULL 0x0001 /* has null attribute(s) */
#define HEAP_HASVARWIDTH 0x0002 /* has variable-width attribute(s) */
#define HEAP_HASEXTERNAL 0x0004 /* has external stored attribute(s) */
#define HEAP_HASOID 0x0008 /* has an object-id field */
#define HEAP_COMPRESSED 0x0010 /* has compressed data */
#define HEAP_COMBOCID 0x0020 /* t_cid is a combo cid */
#define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */
#define HEAP_XMAX_SHARED_LOCK 0x0080 /* xmax is shared locker */
/* xmax is a key-shared locker */
#define HEAP_XMAX_KEYSHR_LOCK (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_SHARED_LOCK)
#define HEAP_LOCK_MASK (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_SHARED_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */
#define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */
#define HEAP_XMIN_FROZEN (HEAP_XMIN_INVALID | HEAP_XMIN_COMMITTED)
#define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */
#define HEAP_XMAX_INVALID 0x0800 /* t_xmax invalid/aborted */
#define HEAP_XMAX_IS_MULTI 0x1000 /* t_xmax is a MultiXactId */
#define HEAP_UPDATED 0x2000 /* this is UPDATEd version of row */
#define HEAP_HAS_8BYTE_UID (0x4000) /* tuple has 8 bytes uid */
#define HEAP_UID_MASK (0x4000)
#define NDP_HANDLED_TUPLE (0x8000) /* tuple is from ndp backend */
#define HEAP_XACT_MASK (0x3FE0) /* visibility-related bits */
infomask2相关的标记
/*
* information stored in t_infomask2:
*/
#define HEAP_NATTS_MASK 0x07FF /* 11 bits for number of attributes */
#define HEAP_XMAX_LOCK_ONLY 0x0800 /* xmax, if valid, is only a locker */
#define HEAP_KEYS_UPDATED 0x1000 /* tuple was updated and key cols modified, or tuple deleted */
#define HEAP_HAS_REDIS_COLUMNS 0x2000 /* tuple has hidden columns added by redis */
#define HEAP_HOT_UPDATED 0x4000 /* tuple was HOT-updated */
#define HEAP_ONLY_TUPLE 0x8000 /* this is heap-only tuple */
#define HEAP2_XACT_MASK 0xD800 /* visibility-related bits */
#define HeapTupleHeaderSetHotUpdated(tup) ((tup)->t_infomask2 |= HEAP_HOT_UPDATED)
#define HeapTupleHeaderClearHotUpdated(tup) ((tup)->t_infomask2 &= ~HEAP_HOT_UPDATED)
#define HeapTupleHeaderIsHeapOnly(tup) (((tup)->t_infomask2 & HEAP_ONLY_TUPLE) != 0)
#define HeapTupleHeaderSetHeapOnly(tup) ((tup)->t_infomask2 |= HEAP_ONLY_TUPLE)
#define HeapTupleHeaderClearHeapOnly(tup) ((tup)->t_infomask2 &= ~HEAP_ONLY_TUPLE)
#define HeapTupleHeaderHasRedisColumns(tup) (((tup)->t_infomask2 & HEAP_HAS_REDIS_COLUMNS) != 0)
#define HeapTupleHeaderSetRedisColumns(tup) ((tup)->t_infomask2 |= HEAP_HAS_REDIS_COLUMNS)
#define HeapTupleHeaderUnsetRedisColumns(tup) ((tup)->t_infomask2 &= ~HEAP_HAS_REDIS_COLUMNS)
#define HeapTupleHeaderHasMatch(tup) (((tup)->t_infomask2 & HEAP_TUPLE_HAS_MATCH) != 0)
#define HeapTupleHeaderSetMatch(tup) ((tup)->t_infomask2 |= HEAP_TUPLE_HAS_MATCH)
#define HeapTupleHeaderClearMatch(tup) ((tup)->t_infomask2 &= ~HEAP_TUPLE_HAS_MATCH)
参考内容
multixact
https://www.modb.pro/db/14939
https://blog.youkuaiyun.com/mirai_D_zoro/article/details/136970358
SLRU
Columns(tup) ((tup)->t_infomask2 &= ~HEAP_HAS_REDIS_COLUMNS)
#define HeapTupleHeaderHasMatch(tup) (((tup)->t_infomask2 & HEAP_TUPLE_HAS_MATCH) != 0)
#define HeapTupleHeaderSetMatch(tup) ((tup)->t_infomask2 |= HEAP_TUPLE_HAS_MATCH)
#define HeapTupleHeaderClearMatch(tup) ((tup)->t_infomask2 &= ~HEAP_TUPLE_HAS_MATCH)
# 参考内容
**multixact**
https://www.modb.pro/db/14939
https://blog.youkuaiyun.com/mirai_D_zoro/article/details/136970358
**SLRU**
https://blog.youkuaiyun.com/mirai_D_zoro/article/details/136970287