searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

openGauss Hint Bits

2023-06-25 08:12:28
32
0

openGauss Hint Bits

背景

数据库中常用MVCC作为并发控制机制,通过保存数据的多个快照版本,实现读写并发。MySQL,Oracle 基于回滚段实现MVCC,openGauss 则在堆表中实际存储每个元组(tuple)的多个版本,多个版本的元组通过指针构成版本链。事务执行时,依次判断元组的可见性,访问对其可见的元组。判断元组可见性时,需要知道插入或删除该元组的事务的状态(commit、abort、或者进行 中)。

openGauss将事务状态记录在 CLOG(Commit LOG) 日志中,在内存中维护一个 SLRU Buffer Pool 用于缓存 CLOG。事务在判断元组可见性时,需要从 SLRU Buffer Pool 甚至磁盘中读取事务的状态。由于判断元组可见性的操作可能非常频繁,因此要求读取事务状态的操作尽量高效,为避免每次都从 CLOG 缓存或磁盘文件中读取,引入 Hint Bits 在元组中直接标识插入/删除该元组的事务的状态,从而减少缓存或者磁盘中读取。

堆表结构简介

openGauss中的表是用堆组织的即堆表。每个表由若干文件组成(表大小超过 RELSEG_SIZE 个块(1G)就会被切分成多个文件),每个文件由若干块(block)构成,每个块大小为 BLCKSZ,默认 8KB。

块的结构分为两部分: 页头(Page Header)和元组(Heap Tuple),元组中存储真正的数据。元组的结构又分为两部分:元组头(Tuple Header)和元组数据(Tuple Data),如下图所示:

t_xmin 记录插入该元组的事务 ID

t_xmax 记录删除该元组的事务 ID

t_ctid 指向新版本的数据,如果没有则指向自己

t_infomask 记录元组的状态信息,包括 Hint Bits.

 

数据多版本

通过示例说明 openGauss中数据多版本的结构。

  1. 事务 101 插入元组,因此该元组的 t_xmin 为 101
  2. 事务 102 有两条更新语句,openGauss 中的更新操作相当于 删除+插入,删除操作是标记删除,将元组的 t_xmax 设置为删除该元组的事务 ID,即 102;随后插入新的元组,旧元组的 t_tcid 执行新元组,构成多版本链
  3. 事务 103 将元组删除,将元组 t_xmax 设置为该事务的 ID 即可

可见性判断

openGauss堆表中可能存在很多版本的数据,有些版本的数据已经永远不会再有事务会使用,可以通过 VACUUM 机制清理;如果有长事务,可能导致堆表膨胀。基于现在堆表的实现,一个事务查询数据时,如何判断哪些数据是对自己可见的呢?基于 interdb 的总结,可见性判断大致包括以下规则:

具体的解释可以参考原文链接或者参考源码实现 HeapTupleSatisfiesMVCC。

Commit Log (clog)

openGauss在 CLOG 中维护事务的状态,持久化存储在 pg_xact 目录下,为了访问高效,会在内存中维护一块共享内存用于缓存 CLOG 的内容。

openGauss中定义了以下四种事务状态:

#define TRANSACTION_STATUS_IN_PROGRESS        0x00

#define TRANSACTION_STATUS_COMMITTED        0x01

#define TRANSACTION_STATUS_ABORTED            0x02

#define TRANSACTION_STATUS_SUB_COMMITTED    0x03

CLOG 文件由一个或者多个 page 构成,CLOG 的内容从逻辑上构成一个数据,数组的下标是事务 ID(即可以根据事务 ID 计算出事务状态的偏移量,可以参考 TransactionIdGetStatus 这个函数),数组的内容是事务状态,可见每个事务状态占用 2 bit 即可。以一个页面 8KB 为例,可以存储 8KB * 8/2 = 32K 个事务的状态。CLOG buffer 的大小为 Min(128, Max(4, NBuffers / 512))。

CLOG 文件以 0000,0001 这种方式命名,每个文件最大 32(SLRU_PAGES_PER_SEGMENT)个页,默认256KB。openGauss启动时会从 pg_xact 中读取事务的状态加载至内存。

