移动云大云海山数据库(He3DB)存算分离架构下Page页存储正确性校验框架介绍

一、概述

1.1目的

He3PG采用数据共享存储架构,当用户请求计算节点进行数据查询获取,但是本地数据被淘汰时,没有缓存指定数据时会去共享存储获取数据,在此存算分离场景下,获取的数据可能无法判断正确性,可能存在从共享存储读到的是未来页或者过去页,与计算节点本身应有的数据是不一致的,因此需要一种框架,来发现存在不一致数据页的问题,从而帮助定位问题,提升数据库的正确性。

基于以上一些考虑点,所以设计此框架,帮助发现及定位数据正确性问题。

1.2范围

He3DB--PG内核存储模块Page页正确性校验。

二、整体设计

2.1整体设计

数据库共享存储架构下的Page页正确性校验框架是利用对于PG数据页的内容进行Checksum计算,计算层在数据淘汰到FC本地缓存时,记录数据Page页的checksum值到Redis中,每一个FC节点对应Redis的一个DB库,至多15个库。每次计算节点读取数据时,首先根据对应的Page页的属性,拼凑成key值,从Redis中获取对应的value即为checksum值,再根据读取的数据页计算数据页的checksum值,进行比对,如果不一致,则说明page页不正确,此时进程崩溃。

主体架构图如下图所示:

2.2流程设计

**步骤一:**计算节点启动时,在Redis库中初始化对应的启动时间starttimestamp,验证Redis是否可用。对应的计算节点编号即为Redis中的db号。对应函数:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #ifdef CHECKSUM_DS //not push standby should flush all every database start up if(is_redis_checksum && !is_push_standby){ redisContext * c = redisConnect(redis_connect, 32571); if (c->err) { elog(WARNING,"Connect Redis Error: %sn", c->errstr); redisFree(c); }else{ redisCommand(c, "auth %s","****"); if(*cluster_name != '0'){ elog(INFO,"Flush db %s",cluster_name); redisCommand(c, "select %s",cluster_name); redisCommand(c, "flushdb"); } time_t timestamp; time(&timestamp); char *strTime = ctime(&timestamp); redisReply *reply = redisCommand(c, "set starttime %s",strTime); if (reply!=NULL&&reply->type == REDIS_REPLY_ERROR) { elog(WARNING,"Command Error is: %sn", reply->str); freeReplyObject(reply); redisFree(c); } redisFree(c); } } #endif |

**步骤二:**在计算节点将内存中的数据淘汰至磁盘时,进行checksum的计算及将其存储到Redis中,对应的key值为基于Page页拼凑的key值。Page对应的表Oid+BlockNumber构成,checksum计算函数如下:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| static uint32 pg_checksum_block(const PGChecksummablePage *page) { uint32 sumsN_SUMS; uint32 result = 0; uint32 i, j; Assert(sizeof(PGChecksummablePage) == BLCKSZ); memcpy(sums, checksumBaseOffsets, sizeof(checksumBaseOffsets)); for (i = 0; i < (uint32) (BLCKSZ / (sizeof(uint32) * N_SUMS)); i++) for (j = 0; j < N_SUMS; j++) CHECKSUM_COMP(sumsj, page->dataij); for (i = 0; i < 2; i++) for (j = 0; j < N_SUMS; j++) CHECKSUM_COMP(sumsj, 0); for (i = 0; i < N_SUMS; i++) result ^= sumsi; return result; } |

**步骤三:**当从FC或DS读取Page页数据时,首先根据Page页构造相应的key值,再根据key值去Redis中获取对应的checksum。接着,获取到checksum之后针对新获取到的page页进行checksum的计算,计算完了再比对校验,如果checksum一致则验证通过,否则panic异常进程。

三、功能模块设计

3.1基于数据页的正确性校验

Postgresql 保存数据的基本单位是 page,一个 page 里包含多条数据。postgresql 同磁盘的读写单位也是 page,一个 page 对应于磁盘的一个 block。block 的格式和 page 是相同的,本篇文章详细得介绍了 page 的数据存储格式和相关的增删改查操作。

内存结构,page 可以简单划分为四块区域:

  1. Page 头部区域,描述整个 page 的情况,比如空闲空间,校检值等;
  2. 数据指针区域,数据指针用来描述实际数据的存储信息;
  3. 数据区域,用来存储实际数据;
  4. 特殊区域,用来存储一些特殊数据;

Page 头部,page 头部由结构体PageHeaderData来表示:

对于数据页的Special空间就是0;

因此对于数据页直接进行clog的mask函数,去除对应的信息即可。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Page page = (Page) pagedata; OffsetNumber off; mask_page_lsn_and_checksum(page); mask_page_hint_bits(page); mask_unused_space(page); for (off = 1; off <= PageGetMaxOffsetNumber(page); off++) { ItemId iid = PageGetItemId(page, off); char *page_item; page_item = (char *) (page + ItemIdGetOffset(iid)); if (ItemIdIsNormal(iid)) { HeapTupleHeader page_htup = (HeapTupleHeader) page_item; if (!HeapTupleHeaderXminFrozen(page_htup)) page_htup->t_infomask &= ~HEAP_XACT_MASK; else { page_htup->t_infomask &= ~HEAP_XMAX_INVALID; page_htup->t_infomask &= ~HEAP_XMAX_COMMITTED; } page_htup->t_choice.t_heap.t_field3.t_cid = MASK_MARKER; if (HeapTupleHeaderIsSpeculative(page_htup)) ItemPointerSet(&page_htup->t_ctid, blkno, off); } if (ItemIdHasStorage(iid)) { int len = ItemIdGetLength(iid); int padlen = MAXALIGN(len) - len; if (padlen > 0) memset(page_item + len, MASK_MARKER, padlen); } } |

