openGauss极致RTO流程讲解及运维方法
极致RTO简单介绍
极致RTO是openGauss在并行回放的基础上,实现的一个加速回放功能。其主要原理是将record粒度的日志拆分成block粒度的日志进行回放,通过增加流水线和redoworker线程的数量,并且利用相关hash算法,保证同一张表的日志由同一流水线回放,以此提高回放速度和回放并发度。
极致RTO相关参数
recovery_parse_workers > 1 或 recovery_redo_workers > 1,即为开启极致RTO.
recovery_parse_workers
参数说明: 是极致RTO特性中ParseRedoRecord线程的数量。
该参数属于POSTMASTER类型参数,请参考表1中对应设置方法进行设置。
取值范围:整型,1~16
仅在开启极致RTO情况下可以设置recovery_parse_workers为>1。需要配合recovery_redo_workers使用。若同时开启recovery_parse_workers和recovery_max_workers,以开启极致RTO的recovery_parse_workers为准,并行回放特性失效。因极致RTO不支持主备从模式,仅在参数replication_type设置成1时可以设置recovery_parse_workers为>1。另外,极致RTO也不支持列存,在已经使用列存表或者即将使用列存表的系统中,请关闭极致RTO。极致RTO不再自带流控,流控统一由recovery_time_target参数来控制。
默认值: 1
recovery_redo_workers
参数说明: 是极致RTO特性中每个ParseRedoRecord线程对应的PageRedoWorker数量。
该参数属于POSTMASTER类型参数,请参考表1中对应设置方法进行设置。
取值范围:整型,1~8
需要配合recovery_parse_workers使用。在配合recovery_parse_workers使用时,只有recovery_parse_workers大于1,recovery_redo_workers参数才生效。
默认值: 1
recovery_parallelism
参数说明: 查询实际回放线程个数,该参数为只读参数,无法修改。
该参数属于POSTMASTER类型参数,受recovery_max_workers以及recovery_parse_workers参数影响,任意一值大于0时,recovery_parallelism将被重新计算。
取值范围: 整型,1~2147483647
exrto_standby_read_opt
参数说明:支持极致RTO备机读优化,默认开启。主机和备机间不同步该参数。 该参数属于POSTMASTER类型参数,请参考表1中对应设置方法进行设置。(该功能在社区还属于创新版) (传统HA模式特有参数,资源池化模式原生支持极致RTO+备机读)
取值范围:布尔型。
- on表示开启优化。
- off表示关闭优化。
默认值:on
极致RTO回放线程类型
REDO_BATCH 负责record粒度的日志拆分成block粒度,并分发给redo manager线程
REDO_PAGE_MNG, 负责向redo worker线程分发xlog
REDO_PAGE_WORKER, 负责回放
REDO_TRXN_MNG, 负责向trxn worker分发xlog
REDO_TRXN_WORKER, 负责回放事务日志
REDO_READ_WORKER, 流复制场景,用于从磁盘读xlog
REDO_READ_PAGE_WORKER, 负责从磁盘读取xlog或从xlog缓冲区读取xlog,并校验完整性
REDO_READ_MNG, 负责控制读xlog线程
REDO_SEG_WORKER, 按需回放新增线程, (非资源池化模式不涉及)
REDO_HTAB_MNG, 按需回放新增线程, (非资源池化模式不涉及)
REDO_CTRL_WORKER, 按需回放新增线程,(非资源池化模式不涉及)
Startup线程:负责将日志分发给对应的流水线
传统HA模式回放流程示意图
主备同步场景,极致RTO处理流程
传统HA场景,用户在主机执行写业务时,backend线程(业务线程)在修改数据时,会先通过wal writer线程向磁盘中写入xlog(也称wal日志、预写日志),然后主机会通过wal sender线程从磁盘中读取xlog日志,发送到备机,由备机的wal receiver接收到后写入到备机的磁盘中。备机将xlog接受到后,由XLogReadWorker线程读取xlog,发送给Startup线程,并分发给回放线程进行回放。在相关的日志回放完成后,用户可以从备机读到相关的数据,以此完成主备节点之间的同步。
主备故障、备机failover,极致RTO处理流程
主机故障场景,需要备机通过failover进行故障恢复后升主。在此期间,由于主机故障,新主节点的wal receiver线程会退出,XLogReadPageWorker线程会继续从磁盘中把还没读取的xlog继续读完,发送给Startup线程,并分发给回放线程进行回放。当所有的已有xlog全部读取并回放完,新主节点才完成failover阶段,此时该节点正式升为主节点。
极致RTO流水线示意图
极致RTO回放功能大体流程如下所示:极致RTO是多流水线、多线程架构,由同一xlog读取线程从磁盘中读取,然后经由Startup分发给不同的回放流水线,再由BatchRedo线程将xlog解析为block粒度的日志**(与并行回放的主要差别)**,最后经由RedoManager线程分发给具体的回放线程完成回放。通过特定的hash算法,可以保证同一个表相同block的日志会路由到同一流水线的同一RedoWorker线程回放,以保证日志回放时有序。
所有回放线程之间通过一种无锁队列SPSCBlockingQueue完成xlog在流水线中的分发,每两个互相存在上下级关系的回放线程之间都有一个独有的SPSCBlockingQueue,上级流水线处理后将xlog日志SPSCBlockingQueue,由下级从SPSCBlockingQueue中循环读取并处理。
大体分发流程如下:
- 备机的XLogReadWorker线程或XLogReadPageWorker线程从磁盘中按条读取xlog日志,并将读取到的xlog发送给Startup线程,Startup线程会根据xlog的类型,和修改的表的oid等信息选择对应的流水线分发xlog。
- 事务日志会通过Startup线程->TrxnManagerMain->TrxnWorkerMain的流程完成回放;
- 页面修改的日志会通过Startup->BatchRedoMain->RedoPageManagerMain->RedoPageWorker,完成分发、xlog细粒度化解析、二次分发、回放的处理流程。
回放线程数量计算方式
以recovery_redo_workers = 4, recovery_parse_workers = 4 为例,recovery_parallelism = (BatchRedoMain + RedoPageManagerMain + RedoPageWorkerMain * 4) * 4 + TxnManageMain + TrxnWorkerMain + XLogReadPageWorkerMain + XLogReadWorkerMain + XLogReadManagerMain = 29
各线程功能简述
REDO_READ_WORKER
主函数:XLogReadWorkerMain
流复制场景,用于从磁盘读xlog,该线程会把xlog从磁盘读取到g_recordbuffer->xlogsegarray[readindex],read buffer中。
代码流程
XLogReadWorkerMain->XLogReadWorkRun->XLogReadFromWriteBuffer
- 获取wal receiver线程从备机接收并刷盘的xlog最新位置。
receivedUpto = GetWalRcvWriteRecPtr(NULL);
- 获取xlogreadworker线程已经读取到的xlog位置
uint32 readindex = pg_atomic_read_u32(&(g_recordbuffer->readindex));
RecordBufferAarray *readseg = &g_recordbuffer->xlogsegarray[readindex];
- 通过比较receivedUpto 和readseg判断有xlog要读
- 如果有就调用XLogReadFromWriteBuffer读取xlog,并将xlog写到g_recordbuffer->xlogsegarray[readindex]。
REDO_READ_PAGE_WORKER
XLogReadPageWorkerMain,循环读取xlog,将读到的xlog record,解析为XLogReaderState,并置入g_dispatcher->readLine.readPageThd->queue传递给startup线程
- 流复制场景(传统HA模式),
- failover阶段,IsRecoveryDone == false,从磁盘读xlog。
- 备机normal状态,IsRecoveryDone == true,用于从g_recordbuffer->xlogsegarray[readindex]缓冲区读取日志。
- 资源池化场景,XLogReadPageWorkerMain同时负责读取xlog和解析xlog传递给startup线程。
代码流程
xlog读取主流程
XLogReadPageWorkerMain->XLogParallelReadNextRecord->ParallelReadRecord->ParallelReadPageInternal->ParallelXLogPageRead
读xlog的场景分类
- 流复制场景(传统主备):ParallelXLogReadWorkBufRead->XLogPageReadForExtRto
- 资源池化,从DSS读xlog文件:SSXLogPageRead
- 非池化,通过文件系统读xlog文件:ParallelXLogPageReadFile
XLogReadPageWorkerMain循环流程
-
读xlog:XLogParallelReadNextRecord
-
生成下一个readerState: NewReaderState
-
更新读取最新xlog的起止点:
g_redoWorker->lastReplayedReadRecPtr = xlogreader->ReadRecPtr;
g_redoWorker->lastReplayedEndRecPtr = xlogreader->EndRecPtr;
-
更新g_GlobalLsnForwarder:SendLsnFowarder
-
把读到的xlog(XLogReaderState)存入队列:PutRecordToReadQueue
StartupXLOG
代码流程
循环执行以下流程直到XLogReaderState == NULL
- 从队列中读取下一条xlog:ReadNextXLogRecord->ReadNextRecordFromQueue->SPSCBlockingQueueTake(g_dispatcher->readLine.readPageThd->queue)
- 读取获取到record后解析xlog,从XLogReaderState解析到XLogRecord,DecodeXLogRecord
- 如果读到g_redoEndMark.record,则退出循环
- 如果读取到g_GlobalLsnForwarder.record或g_cleanupMark.record,调用StartupSendFowarder,向所有batchRedo线程和txnmanager线程分发该日志
- 将xlog解析成RedoItem ,并分发给下一级流水线线程
- DispatchRedoRecord->ExtremeDispatchRedoRecordToFile->g_dispatchTable[rmid].rm_dispatch(record, expectedTLIs, recordXTime);
- 根据XLogReaderState 偏移获取RedoItem : RedoItem *item = GetRedoItemPtr(record);
- 根据xlog的类型(rmid),选择对应的分发方法:g_dispatchTable[rmid].rm_dispatch(record, expectedTLIs, recordXTime);
- 普通日志通常分发给batchRedo线程(g_dispatcher->pageLines[i].batchThd)。其中,同一个表的日志一般发给某个特定流水线,会通过rnode计算流水线Id:GetSlotId
- 事务日志分发给txnmanager线程。(g_dispatcher->trxnLine.managerThd),对应函数:DispatchXactRecord
- ddl相关日志,如果是create table则只分发给对应的流水线、如果是truncate则会分发给所有的流水线,对应函数:DispatchSmgrRecord
- 不同类型的xlog分发方法参考
- src\gausskernel\storage\access\transam\extreme_rto\dispatcher.cpp
- static const RmgrDispatchData g_dispatchTable[RM_MAX_ID + 1]
分发策略:
极致RTO通过ExtremeDispatchRedoRecordToFile函数向下级流水线分发
具体分发方法会根据xlog类型rmid选择对应的分发算法g_dispatchTable[rmid].rm_dispatch(record, expectedTLIs, recordXTime);
在选择流水线时,为了尽量避免回放时的页面交换问题,极致RTO根据relfilenode计算hash值,该hash值用于选择哪一条流水线向下分发日志。对应函数:GetWorkerIds。
BatchRedoMain
循环把record级别的xlog(RedoItem)解析成Block级别的xlog(XLogRecParseState),并传递给PageRedoManager线程进行回放。
- 从流水线中循环读取出xlog:BatchRedoMain->SPSCBlockingQueueGetAll(g_redoWorker->queue, &eleArry, &eleNum)
- 把record级别的xlog(RedoItem)解析成Block级别的xlog(XLogRecParseState): BatchRedoDistributeItems->BatchRedoParseItemAndDispatch->XLogParseToBlockForExtermeRTO->g_xlogParseBlockTable[rmid].xlog_parseblock(record, blocknum)
- 把 Block级别的xlog分发给下一级流水线:AddPageRedoItem(myRedoLine->managerThd, recordblockstate);
图中是一条由pg_xlogdump解析出来的xlog日志,该条xlog是一个XLOG_HEAP_UPDATE类型日志,该条xlog涉及到两个跨页面的tuple的修改,即将原tuple(663/15825/15213/-1/0-0 blkno 24 off 41 置为无效),并插入一条新tuple(1663/15825/15213/-1/0-0 blkno 22 off 10)。BatchRedo线程会将该日志拆分为两条block粒度的xlog(XLogRecParseState):
- 对页面rel 1663/15825/15213/-1/0, forknum:0 storage HEAP DISK blk 24,元组 off 41的修改
- 对页面rel 1663/15825/15213/-1/0, forknum:0 storage HEAP DISK blk 22,元组 off 10 的修改。
然后把以上解析后的日志分发给下级流水线。
REDO @ 0/80AA0E8; LSN 0/80AA1F0: prev 0/80A9F78; xid 14259; term 1; len 5; total 260; crc 2577088048; desc: Heap - XLOG_HEAP_UPDATE update: off 41 new off 10, blkref #0: rel 1663/15825/15213/-1/0, forknum:0 storage HEAP DISK blk 24 lastlsn 0/7CF57A8, blkref #1: rel 1663/15825/15213/-1/0, forknum:0 storage HEAP DISK blk 22 lastlsn 0/80AA0E8
RedoPageManagerMain
把XLogRecParseState根据rnode等信息,分发给RedoPageWorker线程,
- RedoPageManagerMain->PageManagerRedoDistributeItems->PageManagerRedoParseState->根据日志类型选择解析方式
- 通过将xlog置入无锁队列分发给下级流水线:AddPageRedoItem(myRedoLine->redoThd[work_id], record_block_state);
RedoPageWorkerMain
回放上一层流水线下发的日志。不同xlog类型执行函数不同
RedoPageWorkerMain->XLogBlockRedoForExtremeRTO->g_xlogExtRtoRedoTable[block_valid].xlog_redoextrto(blockhead, blockrecbody, bufferinfo);
一些关键变量
特殊xlog标记
RedoItem g_redoEndMark = { false, false, NULL, 0, NULL, 0 };
RedoItem g_terminateMark = { false, false, NULL, 0, NULL, 0 };
RedoItem g_GlobalLsnForwarder;
RedoItem g_cleanupMark;
RedoItem g_closefdMark;
RedoItem g_cleanInvalidPageMark;
不同类型XLOG的处理函数
极致RTOxlog分发函数
src\gausskernel\storage\access\transam\extreme_rto\dispatcher.cpp
static const RmgrDispatchData g_dispatchTable[RM_MAX_ID + 1]
极致RTO batchRedo线程解析函数
src\gausskernel\storage\access\redo\redo_xlogutils.cpp
static const XLogParseBlock g_xlogParseBlockTable[RM_MAX_ID + 1] = {
极致RTO 回放函数
src\gausskernel\storage\access\redo\redo_xlogutils.cpp
static const XLogBlockRedoExtreRto g_xlogExtRtoRedoTable[BLOCK_DATA_CSNLOG_TYPE + 1] = {
{ XLogBlockDataCommonRedo, BLOCK_DATA_MAIN_DATA_TYPE }, { XLogBlockVmCommonRedo, BLOCK_DATA_VM_TYPE },
{ XLogBlockUndoCommonRedo, BLOCK_DATA_UNDO_TYPE },
{ XLogBlockFsmCommonRedo, BLOCK_DATA_FSM_TYPE }, { XLogBlockDdlCommonRedo, BLOCK_DATA_DDL_TYPE },
{ XLogBlockBcmCommonRedo, BLOCK_DATA_BCM_TYPE }, { XLogBlockNewCuCommonRedo, BLOCK_DATA_NEWCU_TYPE },
{ XLogBlockClogCommonRedo, BLOCK_DATA_CLOG_TYPE }, { XLogBlockCsnLogCommonRedo, BLOCK_DATA_CSNLOG_TYPE },
};
回放相关视图
select * from dbe_perf.GLOBAL_REDO_STATUS
部分回放信息展示
名称 | 类型 | 描述 |
---|---|---|
node_name | text | 数据库进程名称。 |
redo_start_ptr | bigint | 当前实例日志回放的起始点。 |
redo_start_time | bigint | 当前实例日志回放的起始UTC时间。 |
redo_done_time | bigint | 当前实例日志回放的结束UTC时间。 |
curr_time | bigint | 当前实例的当前UTC时间。 |
min_recovery_point | bigint | 当前实例日志的最小一致性点位置。(在此之前的日志都回放完毕) |
read_ptr | bigint | 当前实例日志的读取位置。 |
last_replayed_read_ptr | bigint | 当前实例的日志回放位置。(当前实例回放的最新日志) |
recovery_done_ptr | bigint | 当前实例启动完成时的回放位置。 |
speed | bigint | 当前实例日志回放速率,单位:KB/s。 |
local_max_ptr | bigint | 当前实例启动成功后walreceiver线程收到的回放日志的最大值。 |
primary_flush_ptr | bigint | 主机落盘日志的位置。 |
select * from pg_last_xlog_replay_location()
当前xlog的回放位置
openGauss=# select * from pg_last_xlog_replay_location();
term | lsn
------+-----------
1 | 0/84773B8
(1 row)
gs_ctl query可以查询备机的回放进度
其中receiver_replay_location表示备机的回放进度。
[zhoucong_normal@openGauss111 pg_xlog]$ gs_ctl query -D /home/zhoucong_normal/work/data-list/1p2s-6.0.0/node1
[2024-10-29 11:51:16.398][772625][][gs_ctl]: gs_ctl query ,datadir is /home/zhoucong_normal/work/data-list/1p2s-6.0.0/node1
HA state:
local_role : Standby
static_connections : 2
db_state : Normal
detail_information : Normal
Senders info:
No information
Receiver info:
receiver_pid : 2139555
local_role : Standby
peer_role : Primary
peer_state : Normal
state : Normal
sender_sent_location : 0/8822518
sender_write_location : 0/8822518
sender_flush_location : 0/8822518
sender_replay_location : 0/8822518
receiver_received_location : 0/8822518
receiver_write_location : 0/8822518
receiver_flush_location : 0/8822518
receiver_replay_location : 0/8822478
sync_percent : 100%
channel : 20.20.20.111:46912<--20.20.20.111:36144
: 0/8822518
receiver_received_location : 0/8822518
receiver_write_location : 0/8822518
receiver_flush_location : 0/8822518
receiver_replay_location : 0/8822478
sync_percent : 100%
channel : 20.20.20.111:46912<–20.20.20.111:36144