系统运行过程中,并不是所有事务的状态都需要长期保留在 CLOG 文件中,因此 vacuum 操作会定期将不再使用的 CLOG 文件删除。

Hint Bits

所有 Hint Bits,就是把事务状态直接记录在元组头中(HeapTupleHeaderData),避免频繁访问 CLOG,元组头中对应的标识位如下:

#define HEAP_XMIN_COMMITTED        0x0100    /* t_xmin committed */

#define HEAP_XMIN_INVALID        0x0200    /* t_xmin invalid/aborted */

#define HEAP_XMIN_FROZEN        (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)

#define HEAP_XMAX_COMMITTED        0x0400    /* t_xmax committed */

#define HEAP_XMAX_INVALID        0x0800    /* t_xmax invalid/aborted */

openGauss并不会在事务提交或者回滚时主动更新元组上的 Hint Bits,而是等到访问该元组并进行可见性判断时,如果发现 Hint Bits 没有设置,则从 CLOG 中读取并设置,否则直接读取 Hint Bits 的值。判断可见性过程中设置 Hint Bits 的函数入口为 SetHintBits。这里的访问可能是 VACUUM,DML 或者 SELECT。

因此,Hint Bits 可以理解为是事务状态在元组头上的一份缓存。

Hint Bits 与日志

在开启 checksum 或者 GUC 参数 wal_log_hints 为 true 的情况下,如果 checkpoint 后第一次使页面 dirty 的操作是更新 Hint Bits,则会产生一条 WAL 日志,将当前页面写入 WAL 日志中(即 Full Page Image),避免产生部分写,导致数据 checksum 异常。

注意,以上写 Full Page Image 日志的行为与是否开启 full_page_writes 没有关系。

因此,在开启 checksum 或者 GUC 参数 wal_log_hints 为 true 时,即便执行 SELECT,也可能更改页面的 Hint Bits,从而导致产生 WAL 日志,这会在一定程度上增加 WAL 日志占用的存储空间。

 

0条评论
0 / 1000
l****n
3文章数
0粉丝数
l****n
3 文章 | 0 粉丝
l****n
3文章数
0粉丝数
l****n
3 文章 | 0 粉丝
原创

openGauss Hint Bits

2023-06-25 08:12:28
32
0

openGauss Hint Bits

背景

数据库中常用MVCC作为并发控制机制,通过保存数据的多个快照版本,实现读写并发。MySQL,Oracle 基于回滚段实现MVCC,openGauss 则在堆表中实际存储每个元组(tuple)的多个版本,多个版本的元组通过指针构成版本链。事务执行时,依次判断元组的可见性,访问对其可见的元组。判断元组可见性时,需要知道插入或删除该元组的事务的状态(commit、abort、或者进行 中)。

openGauss将事务状态记录在 CLOG(Commit LOG) 日志中,在内存中维护一个 SLRU Buffer Pool 用于缓存 CLOG。事务在判断元组可见性时,需要从 SLRU Buffer Pool 甚至磁盘中读取事务的状态。由于判断元组可见性的操作可能非常频繁,因此要求读取事务状态的操作尽量高效,为避免每次都从 CLOG 缓存或磁盘文件中读取,引入 Hint Bits 在元组中直接标识插入/删除该元组的事务的状态,从而减少缓存或者磁盘中读取。

堆表结构简介

openGauss中的表是用堆组织的即堆表。每个表由若干文件组成(表大小超过 RELSEG_SIZE 个块(1G)就会被切分成多个文件),每个文件由若干块(block)构成,每个块大小为 BLCKSZ,默认 8KB。

块的结构分为两部分: 页头(Page Header)和元组(Heap Tuple),元组中存储真正的数据。元组的结构又分为两部分:元组头(Tuple Header)和元组数据(Tuple Data),如下图所示:

t_xmin 记录插入该元组的事务 ID

t_xmax 记录删除该元组的事务 ID

t_ctid 指向新版本的数据,如果没有则指向自己

t_infomask 记录元组的状态信息,包括 Hint Bits.

 

数据多版本