3.2基于索引页的正确性校验

Postgresql中主要支持6种类型的索引:BTREE、HASH、GiST、SP-GiST、GIN、BRIN。

针对不同的索引,需要对索引页中的clog等信息进行mask,因此,对于不同的索引,需要区分索引的不同结构,同时针对其中变化的clog数据进行mask后再计算checksum,否则极可能因为clog信息不一致导致checksum校验不通过。

如何区分不同的索引:

首先,BTREE,HASH,GiST页的Special空间是16个字节,而sp,gin,brin的special空间是8个字节,因此可以简单区分出BTREE,HASH,GiST索引的Page页。

其次,针对同类索引内部,在16个字节中,同样有唯一标识出索引的字段,进行解析即可。

|----------|-------------|--------------------------------|
| 索引Page类型 | Special空间大小 | Special标识字段 |
| BTREE | 16 | -- |
| HASH | 16 | HASHO_PAGE_ID (0xFF80) |
| GiST | 16 | GIST_PAGE_ID (0xFF81) |
| SP-GiST | 8 | SPGIST_PAGE_ID (0xFF82) |
| GIN | 8 | BRIN_PAGETYPE_REGULAR (0xF093) |
| BRIN | 8 | -- |

针对不同的索引,分别过滤对应的clog信息即可,完成checksum的计算。

GinMask函数:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| void gin_mask(char *pagedata, BlockNumber blkno) { Page page = (Page) pagedata; PageHeader pagehdr = (PageHeader) page; GinPageOpaque opaque; mask_page_lsn_and_checksum(page); opaque = GinPageGetOpaque(page); mask_page_hint_bits(page); if (opaque->flags & GIN_DELETED) mask_page_content(page); else if (pagehdr->pd_lower > SizeOfPageHeaderData) mask_unused_space(page); } |

3.3Page页CheckSum存储

在内存中计算好了对应Page页的CheckSum,将其转储到Redis的数据库中。对应代码及流程如下:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #ifdef CHECKSUM_DS //set checksum to redis if(is_redis_checksum&&!is_push_standby&&!c->err){ // while (!acquire_lock(c, redis_lock_key, redis_lock_value, lock_ttl)) { // printf("Write Lock not acquired, waiting...n"); // sleep(1); // } // uint16 newCheckSum=pg_checksum_page((char *)bufBlock, buf->tag.blockNum); char *pageKey=GetRelationPage(reln->smgr_rnode.node.dbNode,reln->smgr_rnode.node.spcNode,reln->smgr_rnode.node.relNode,forknum,blocknum); //except global if(pageKey0!='g'){ uint16 checksum = pgx_checksum_page_with_mask(buffer,blocknum,reln->smgr_rnode.node); if(checksum==0){ return; } ((PageHeader) buffer)->pd_checksum=checksum; elog(DEBUG5,"begin set %s with check sum %u",pageKey,checksum); redisReply *reply = redisCommand(c, "SET %s %u",pageKey,checksum); if (reply->type == REDIS_REPLY_ERROR) { elog(WARNING,"Set error"); freeReplyObject(reply); redisFree(c); // release_lock(c, redis_lock_key, redis_lock_value); } elog(DEBUG5,"done set %s with check sum %u",pageKey,checksum); pfree(pageKey); freeReplyObject(reply); } // release_lock(c, redis_lock_key, redis_lock_value); } #endif |

相关推荐
保定公民几秒前
达梦数据库存储过程中的数组类型详解:基础数组与记录数组的差异化应用
数据库·达梦·存储过程·达梦数据库·dm8·dm
梦Arrebol30 分钟前
Redis 内容及相关实验
数据库·redis
大模型码小白1 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
美狐美颜SDK开放平台1 小时前
视频美颜SDK是什么?直播APP开发中美颜功能实现方式介绍
深度学习·架构·实时互动·音视频·视频美颜sdk
DBA大董3 小时前
TDengine3.0 DBA常用的运维命令和SQL2
运维·数据库·时序数据库·dba·tdengine
画中有画3 小时前
论多源异构数据集成与数据架构设计
架构
ManageEngineITSM3 小时前
什么是CMDB?配置管理数据库的定义、作用与建设方法一文讲清
大数据·数据库·人工智能·资产管理·变更管理
IvorySQL3 小时前
PostgreSQL 日报|PG19 外键快速路径隐患已修复(8 月 23 日)
数据库·postgresql
Elastic 中国社区官方博客4 小时前
OpenTelemetry Java 扩展:无需分叉 agent 即可自定义追踪
java·大数据·运维·开发语言·数据库·人工智能·elasticsearch