openGauss-server/src/gausskernel/storage/remote/remote_adapter.cpp

973 lines
41 KiB
C++
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* ---------------------------------------------------------------------------------------
*
* remote_adapter.cpp
* using simple C API interface for rpc call PG founction
* Don't include any of RPC header file.
*
* IDENTIFICATION
* src/gausskernel/storage/remote/remote_adapter.cpp
*
* ---------------------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
#include "access/xlog.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "funcapi.h"
#include "postmaster/postmaster.h"
#include "storage/smgr/segment_internal.h"
#include "storage/smgr/segment.h"
#include "storage/custorage.h"
#include "storage/ipc.h"
#include "storage/remote_adapter.h"
#include "libpq/pqformat.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/builtins.h"
#include "utils/aiomem.h"
const int DEFAULT_WAIT_TIMES = 60;
int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea** fileData, int timeout);
int ReadCOrCsnFileForRemote(RelFileNode rnode, bytea** fileData);
/*
* @Description: wait lsn to replay
* @IN primary_insert_lsn: remote request lsn
* @Return: remote read error code
* @See also:
*/
//用于等待指定LSN日志序列号被回放到本地
// 常见的应用场景是在流复制Streaming Replication等待从节点接收并成功应用指定的事务日志
// primary_insert_lsn远程请求的LSN日志序列号
int XLogWaitForReplay(uint64 primary_insert_lsn, int timeout = DEFAULT_WAIT_TIMES)
{
int wait_times = 0;
/* local replayed lsn */
//获取本地正在回放的LSN
XLogRecPtr standby_replay_lsn = GetXLogReplayRecPtr(NULL, NULL);
/* if primary_insert_lsn > standby_replay_lsn then need wait */
//循环判断如果primary_insert_lsn大于standby_replay_lsn则需要继续等待
while (!XLByteLE(primary_insert_lsn, standby_replay_lsn)) {
/* if sleep to much times */
//判断等待次数是否超过超时时间
if (wait_times >= timeout) {
//如果超过则返回错误并打印相应日志信息
ereport(LOG,
(errmodule(MOD_REMOTE),
errmsg("replay slow. requre lsn %X/%X, replayed lsn %X/%X",
(uint32)(primary_insert_lsn >> 32),
(uint32)primary_insert_lsn,
(uint32)(standby_replay_lsn >> 32),
(uint32)standby_replay_lsn)));
//等待超时则返回REMOTE_READ_NEED_WAIT远程读取需要等待
return REMOTE_READ_NEED_WAIT;
}
/* sleep 1s */
//进行1秒的延迟
pg_usleep(1000000L);
//递增等待次数
++wait_times;
/* get current replay lsn again */
//再次获取当前的回放LSN更新
(void)GetXLogReplayRecPtr(NULL, &standby_replay_lsn);
}
//直到primary_insert_lsn小于等于standby_replay_lsn表示LSN已经回放完成
//成功回放则返回REMOTE_READ_OK远程读取完成
return REMOTE_READ_OK;
}
/*
* Read block from buffer from primary, returning it as bytea
*/
//实现了从主节点读取缓冲区中的数据块并将其作为bytea类型返回
Datum gs_read_block_from_remote(PG_FUNCTION_ARGS)
{
uint32 spcNode; //文件空间节点号
uint32 dbNode; //数据库节点号
uint32 relNode; //关系节点号
int16 bucketNode; //哈希桶节点号
int32 forkNum; //分叉号
uint64 blockNum; //块号
uint32 blockSize; //块大小
uint64 lsn; //日志序列号
bool isForCU = false; //是否是针对CU压缩单元块的请求
bytea* result = NULL;
int timeout = 0;
//检查当前用户是否为超级用户
if (GetUserId() != BOOTSTRAP_SUPERUSERID) {
//如果不是则抛出异常
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files"))));
}
/* handle optional arguments */
//解析传入的参数
spcNode = PG_GETARG_UINT32(0);
dbNode = PG_GETARG_UINT32(1);
relNode = PG_GETARG_UINT32(2);
bucketNode = PG_GETARG_INT16(3);
forkNum = PG_GETARG_INT32(4);
blockNum = (uint64)PG_GETARG_TRANSACTIONID(5);
blockSize = PG_GETARG_UINT32(6);
lsn = (uint64)PG_GETARG_TRANSACTIONID(7);
isForCU = PG_GETARG_BOOL(8);
timeout = PG_GETARG_INT32(9);
//创建修复块的键RepairBlockKey对象并填充相关属性
RepairBlockKey key;
key.relfilenode.spcNode = spcNode;
key.relfilenode.dbNode = dbNode;
key.relfilenode.relNode = relNode;
key.relfilenode.bucketNode = bucketNode;
key.relfilenode.opt = 0;
key.forknum = forkNum;
key.blocknum = blockNum;
/* get block from local buffer */
// 从本地缓冲区读取块
if (isForCU) {
// 从本地缓冲区读取 CU 块
/* if request to read CU block, we use forkNum column to replace colid. */
(void)StandbyReadCUforPrimary(key, blockNum, blockSize, lsn, timeout, &result);
} else {
//从本地缓冲区读取普通页面块
(void)StandbyReadPageforPrimary(key, blockSize, lsn, &result, timeout, NULL);
}
if (NULL != result) {
//成功获取到数据块则将其转换为bytea类型并返回
PG_RETURN_BYTEA_P(result);
} else {
//未获取到数据块则返回NULL
PG_RETURN_NULL();
}
}
/*
* Read block from buffer from primary, returning it as bytea
*/
//用于从主节点的压缩块缓冲区中读取数据并将其以bytea类型返回
Datum gs_read_block_from_remote_compress(PG_FUNCTION_ARGS)
{
RepairBlockKey key; // 修复块的关键信息
uint32 blockSize; // 块大小
uint64 lsn; // 日志序列号
int timeout = 0; // 超时时间
bool isForCU = false; // 是否请求读取CU块
bytea* result = NULL; // 返回的块数据
// 检查当前用户是否为超级用户
if (GetUserId() != BOOTSTRAP_SUPERUSERID) {
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files"))));
}
/* handle optional arguments */
// 获取传入的参数值
key.relfilenode.spcNode = PG_GETARG_UINT32(0); // 表空间OID
key.relfilenode.dbNode = PG_GETARG_UINT32(1); // 数据库OID
key.relfilenode.relNode = PG_GETARG_UINT32(2); // 关系OID
key.relfilenode.bucketNode = PG_GETARG_INT16(3); // 桶OID
key.relfilenode.opt = PG_GETARG_UINT16(4); // 扩展选项
key.forknum = PG_GETARG_INT32(5); // 分支号
key.blocknum = (uint64)PG_GETARG_TRANSACTIONID(6); // 块号
blockSize = PG_GETARG_UINT32(7); // 块大小
lsn = (uint64)PG_GETARG_TRANSACTIONID(8); // 日志序列号
isForCU = PG_GETARG_BOOL(9); // 是否请求读取CU块
timeout = PG_GETARG_INT32(10); // 超时时间
/* get block from local buffer */
if (isForCU) {
// 从本地缓冲区读取 CU 块
/* if request to read CU block, we use forkNum column to replace colid. */
(void)StandbyReadCUforPrimary(key, key.blocknum, blockSize, lsn, timeout, &result);
} else {
// 从本地缓冲区读取页块
(void)StandbyReadPageforPrimary(key, blockSize, lsn, &result, timeout, NULL);
}
// 检查块是否成功获取
if (NULL != result) {
// 返回块数据
PG_RETURN_BYTEA_P(result);
} else {
// 返回空值
PG_RETURN_NULL();
}
}
/*
* @Description: read cu for primary
* @IN spcnode: tablespace id
* @IN dbnode: database id
* @IN relnode: relfilenode
* @IN colid: column id
* @IN offset: cu offset
* @IN size: cu size
* @IN lsn: lsn wait for replay
* @IN/OUT context: read context
* @OUT cudata: output cu data
* @Return: remote read error code
* @See also:
*/
//用于从主节点读取某个文件中指定块号的CU数据块
int StandbyReadCUforPrimary(RepairBlockKey key, uint64 offset, int32 size, uint64 lsn, int32 timeout,
bytea** cudata)
{
// 确保输出参数cudata存在
Assert(cudata);
// 初始化返回值
int ret_code = REMOTE_READ_OK;
/* wait request lsn for replay */
//等待请求的LSN回放完成
// 检查是否处于恢复进程中
if (RecoveryInProgress()) {
// 等待指定LSN的日志重放超时时间为timeout
ret_code = XLogWaitForReplay(lsn, timeout);
if (ret_code != REMOTE_READ_OK) {
// 如果错误码不等于REMOTE_READ_OK则发生了错误记录错误日志并返回错误码
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("could not redo request lsn.")));
return ret_code;
}
}
// 使用传入的参数构造关系文件节点RelFileNode对象
RelFileNode relfilenode {key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, InvalidBktId};
// 在匿名块中执行以下语句
{
/* read from disk */
//创建CFileNode对象用于标识具体的文件和文件块
CFileNode cfilenode(relfilenode, key.forknum, MAIN_FORKNUM);
// 创建CUStorage对象用于管理和访问CU数据
CUStorage* custorage = New(CurrentMemoryContext) CUStorage(cfilenode);
// 创建CU对象用于加载和处理CU数据
CU* cu = New(CurrentMemoryContext) CU();
cu->m_inCUCache = false;
// 从磁盘加载指定偏移量和大小的CU块到内存中的CU对象中
custorage->LoadCU(cu, offset, size, false, false);
/* check crc */
// 检查CU数据的CRC校验码是否正确
if (ret_code == REMOTE_READ_OK) {
if (!cu->CheckCrc()) {
// 如果CRC校验失败则记录错误日志并更新返回值为REMOTE_READ_CRC_ERROR
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("check CU crc error.")));
ret_code = REMOTE_READ_CRC_ERROR;
} else {
// 复制CU数据到返回值缓冲区
bytea* buf = (bytea*)palloc0(VARHDRSZ + size);
SET_VARSIZE(buf, size + VARHDRSZ);
errno_t rc = memcpy_s(VARDATA(buf), size, cu->m_compressedLoadBuf, size);
if (rc != EOK) {
// 如果复制过程中发生错误则记录错误日志并更新返回值为REMOTE_READ_MEMCPY_ERROR
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("memcpy_s error, retcode=%d", rc)));
ret_code = REMOTE_READ_MEMCPY_ERROR;
}
// 将buf对象赋值给输出参数cudata
*cudata = buf;
// 释放内存删除custorage和cu对象
DELETE_EX(custorage);
DELETE_EX(cu);
}
}
}
// 返回错误代码
return ret_code;
}
/*
* @Description: read page for primary
* @IN spcnode: tablespace id
* @IN dbnode: database id
* @IN relnode: relfilenode
* @IN forknum: forknum
* @IN blocknum: block number
* @IN blocksize: block size
* @IN lsn: lsn wait for replay
* @IN/OUT context: read context
* @OUT pagedata: output page data
* @Return: remote read error code
* @See also:
*/
//读取主库的数据页并将数据存储在pagedata中
int StandbyReadPageforPrimary(RepairBlockKey key, uint32 blocksize, uint64 lsn, bytea** pagedata,
int timeout, const XLogPhyBlock *pblk)
{
//通过断言语句检查pagedata是否为空指针
Assert(pagedata);
//传入的数据块大小是否与定义的常量BLCKSZ相同
if (unlikely(blocksize != BLCKSZ))
//不同则返回错误码
return REMOTE_READ_BLCKSZ_NOT_SAME;
int ret_code = REMOTE_READ_OK;
/* wait request lsn for replay */
//如果系统正在恢复中
if (RecoveryInProgress()) {
//等待请求的LSN日志序列号进行重放
ret_code = XLogWaitForReplay(lsn, timeout);
if (ret_code != REMOTE_READ_OK) {
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("could not redo request lsn.")));
return ret_code;
}
}
//该对象用于标识要读取数据页的关系文件在数据库中的位置
RelFileNode relfilenode{key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode,
key.relfilenode.bucketNode, key.relfilenode.opt};
//如果指针 pblk 不为空,则表示当前正在读取的是备库上的数据块
// 这时需要验证备库中的数据块位置与主库是否一致
if (NULL != pblk) {
//获取分段物理位置信息
SegPageLocation loc = seg_get_physical_location(relfilenode, key.forknum, key.blocknum);
//将loc.extent_size扩展大小转换为一个无符号8位整数
uint8 standby_relNode = (uint8) EXTENT_SIZE_TO_TYPE(loc.extent_size);
BlockNumber standby_block = loc.blocknum;
//验证备库中的关系文件和数据块的位置与主库是否一致
if (standby_relNode != pblk->relNode || standby_block != pblk->block) {
//如果不一致,将报告错误并返回相应的错误码
ereport(ERROR, (errmodule(MOD_REMOTE),
errmsg("Standby page file is invalid! Standby relnode is %u, "
"master is %u; Standby block is %u, master is %u.",
standby_relNode, pblk->relNode, standby_block, pblk->block)));
return REMOTE_READ_BLCKSZ_NOT_SAME;
}
}
//如果 pblk 为空,则表示当前正在读取主库上的数据块,不需要进行验证
//分配了一个大小为 BLCKSZ + VARHDRSZ 的内存空间,并设置 pageData 的头部为该空间的有效大小
bytea* pageData = (bytea*)palloc(BLCKSZ + VARHDRSZ);
SET_VARSIZE(pageData, BLCKSZ + VARHDRSZ);
//判断关系文件节点是否属于分段物理模型
if (IsSegmentPhysicalRelNode(relfilenode)) {
Buffer buffer = InvalidBuffer;
//如果是,则使用 spc_open 函数打开对应的分段空间,并获取关系文件的数据块总数
SegSpace *spc = spc_open(relfilenode.spcNode, relfilenode.dbNode, false, false);
BlockNumber spc_nblocks = spc_size(spc, relfilenode.relNode, key.forknum);
//查给定的数据块号 key.blocknum 是否小于数据块总数 spc_nblocks
if (key.blocknum < spc_nblocks) {
//读取磁盘上对应数据块的内容
buffer = ReadBufferFast(spc, relfilenode, key.forknum, key.blocknum, RBM_FOR_REMOTE);
}
//检查 Buffer 是否有效(即是否成功读取到了数据块)
if (BufferIsValid(buffer)) {
//如果有效,则通过 LockBuffer 将该 Buffer 锁定,并获取其对应的 Block
LockBuffer(buffer, BUFFER_LOCK_SHARE);
Block block = BufferGetBlock(buffer);
//将 Block 的内容拷贝到 pageData 中
errno_t rc = memcpy_s(VARDATA(pageData), BLCKSZ, block, BLCKSZ);
//如果 memcpy_s 函数执行失败
if (rc != EOK) {
//释放之前分配的内存空间 pageData通过 ereport 报告错误
pfree(pageData);
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("memcpy_s error, retcode=%d", rc)));
//将返回值 ret_code 设置为 REMOTE_READ_MEMCPY_ERROR
ret_code = REMOTE_READ_MEMCPY_ERROR;
}
//解锁并释放 Buffer
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
ReleaseBuffer(buffer);
} else {
//没有成功读取到数据块,则将 ret_code 设置为 REMOTE_READ_SIZE_ERROR
ret_code = REMOTE_READ_SIZE_ERROR;
}
} else { //关系文件节点不是分段物理关系
bool hit = false;
//读取指定的数据块
/* read page, if PageIsVerified failed will long jump to PG_CATCH() */
Buffer buf = ReadBufferForRemote(relfilenode, key.forknum, key.blocknum, RBM_FOR_REMOTE, NULL, &hit, pblk);
//如果读取成功,则获取对应的 Buffer
if (BufferIsInvalid(buf)) {
//则通过 ereport 报告错误并设置返回值 ret_code 为 REMOTE_READ_BLCKSZ_NOT_SAME
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("standby page buffer is invalid!")));
return REMOTE_READ_BLCKSZ_NOT_SAME;
}
//通过 LockBuffer 锁定该 Buffer并获取其对应的 Block
LockBuffer(buf, BUFFER_LOCK_SHARE);
Block block = BufferGetBlock(buf);
//将 Block 的内容拷贝到 pageData 中
errno_t rc = memcpy_s(VARDATA(pageData), BLCKSZ, block, BLCKSZ);
//如果 memcpy_s 函数执行失败
if (rc != EOK) {
//释放之前分配的内存空间 pageData通过 ereport 报告错误
pfree(pageData);
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("memcpy_s error, retcode=%d", rc)));
//将返回值 ret_code 设置为 REMOTE_READ_MEMCPY_ERROR
ret_code = REMOTE_READ_MEMCPY_ERROR;
}
//解锁并释放 Buffer
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
ReleaseBuffer(buf);
}
//读取数据块成功的情况下执行
if (ret_code == REMOTE_READ_OK) {
//外部调用者就可以获取到读取到的数据块的内容
*pagedata = pageData;
//计算并设置该页的校验和
PageSetChecksumInplace((Page) VARDATA(*pagedata), key.blocknum);
}
//如果读取操作成功,返回的结果是 REMOTE_READ_OK否则返回的是之前判断出的其他错误码
return ret_code;
}
const int RES_COL_NUM = 2;
//从远程读取文件,并返回结果
Datum gs_read_file_from_remote(PG_FUNCTION_ARGS)
{
RelFileNode rnode; //文件节点信息
RemoteReadFileKey key;
int32 forknum; //分片号
uint32 blockstart; //起始块号
uint64 lsn;
bytea* result = NULL;
Datum values[RES_COL_NUM];
bool nulls[RES_COL_NUM] = {false};
HeapTuple tuple = NULL;
TupleDesc tupdesc;
int ret_code = 0;
int32 timeout;
int parano = 0;
XLogRecPtr current_lsn = InvalidXLogRecPtr;
//检查当前用户是否具有足够的权限执行该函数
if (GetUserId() != BOOTSTRAP_SUPERUSERID) {
//如果不是超级用户,则会抛出错误并中断执行
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files"))));
}
/* handle optional arguments */
//使用 PG_GETARG_* 宏来获取每个参数并将它们存储在对应的变量中
//这些参数是通过调用该函数的 SQL 语句传递给函数的
rnode.spcNode = PG_GETARG_UINT32(parano++);
rnode.dbNode = PG_GETARG_UINT32(parano++);
rnode.relNode = PG_GETARG_UINT32(parano++);
rnode.bucketNode = PG_GETARG_INT32(parano++);
rnode.opt = 0;
forknum = PG_GETARG_INT32(parano++);
blockstart = PG_GETARG_INT32(parano++);
lsn = (uint64)PG_GETARG_TRANSACTIONID(parano++);
timeout = PG_GETARG_INT32(parano++);
if (rnode.spcNode != 1 && rnode.spcNode != 2) {
//存储文件的表空间节点号不是 1 或 2需要获取数据文件
/* get tale data file */
//初始化了一个 key 变量,并将传入的参数赋值给 key 的相应字段
key.relfilenode = rnode;
key.forknum = forknum;
key.blockstart = blockstart;
//检查 forknum 的值是否等于 MAIN_FORKNUM
if (forknum != MAIN_FORKNUM) {
//如果不是,会发出一个警告并返回 NULL
ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
(errmsg("Forknum should be 0. Now is %d. \n", forknum))));
PG_RETURN_NULL();
}
//forknum 等于 MAIN_FORKNUM远程读取文件
//读取指定位置的文件内容
ret_code = ReadFileForRemote(&key, lsn, &result, timeout);
} else {//rnode.spcNode 的值为 1 或 2
//读取与 rnode 相关的文件内容
ret_code = ReadCOrCsnFileForRemote(rnode, &result);
}
if (ret_code != REMOTE_READ_OK) {
//如果文件读取失败,返回 NULL
PG_RETURN_NULL();
}
if (!RecoveryInProgress()) {
//如果当前没有进行恢复操作函数会获取当前的逻辑日志序列号LSN
current_lsn = GetXLogInsertRecPtr();
}
//创建一个元组描述符TupleDesc对象并设置两个字段file 和 lsn
tupdesc = CreateTemplateTupleDesc(RES_COL_NUM, false, TAM_HEAP);
parano = 1;
TupleDescInitEntry(tupdesc, (AttrNumber)parano++, "file", BYTEAOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)parano++, "lsn", XIDOID, -1, 0);
//values 数组存储了对应字段的值nulls 数组表示对应字段是否为 NULL
values[0] = PointerGetDatum(result);
nulls[0] = false;
values[1] = UInt64GetDatum(current_lsn);
nulls[1] = false;
//将函数的返回值转换为合适的格式,并返回给调用者
tupdesc = BlessTupleDesc(tupdesc);
tuple = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
//获取指定文件在远程节点上的大小
Datum gs_read_file_size_from_remote(PG_FUNCTION_ARGS)
{
RelFileNode rnode;
int32 forknum;
uint64 lsn;
int64 size = 0;
int32 timeout;
int parano = 0;
int ret_code = REMOTE_READ_OK;
//检查当前用户 id 是否是 Bootstrap 超级用户
if (GetUserId() != BOOTSTRAP_SUPERUSERID) {
//如果不是,则抛出一个权限不足的错误
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files"))));
}
/* handle optional arguments */
//从输入参数中获取文件所在的 RelFileNode、forknum、lsn 和 timeout 等信息
rnode.spcNode = PG_GETARG_UINT32(parano++);
rnode.dbNode = PG_GETARG_UINT32(parano++);
rnode.relNode = PG_GETARG_UINT32(parano++);
rnode.bucketNode = PG_GETARG_INT32(parano++);
rnode.opt = 0;
forknum = PG_GETARG_INT32(parano++);
lsn = (uint64)PG_GETARG_TRANSACTIONID(parano++);
timeout = PG_GETARG_INT32(parano++);
/* get file size */
//尝试在远程节点上获取指定文件的大小
ret_code = ReadFileSizeForRemote(rnode, forknum, lsn, &size, timeout);
if (ret_code == REMOTE_READ_OK) {
//如果成功获取文件大小,则通过 PG_RETURN_INT64 宏将 size 的值作为函数的返回值返回给调用者
PG_RETURN_INT64(size);
} else {
//如果获取文件大小失败,则返回 null 值
PG_RETURN_NULL();
}
}
//在远程节点上获取指定文件的大小,并将结果通过 res 参数返回
int ReadFileSizeForRemote(RelFileNode rnode, int32 forknum, XLogRecPtr lsn, int64* res, int timeout)
{
SMgrRelation smgr = NULL;
int64 nblock = 0;
int ret_code = REMOTE_READ_OK;
/* wait request lsn for replay */
//检查是否处于恢复状态
if (RecoveryInProgress()) {
//如果正在进行恢复,等待请求的 lsn 进行重放
ret_code = XLogWaitForReplay(lsn, timeout);
//如果等待超时或发生错误,函数会抛出一个错误并返回相应的错误码
if (ret_code != REMOTE_READ_OK) {
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("could not redo request lsn.")));
return ret_code;
}
}
//发送请求进行立即强制的检查点,并等待完成
RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_FORCE | CHECKPOINT_WAIT);
/* check whether the file exists. not exist, return size -1 */
//检查文件是否存在
struct stat statBuf;
//根据 rnode 和 forknum 构建文件路径
char* path = relpathperm(rnode, forknum);
//调用 stat 函数检查文件的状态信息
if (stat(path, &statBuf) < 0 && errno == ENOENT) {
//如果文件不存在,则将 res 设置为 -1并直接返回
*res = -1;
pfree(path);
return ret_code;
}
pfree(path);
//如果文件存在,且不属于分段文件
if (!IsSegmentFileNode(rnode)) {
//打开相应的 SMgrRelation
smgr = smgropen(rnode, InvalidBackendId);
//获取文件的块数量
nblock = smgrnblocks(smgr, forknum);
//关闭所有 SMgrRelation
smgrcloseall();
} else { //如果文件属于分段文件
//打开相应的 SegSpace
SegSpace *spc = spc_open(rnode.spcNode, rnode.dbNode, true, true);
spc_datafile_create(spc, rnode.relNode, forknum);
//获取分段文件的大小
nblock = spc_size(spc, rnode.relNode, forknum);
}
//将 nblock 乘以 BLCKSZ得到文件的大小
*res = nblock * BLCKSZ;
return ret_code;
}
//从远程文件中读取指定块的内容,并将读取到的数据复制到 pageData 中的相应位置
int ReadFileByReadBufferComom(RemoteReadFileKey *key, bytea* pageData, uint32 nblock)
{
int ret_code = REMOTE_READ_OK;
uint32 i = 0;
uint32 j = 0;
uint32 blk_start;
uint32 blk_end;
bool hit = false; //标志是否命中
char* bufBlock = NULL;
errno_t rc;
/* get the segno file block start and block end */
//计算要读取的段号文件块的起始块和终止块
blk_start = key->blockstart;
blk_end = (nblock >= blk_start + MAX_BATCH_READ_BLOCKNUM ? blk_start + MAX_BATCH_READ_BLOCKNUM : nblock);
//使用循环从起始块到终止块,依次读取每个块的内容
for (i = blk_start, j = 0; i < blk_end; i++, j++) {
/* read page, if PageIsVerified failed will long jump */
//来获取访问策略,将策略指针赋给 bstrategy 变量
BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_REPAIR);
//获取指定块的缓冲区,并通过 Buffer 类型的变量 buf 进行引用
Buffer buf = ReadBufferForRemote(key->relfilenode, key->forknum, i, RBM_FOR_REMOTE, bstrategy, &hit, NULL);
//如果 buf 无效,即无法获取有效的缓冲区
if (BufferIsInvalid(buf)) {
//返回错误码 REMOTE_READ_BLCKSZ_NOT_SAME
ereport(WARNING, (errmodule(MOD_REMOTE), errmsg("repair file failed!")));
return REMOTE_READ_BLCKSZ_NOT_SAME;
}
//对缓冲区进行共享锁定
LockBuffer(buf, BUFFER_LOCK_SHARE);
bufBlock = (char*)BufferGetBlock(buf);
//将缓冲区的内容复制到 pageData 的相应位置
rc = memcpy_s(VARDATA(pageData) + j * BLCKSZ, BLCKSZ, bufBlock, BLCKSZ);
//memcpy_s 函数调用失败
if (rc != EOK) {
ereport(WARNING, (errmodule(MOD_REMOTE), errmsg("repair file failed, memcpy_s error, retcode=%d", rc)));
//设置 ret_code 为 REMOTE_READ_MEMCPY_ERROR
ret_code = REMOTE_READ_MEMCPY_ERROR;
//返回之前解锁并释放缓冲区
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
ReleaseBuffer(buf);
return ret_code;
}
//解锁并释放缓冲区
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
ReleaseBuffer(buf);
//每个块的内容复制完成后,为复制的页面计算并设置校验和
PageSetChecksumInplace((Page) (VARDATA(pageData) + j * BLCKSZ), i);
}
//返回 ret_code 表示操作的结果
return ret_code;
}
const int MAX_RETRY_TIMES = 60;
//根据指定的参数从磁盘或者远程位置读取指定块的数据
int ReadFileByReadDisk(SegSpace* spc, RemoteReadFileKey *key, char* bufBlock, BlockNumber blocknum)
{
int ret_code = REMOTE_READ_OK;
int pageStatus; //页面状态
int retryTimes = 0; //重试次数
//如果传入的文件节点是段文件节点
if (IsSegmentFileNode(key->relfilenode)) {
//创建一个伪造的文件节点 fakenode将其属性设置为传入的文件节点属性
// 但将 bucketNode 属性设置为 SegmentBktId
RelFileNode fakenode = {
.spcNode = key->relfilenode.spcNode,
.dbNode = key->relfilenode.dbNode,
.relNode = key->relfilenode.relNode,
.bucketNode = SegmentBktId,
.opt = 0
};
SEG_RETRY:
//从远程位置读取指定块的数据,并将数据存储到 bufBlock 中
seg_physical_read(spc, fakenode, key->forknum, blocknum, (char *)bufBlock);
retryTimes++;
//检查页面的校验和是否匹配
if (PageIsVerified((Page)bufBlock, blocknum)) {
//如果匹配,则将页面状态设置为 SMGR_RD_OK
pageStatus = SMGR_RD_OK;
} else {
//如果校验和不匹配,将页面状态设置为 SMGR_RD_CRC_ERROR
pageStatus = SMGR_RD_CRC_ERROR;
//如果重试次数小于最大重试次数
if (retryTimes < MAX_RETRY_TIMES) {
/* sleep 10ms */
pg_usleep(10000L);
//等待 10 毫秒,并跳转到标签 SEG_RETRY 处再次尝试读取数据
goto SEG_RETRY;
} else { //如果重试次数达到最大重试次数
//释放 bufBlock 的内存,并输出警告信息
pfree(bufBlock);
ereport(WARNING, (errmodule(MOD_REMOTE),
errmsg("repair file failed, read page crc check error, page: %u/%u/%u/%d, "
"forknum is %d, block num is %u", key->relfilenode.spcNode, key->relfilenode.dbNode,
key->relfilenode.relNode, key->relfilenode.bucketNode, key->forknum, blocknum)));
//将返回码 ret_code 设置为 REMOTE_READ_CRC_ERROR并返回该返回码
ret_code = REMOTE_READ_CRC_ERROR;
return ret_code;
}
}
} else { //传入的文件节点不是段文件节点
//打开指定文件节点对应的存储管理器关系对象 smgr
SMgrRelation smgr = smgropen(key->relfilenode, InvalidBackendId);
/* standby read page, replay finish, there will be no synchronous changes. */
//从磁盘读取指定块的数据,并将数据存储到 bufBlock 中
pageStatus = smgrread(smgr, key->forknum, blocknum, (char *)bufBlock);
retryTimes++;
//检查页面状态是否为 SMGR_RD_OK
if (pageStatus != SMGR_RD_OK) {
//如果不是,则释放 bufBlock 的内存,并输出警告信息
pfree(bufBlock);
ereport(WARNING, (errmodule(MOD_REMOTE),
errmsg("repair file failed, read page crc check error, page: %u/%u/%u/%d, "
"forknum is %d, block num is %u", key->relfilenode.spcNode, key->relfilenode.dbNode,
key->relfilenode.relNode, key->relfilenode.bucketNode, key->forknum, blocknum)));
//然后将返回码 ret_code 设置为 REMOTE_READ_CRC_ERROR
ret_code = REMOTE_READ_CRC_ERROR;
//关闭存储管理器关系对象 smgr
smgrclose(smgr);
return ret_code;
}
//关闭存储管理器关系对象 smgr
smgrclose(smgr);
}
return ret_code;
}
//远程读取指定文件的数据块并将读取的数据存储到bytea类型的变量中
int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea** fileData, int timeout)
{
int ret_code = REMOTE_READ_OK;
SMgrRelation smgr = NULL;
SegSpace* spc = NULL;
bytea* pageData = NULL;
char* bufBlock = NULL;
uint32 nblock = 0;
uint32 blk_start = 0;
uint32 blk_end = 0;
uint32 i = 0;
uint32 j = 0;
errno_t rc;
/* wait request lsn for replay */
//检查是否在恢复模式中
if (RecoveryInProgress()) {
//如果是则等待请求的日志序列号lsn进行重放
ret_code = XLogWaitForReplay(lsn, timeout);
if (ret_code != REMOTE_READ_OK) {
//如果超时或出错,返回错误代码
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("could not redo request lsn.")));
return ret_code;
}
}
//如果在恢复模式中或者目标文件是段文件节点
if (RecoveryInProgress() || IsSegmentFileNode(key->relfilenode)) {
//发送请求进行检查点
RequestCheckpoint(CHECKPOINT_WAIT | CHECKPOINT_FORCE | CHECKPOINT_IMMEDIATE);
}
/* get block num */
//如果不是段文件节点
if (!IsSegmentFileNode(key->relfilenode)) {
//打开对应的SMgrRelation
smgr = smgropen(key->relfilenode, InvalidBackendId);
//获取块数
nblock = smgrnblocks(smgr, key->forknum);
//关闭SMgrRelation
smgrclose(smgr);
} else { //如果是段文件节点
//打开对应的SegSpace
spc = spc_open(key->relfilenode.spcNode, key->relfilenode.dbNode, false, false);
if (!spc) {
ereport(WARNING, (errmodule(MOD_REMOTE),
errmsg("Spc open failed. spcNode is: %u, dbNode is %u",
key->relfilenode.spcNode, key->relfilenode.dbNode)));
return REMOTE_READ_IO_ERROR;
}
//获取块数
nblock = spc_size(spc, key->relfilenode.relNode, key->forknum);
}
//检查请求的起始块blockstart是否超出文件块数
if (nblock <= key->blockstart) {
ret_code = REMOTE_READ_SIZE_ERROR;
return ret_code;
}
/* get the segno file block start and block end */
//根据起始块和最大批量读取块数计算出要读取的段segno的起始块blk_start和结束块blk_end
blk_start = key->blockstart;
blk_end = (nblock >= blk_start + MAX_BATCH_READ_BLOCKNUM ? blk_start + MAX_BATCH_READ_BLOCKNUM : nblock);
//分配用于存储读取的数据的内存空间
pageData = (bytea*)palloc((blk_end - blk_start) * BLCKSZ + VARHDRSZ);
SET_VARSIZE(pageData, ((blk_end - blk_start) * BLCKSZ + VARHDRSZ));
/* primary read page, need read page by ReadBuffer_common */
//如果不是段文件节点并且不在恢复模式中
if (!IsSegmentFileNode(key->relfilenode) && !RecoveryInProgress()) {
//读取主数据节点的页数据
ret_code = ReadFileByReadBufferComom(key, pageData, nblock);
if (ret_code != REMOTE_READ_OK) {
//如果读取失败,则释放分配的内存空间,返回错误代码
pfree(pageData);
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("read file failed!")));
return ret_code;
}
} else { //如果是段文件节点或者在恢复模式中
// 判断是否使用ADIOAsynchronous Direct I/O读取数据
ADIO_RUN()
{
//如果是则使用adio_align_alloc函数分配对齐的内存块bufBlock大小为BLCKSZ
bufBlock = (Page)adio_align_alloc(BLCKSZ);
}
ADIO_ELSE()
{
//如果不是则使用palloc函数分配内存块bufBlock大小为BLCKSZ
bufBlock = (Page)palloc(BLCKSZ);
}
ADIO_END();
//用循环从起始块blk_start到结束块blk_end依次读取数据
for (i = blk_start, j = 0; i < blk_end; i++, j++) {
//并存储到bufBlock中
ret_code = ReadFileByReadDisk(spc, key, bufBlock, i);
if (ret_code != REMOTE_READ_OK) {
//读取失败则释放分配的内存块bufBlock和pageData
pfree(bufBlock);
pfree(pageData);
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("repair file failed, read block %u error, retcode=%d",
i, rc)));
//返回错误代码
return ret_code;
}
//使用memcpy_s函数将bufBlock中的数据复制到pageData的合适位置上
rc = memcpy_s(VARDATA(pageData) + j * BLCKSZ, BLCKSZ, bufBlock, BLCKSZ);
if (rc != EOK) {
//复制失败则释放分配的内存块bufBlock和pageData返回错误代码
pfree(bufBlock);
pfree(pageData);
ret_code = REMOTE_READ_MEMCPY_ERROR;
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("repair file failed, memcpy_s error, retcode=%d", rc)));
return ret_code;
}
}
//释放分配的内存块bufBlock
pfree(bufBlock);
//如果读取失败则释放分配的内存空间pageData返回错误代码
if (ret_code != REMOTE_READ_OK) {
pfree(pageData);
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("read file failed!")));
return ret_code;
}
}
//将读取的数据赋值给fileData指针
*fileData = pageData;
//返回读取结果代码
return ret_code;
}
const int REGR_MCR_SIZE_1MB = 1048576;
const int CLOG_NODE = 1;
const int CSN_NODE = 2;
//读取指定文件根据RelFileNode确定的数据并将数据存储到bytea类型的变量中
int ReadCOrCsnFileForRemote(RelFileNode rnode, bytea** fileData)
{
uint32 flags = O_RDWR | PG_BINARY;
int fd = -1;
char* logType = NULL;
char* path = (char*)palloc0(MAX_PATH);
errno_t rc;
uint32 logSize = 16 * REGR_MCR_SIZE_1MB;
char *buffer = (char*)palloc(logSize);
int result = -1;
//根据给定的文件类型rnode.spcNode确定文件所在的路径logType
if (rnode.spcNode == CLOG_NODE) {
logType = "pg_clog";
} else if (rnode.spcNode == CSN_NODE) {
logType = "pg_csnlog";
} else {
ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), errmsg("File type\"%u\" does not exist, stop read here.",
rnode.spcNode)));
}
//构建完整的文件路径path格式为"logType/relNode"其中relNode是文件的节点号
rc = snprintf_s(path, MAX_PATH, MAX_PATH - 1, "%s/%012u", logType, rnode.relNode);
securec_check_ss(rc, "\0", "\0");
//调用BasicOpenFile函数以读写模式打开文件并获取文件描述符fd
fd = BasicOpenFile(path, flags, S_IWUSR | S_IRUSR);
if (fd < 0) {
//如果打开失败
pfree(buffer);
if (errno != ENOENT) { //且错误码不是ENOENT
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", path)));
}
// The file does not exist, break.
ereport(LOG,
(errmodule(MOD_SEGMENT_PAGE), errmsg("File \"%s\" does not exist, stop read here.", path)));
pfree(path);
//则报错并返回-1
return -1;
}
//为了记录这次读取文件数据的等待事件
pgstat_report_waitevent(WAIT_EVENT_DATA_FILE_READ);
//分配用于存储文件数据的内存空间buffer大小为logSize其中logSize默认为16 MB
//使用pread函数从文件中读取数据将读取的数据存储到buffer中。读取的字节数由nbytes返回
uint32 nbytes = pread(fd, buffer, logSize, 0);
//结束等待事件的记录
pgstat_report_waitevent(WAIT_EVENT_END);
if (close(fd)) { //关闭文件,并检查关闭是否成功
pfree(path);
pfree(buffer);
ereport(ERROR, (errcode_for_file_access(), errmsg("could not close file \"%s\": %m", path)));
}
//检查实际读取的字节数nbytes是否大于logSize
if (nbytes > logSize) {
//如果是,则表示读取的数据超过了预设的大小
pfree(buffer);
ereport(ERROR,
(errcode(MOD_SEGMENT_PAGE),
errcode_for_file_access(),
errmsg("could not read file %s. nbytes:%u, logSize:%u", path, nbytes, logSize)));
pfree(path);
//报错并返回-1
return -1;
} else {
//分配用于存储读取的数据的内存空间pageData大小为nbytes + VARHDRSZ其中VARHDRSZ是页头变长字段的大小
bytea* pageData = (bytea*)palloc(nbytes + VARHDRSZ);
SET_VARSIZE(pageData, (nbytes + VARHDRSZ));
//将读取的数据从buffer复制到pageData中
rc = memcpy_s(VARDATA(pageData), nbytes, buffer, nbytes);
if (rc != EOK) {
pfree(path);
pfree(pageData);
pfree(buffer);
ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("repair file failed, memcpy_s error, retcode=%d", rc)));
return -1;
} else { //将pageData赋值给fileData指针
*fileData = pageData;
result = 0;
}
}
//释放不再使用的内存空间
pfree(path);
pfree(buffer);
//返回读取结果代码
return result;
}