通过示例说明 openGauss中数据多版本的结构。

  1. 事务 101 插入元组,因此该元组的 t_xmin 为 101
  2. 事务 102 有两条更新语句,openGauss 中的更新操作相当于 删除+插入,删除操作是标记删除,将元组的 t_xmax 设置为删除该元组的事务 ID,即 102;随后插入新的元组,旧元组的 t_tcid 执行新元组,构成多版本链
  3. 事务 103 将元组删除,将元组 t_xmax 设置为该事务的 ID 即可

可见性判断

openGauss堆表中可能存在很多版本的数据,有些版本的数据已经永远不会再有事务会使用,可以通过 VACUUM 机制清理;如果有长事务,可能导致堆表膨胀。基于现在堆表的实现,一个事务查询数据时,如何判断哪些数据是对自己可见的呢?基于 interdb 的总结,可见性判断大致包括以下规则:

具体的解释可以参考原文链接或者参考源码实现 HeapTupleSatisfiesMVCC。

Commit Log (clog)

openGauss在 CLOG 中维护事务的状态,持久化存储在 pg_xact 目录下,为了访问高效,会在内存中维护一块共享内存用于缓存 CLOG 的内容。

openGauss中定义了以下四种事务状态:

#define TRANSACTION_STATUS_IN_PROGRESS        0x00

#define TRANSACTION_STATUS_COMMITTED        0x01

#define TRANSACTION_STATUS_ABORTED            0x02

#define TRANSACTION_STATUS_SUB_COMMITTED    0x03

CLOG 文件由一个或者多个 page 构成,CLOG 的内容从逻辑上构成一个数据,数组的下标是事务 ID(即可以根据事务 ID 计算出事务状态的偏移量,可以参考 TransactionIdGetStatus 这个函数),数组的内容是事务状态,可见每个事务状态占用 2 bit 即可。以一个页面 8KB 为例,可以存储 8KB * 8/2 = 32K 个事务的状态。CLOG buffer 的大小为 Min(128, Max(4, NBuffers / 512))。

CLOG 文件以 0000,0001 这种方式命名,每个文件最大 32(SLRU_PAGES_PER_SEGMENT)个页,默认256KB。openGauss启动时会从 pg_xact 中读取事务的状态加载至内存。

系统运行过程中,并不是所有事务的状态都需要长期保留在 CLOG 文件中,因此 vacuum 操作会定期将不再使用的 CLOG 文件删除。

Hint Bits

所有 Hint Bits,就是把事务状态直接记录在元组头中(HeapTupleHeaderData),避免频繁访问 CLOG,元组头中对应的标识位如下:

#define HEAP_XMIN_COMMITTED        0x0100    /* t_xmin committed */

#define HEAP_XMIN_INVALID        0x0200    /* t_xmin invalid/aborted */

#define HEAP_XMIN_FROZEN        (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)

#define HEAP_XMAX_COMMITTED        0x0400    /* t_xmax committed */

#define HEAP_XMAX_INVALID        0x0800    /* t_xmax invalid/aborted */

openGauss并不会在事务提交或者回滚时主动更新元组上的 Hint Bits,而是等到访问该元组并进行可见性判断时,如果发现 Hint Bits 没有设置,则从 CLOG 中读取并设置,否则直接读取 Hint Bits 的值。判断可见性过程中设置 Hint Bits 的函数入口为 SetHintBits。这里的访问可能是 VACUUM,DML 或者 SELECT。

因此,Hint Bits 可以理解为是事务状态在元组头上的一份缓存。

Hint Bits 与日志

在开启 checksum 或者 GUC 参数 wal_log_hints 为 true 的情况下,如果 checkpoint 后第一次使页面 dirty 的操作是更新 Hint Bits,则会产生一条 WAL 日志,将当前页面写入 WAL 日志中(即 Full Page Image),避免产生部分写,导致数据 checksum 异常。

注意,以上写 Full Page Image 日志的行为与是否开启 full_page_writes 没有关系。

因此,在开启 checksum 或者 GUC 参数 wal_log_hints 为 true 时,即便执行 SELECT,也可能更改页面的 Hint Bits,从而导致产生 WAL 日志,这会在一定程度上增加 WAL 日志占用的存储空间。

 

文章来自个人专栏
数据库&存储
3 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0