PolarDB PostgreSQL logindex 设计

背景介绍

PolarDB采用了共享存储一写多读架构,读写节点RW和多个只读节点RO共享同一份存储,读写节点可以读写共享存储中的数据;只读节点仅能各自通过回放日志,从共享存储中读取数据,而不能写入,只读节点RO通过内存同步来维护数据的一致性。此外,只读节点可同时对外提供服务用于实现读写分离与负载均衡,在读写节点异常crash时,可将只读节点提升为读写节点,保证集群的高可用。基本架构图如下所示:

传统share nothing的架构下,只读节点RO有自己的内存及存储,只需要接收RW节点的WAL日志进行回放即可。如下图所示,如果需要回放的数据页不在Buffer Pool中,需将其从存储文件中读至Buffer Pool中进行回放,从而带来CacheMiss的成本,且持续性的回放会带来较频繁的Buffer Pool淘汰问题。

此外,RW节点多个事务之间可并行执行,RO节点则需依照WAL日志的顺序依次进行串行回放,导致RO回放速度较慢,与RW节点的延迟逐步增大。

与传统share nothing架构不同,共享存储一写多读架构下RO节点可直接从共享存储上获取需要回放的WAL日志。若共享存储上的数据页是最新的,那么RO可直接读取数据页而不需要再进行回放操作。基于此,PolarDB设计了LogIndex来加速RO节点的日志回放。

RO内存同步架构

LogIndex中保存了数据页与修改该数据页的所有LSN的映射关系,基于LogIndex可快速获取到修改某个数据页的所有LSN,从而可将该数据页对应日志的回放操作延迟到真正访问该数据页的时刻进行。LogIndex机制下RO内存同步的架构如下图所示。

RW/RO的相关流程相较传统share nothing架构下有如下区别:

  • 读写节点RW与只读节点RO之间不再传输完整的WAL日志,仅传输WAL Meta,减少网络数据传输量,降低了RO与RW节点的延迟;
  • 读写节点RW依据WAL meta生成LogIndex写入LogIndex Memory Table中,LogIndex Memory Table写满之后落盘,保存至共享存储的LogIndex Table中,已落盘的LogIndex Memory Table可以被复用;
  • 读写节点RW通过LogIndex Meta文件保证LogIndex Memory Table IO操作的原子性,LogIndex Memory Table落盘后会更新LogIndex Meta文件,落盘的同时还会生成Bloom Data,通过Bloom Data可快速检索特定Page是否存在于某LogIndex Table中,从而忽略不必扫描的LogIndex Table提升效率;
  • 只读节点RO接收RW所发送的WAL Meta,并基于WAL Meta在内存中生成相应的LogIndex,同样写入其内存的LogIndex Memory Table中,同时将WAL Meta对应已存在于Buffer Pool中的页面标记为Outdate,该阶段RO节点并不进行真正的日志回放,无数据IO操作,可去除cache miss的成本;
  • 只读节点RO基于WAL Meta生成LogIndex后即可推进回放位点,日志回放操作被交由背景进程及真正访问该页面的backend进程执行,由此RO节点也可实现日志的并行回放;
  • 只读节点RO生成的LogIndex Memory Table不会落盘,其基于LogIndex Meta文件判断已满的LogIndex Memory Table是否在RW节点已落盘,已落盘的LogIndex Memory Table可被复用,当RW节点判断存储上的LogIndex Table不再使用时可将相应的LogIndex Table Truncate。

PolarDB通过仅传输WAL Meta降低RW与RO之间的延迟,通过LogIndex实现WAL日志的延迟回放+并行回放以加速RO的回放速度,以下则对这两点进行详细介绍。

WAL Meta

WAL日志又称为XLOG Record,如下图,每个XLOG Record由两部分组成:

  • 通用的首部部分general header portion:该部分即为XLogRecord结构体,固定长度。主要用于存放该条XLOG Record的通用信息,如XLOG Record的长度、生成该条XLOG Record的事务ID、该条XLOG Record对应的资源管理器类型等;
  • 数据部分data portion:该部分又可以划分为首部和数据两个部分,其中首部部分header part包含0~N个XLogRecordBlockHeader结构体及0~1个XLogRecordDataHeader[Short|Long]结构体。数据部分data part则包含block data及main data。每一个XLogRecordBlockHeader对应数据部分的一个Block data,XLogRecordDataHeader[Short|Long]则与数据部分的main data对应。

共享存储模式下,读写节点RW与只读节点RO之间无需传输完整的WAL日志,仅传输WAL Meta数据,WAL Meta即为上图中的general header portion + header part + main data,RO节点可基于WAL Meta从共享存储上读取完整的WAL日志内容。该机制下,RW与RO之间传输WAL Meta的流程如下:

 

  1. 当RW节点中的事务对其数据进行修改时,会生成对应的WAL日志并将其写入WAL Buffer,同时拷贝对应的WAL meta数据至内存中的WAL Meta queue中;
  2. 同步流复制模式下,事务提交时会先将WAL Buffer中对应的WAL日志flush到磁盘,此后会唤醒WalSender进程;
  3. WalSender进程发现有新的日志可以发送,则从WAL Meta queue中读取对应的WAL Meta,通过已建立的流复制连接发送到对端的RO;
  4. RO的WalReceiver进程接收到新的日志数据之后,将其push到内存的WAL Meta queue中,同时通知Startup进程有新的日志到达;
  5. Startup从WAL Meta queue中读取对应的meta数据,解析生成对应的LogIndex memtable即可。

RW与RO节点的流复制不传输具体的payload数据,减少了网络数据传输量;此外,RW节点的WalSender进程从内存中的WAL Meta queue中获取WAL Meta信息,RO节点的WalReceiver进程接收到WAL Meta后也同样将其保存至内存的WAL Meta queue中,相较于传统主备模式减少了日志发送及接收的磁盘IO过程,从而提升传输速度,降低RW与RO之间的延迟。

LogIndex

内存数据结构

LogIndex实质为一个HashTable结构,其key为PageTag,可标识一个具体数据页,其value即为修改该page的所有LSN。LogIndex的内存数据结构如下图所示,除了Memtable ID、Memtable保存的最大LSN、最小LSN等信息,LogIndex Memtable中还包含了三个数组,分别为:

  • HashTable:HashTable数组记录了某个Page与修改该Page的LSN List的映射关系,HashTable数组的每一个成员指向Segment数组中一个具体的LogIndex Item;
  • Segment:Segment数组中的每个成员为一个LogIndex Item,LogIndex Item有两种结构,即下图中的Item Head和Item Seg,Item Head为某个Page对应的LSN链表的头部,Item Seg则为该LSN链表的后续节点。Item Head中的Page TAG用于记录单个Page的元信息,其Next Seg和Tail Seg则分别指向后续节点和尾节点,Item Seg存储着指向上一节点Prev Seg和后续节点Next Seg的指针。Item Head和Item Seg中保存的Suffix LSN与LogIndex Memtable中保存的Prefix LSN可构成一个完整的LSN,避免了重复存储Prefix LSN带来的空间浪费。当不同Page TAG计算到HashTable的同一位置时,通过Item Head中的Next Item指向下一个具有相同hash值的Page,以此解决哈希冲突;
  • Index Order:Index Order数组记录了LogIndex添加到LogIndex Memtable的顺序,该数组中的每个成员占据2个字节。每个成员的后12bit对应Segment数组的一个下标,指向一个具体的LogIndex Item,前4bit则对应LogIndex Item中Suffix LSN数组的一个下标,指向一个具体的Suffix LSN,通过Index Order可方便地获取插入到该LogIndex Memtable的所有LSN及某个LSN与其对应修改的全部Page的映射关系。

 

内存中保存的LogIndex Memtable又可分为Active LogIndex Memtable和Inactive LogIndex Memtable。如下图所示,基于WAL Meta数据生成的LogIndex记录会写入Active LogIndex Memtable,Active LogIndex Memtable写满后会转为Inactive LogIndex Memtable,并重新申请一个新的Active LogIndex Memtable,Inactive LogIndex Memtable可直接落盘,落盘后的Inactive LogIndex Memtable可再次转为Active LogIndex Memtable。

 

磁盘数据结构

磁盘上保存了若干个LogIndex Table,LogIndex Table与LogIndex Memtable结构类似,一个LogIndex Table可包含64个LogIndex Memtable,Inactive LogIndex Memtable落盘的同时会生成其对应的Bloom Filter。如下图所示,单个Bloom Filter的大小为4096字节,Bloom Filter记录了该Inactive LogIndex Memtable的相关信息,如保存的最小LSN、最大LSN、该Memtable中所有Page在bloom filter bit array中的映射值等。通过Bloom Filter可快速判断某个Page是否存在于对应的LogIndex Table中,从而可忽略无需扫描的LogIndex Table以加速检索。

当Inactive LogIndex MemTable成功落盘后,LogIndex Meta文件也被更新,该文件可保证LogIndex Memtable文件IO操作的原子性。如下,LogIndex Meta文件保存了当前磁盘上最小LogIndex Table及最大LogIndex Memtable的相关信息,其Start LSN记录了当前已落盘的所有LogIndex MemTable中最大的LSN。若Flush LogIndex MemTable时发生部分写,系统会从LogIndex Meta记录的Start LSN开始解析日志,如此部分写舍弃的LogIndex记录也会重新生成,保证了其IO操作的原子性。

Buffer管理可知,一致性位点之前的所有WAL日志修改的数据页均已持久化到共享存储中,RO节点无需回放该位点之前的WAL日志,故LogIndex Table中小于一致性位点的LSN均可清除。RW据此Truncate掉存储上不再使用的LogIndex Table,在加速RO回放效率的同时还可减少LogIndex Table占用的空间。

日志回放

延迟回放

LogIndex机制下,RO节点的Startup进程基于接收到的WAL Meta生成LogIndex,同时将该WAL Meta对应的已存在于Buffer Pool中的页面标记为Outdate后即可推进回放位点,Startup进程本身并不对日志进行回放,日志的回放操作交由背景回放进程及真正访问该页面的Backend进程进行,回放过程如下图所示,其中:

  • 背景回放进程按照WAL顺序依次进行日志回放操作,根据要回放的LSN检索LogIndex Memtable及LogIndex Table,获取该LSN修改的Page List,若某个Page存在于Buffer Pool中则对其进行回放,否则直接跳过。背景回放进程按照LSN的顺序逐步推进Buffer Pool中的页面位点,避免单个Page需要回放的LSN数量堆积太多;
  • Backend进程则仅对其实际需要访问的Page进行回放,当Backend进程需要访问一个Page时,如果该Page在Buffer Pool中不存在,则将该Page读到Buffer Pool后进行回放;如果该Page已经在Buffer Pool中且标记为outdate,则将该Page回放到最新。Backend进程依据Page TAG对LogIndex Memtable及LogIndex Table进行检索,按序生成与该Page相关的LSN List,基于LSN List从共享存储中读取完整的WAL日志来对该Page进行回放。

为降低回放时读取磁盘WAL日志带来的性能损耗,同时添加了XLOG Buffer用于缓存读取的WAL日志。如下图所示,原始方式下直接从磁盘上的WAL Segment File中读取WAL日志,添加XLog Page Buffer后,会先从XLog Buffer中读取,若所需WAL日志不在XLog Buffer中,则从磁盘上读取对应的WAL Page到Buffer中,然后再将其拷贝至XLogReaderState的readBuf中;若已在Buffer中,则直接将其拷贝至XLogReaderState的readBuf中,以此减少回放WAL日志时的IO次数,从而进一步加速日志回放的速度。

 

Mini Transaction

与传统share nothing架构下的日志回放不同,LogIndex机制下,Startup进程解析WAL Meta生成LogIndex与Backend进程基于LogIndex对Page进行回放的操作是并行的,且各个Backend进程仅对其需要访问的Page进行回放。由于一条XLog Record可能会对多个Page进行修改,以索引分裂为例,其涉及对Page_0,Page_1的修改,且其对Page_0及Page_1的修改为一个原子操作,即修改要么全部可见,要么全部不可见。针对此,设计了mini transaction锁机制以保证Backend进程回放过程中内存数据结构的一致性。
如下图所示,无mini transaction lock时,Startup进程对WAL Meta进行解析并按序将当前LSN插入到各个Page对应的LSN List中。若Startup进程完成对Page_0 LSN List的更新,但尚未完成对Page_1 LSN List的更新时,Backend_0和Backend_1分别对Page_0及Page_1进行访问,Backend_0和Backend_1分别基于Page对应的LSN List进行回放操作,Page_0被回放至LSN_N+1处,Page_1被回放至LSN_N处,可见此时Buffer Pool中两个Page对应的版本并不一致,从而导致相应内存数据结构的不一致。

mini transaction锁机制下,对Page_0及Page_1 LSN List的更新被视为一个mini transaction。Startup进程更新Page对应的LSN List时,需先获取该Page的mini transaction lock,如下先获取Page_0对应的mtr lock,获取Page mtr lock的顺序与回放时的顺序保持一致,更新完Page_0及Page_1 LSN List后再释放Page_0对应的mtr lock。Backend进程基于LogIndex对特定Page进行回放时,若该Page对应在Startup进程仍处于一个mini transaction中,则同样需先获取该Page对应的mtr lock后再进行回放操作。故若Startup进程完成对Page_0 LSN List的更新,但尚未完成对Page_1 LSN List的更新时,Backend_0和Backend_1分别对Page_0及Page_1进行访问,此时Backend_0需等待LSN List更新完毕并释放Page_0 mtr lock之后才可进行回放操作,而释放Page_0 mtr lock时Page_1的LSN List已完成更新,从而实现了内存数据结构的原子修改。

 

总结

PolarDB基于RW节点与RO节点共享存储这一特性,设计了LogIndex机制来加速RO节点的内存同步,降低RO节点与RW节点之间的延迟,确保了RO节点的一致性与可用性。本文对LogIndex的设计背景、基于LogIndex的RO内存同步架构及具体细节进行了分析。除了实现RO节点的内存同步,基于LogIndex机制还可实现RO节点的Online Promote,可加速RW节点异常崩溃时,RO节点提升为RW节点的速度,从而构建计算节点的高可用,实现服务的快速恢复。

原文地址:https://www.cnblogs.com/opensource-db

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读601次。Oracle的数据导入导出是一项基本的技能,但是对于懂数据库却不熟悉Oracle的同学可能会有一定的障碍。正好在最近的一个项目中碰到了这样一个任务,于是研究了一下Oracle的数据导入导出,在这里跟大家分享一下。......_oracle 迁移方法 对比
文章浏览阅读553次。开头还是介绍一下群,如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。加群请联系 liuaustin3 ,在新加的朋友会分到2群(共700多人左右 1 + 2)。最近我们在使用MYSQL 8 的情况下(8.025)在数据库运行中出现一个问题 参数prefer_order_i..._mysql prefer_ordering_index
文章浏览阅读3.5k次,点赞3次,收藏7次。折腾了两个小时多才成功连上,在这分享一下我的经验,也仅仅是经验分享,有不足的地方欢迎大家在评论区补充交流。_navicat连接opengauss
文章浏览阅读2.7k次。JSON 代表 JavaScript Object Notation。它是一种开放标准格式,将数据组织成中详述的键/值对和数组。_postgresql json
文章浏览阅读2.9k次,点赞2次,收藏6次。navicat 连接postgresql 注:navicat老版本可能报错。1.在springboot中引入我们需要的依赖以及相应版本。用代码生成器生成代码后,即可进行增删改查(略)安装好postgresql 略。更改配置信息(注释中有)_mybatisplus postgresql
文章浏览阅读1.4k次。postgre进阶sql,包含分组排序、JSON解析、修改、删除、更新、强制踢出数据库所有使用用户、连表更新与删除、获取今年第一天、获取近12个月的年月、锁表处理、系统表使用(查询所有表和字段及注释、查询表占用空间)、指定数据库查找模式search_path、postgre备份及还原_pgsql分组取每组第一条
文章浏览阅读3.3k次。上一篇我们学习了日志清理,日志清理虽然解决了日志膨胀的问题,但就无法再恢复检查点之前的一致性状态。因此,我们还需要日志归档,pg的日志归档原理和Oracle类似,不过归档命令需要自己配置。以下代码在postmaster.c除了开启归档外,还需要保证wal_level不能是MINIMAL状态(因为该状态下有些操作不会记录日志)。在db启动时,会同时检查archive_mode和wal_level。以下代码也在postmaster.c(PostmasterMain函数)。......_postgresql archive_mode
文章浏览阅读3k次。系统:ubuntu22.04.3目的:利用向日葵实现windows远程控制ubuntu。_csdn局域网桌面控制ubuntu
文章浏览阅读1.6k次。表分区是解决一些因单表过大引用的性能问题的方式,比如某张表过大就会造成查询变慢,可能分区是一种解决方案。一般建议当单表大小超过内存就可以考虑表分区了。1,继承式分区,分为触发器(trigger)和规则(rule)两种方式触发器的方式1)创建表CREATE TABLE "public"."track_info_trigger_partition" ( "id" serial, "object_type" int2 NOT NULL DEFAULT 0, "object_name..._pg数据表分区的实现
文章浏览阅读3.3k次。物联网平台开源的有几个,就我晓得的有、、thingskit、JetLink、DG-iot(还有其他开源的,欢迎在评论区留言哦!),然后重点分析了下ThingsBoard、ThingsPanel和JetLink,ThingsBoard和Jetlinks是工程师思维产品,可以更多的通过配置去实现开发的目的,ThingsPanel是业务人员思路产品,或者开发或者用,避免了复杂的配置带来的较高学习门槛。ThingsBoard和Jetlinks是Java技术体系的,ThingsPanel是PHP开发的。_jetlinks和thingsboard
文章浏览阅读3.8k次。PostgreSQL 数据类型转换_pgsql数字转字符串
文章浏览阅读7k次,点赞3次,收藏14次。在做数据统计页面时,总会遇到统计某段时间内,每天、每月、每年的数据视图(柱状图、折线图等)。这些统计数据一眼看过去也简单呀,不就是按照时间周期(天、月、年)对统计数据进行分个组就完了嘛?但是会有一个问题,简单的写个sql对周期分组,获取到的统计数据是缺失的,即没有数据的那天,整条记录也都没有了。如下图需求:以当前月份(2023年2月)为起点,往后倒推一年,查询之前一年里每个月的统计数据。可见图中的数据其实是缺少的,这条sql只查询到了有数据的月份(23年的1月、2月,22年的12月)_如何用一条sql查出按年按月按天的汇总
文章浏览阅读3.8k次,点赞66次,收藏51次。PostgreSQL全球开发小组与2022年10月13日,宣布发布PostgreSQL15,这是世界上最先进的开源数据库的最新版本_mysql8 postgresql15
文章浏览阅读1.3k次。上文介绍了磁盘管理器中VFD的实现原理,本篇将从上层角度讲解磁盘管理器的工作细节。_smgrrelationdata
文章浏览阅读1.1k次。PostgreSQL设置中文语言界面和局域网访问_postgressql汉化
文章浏览阅读4.2k次。PostgreSQL 修改数据存储路径_如何设置postgresql 数据目录
文章浏览阅读4.7k次。在项目中用到了多数据源,在连接postgres数据库时,项目启动报错,说数据库连接错误,说dual不存在,网上好多教程都是说数据库查询的时候的大小写问题,而这个仅仅是连接,咋鞥却处理方法是修改application-dev.yml中的配置文件.项目中的druid参数是这样的:确实在配置文件中有个查询语句。_relation "dual" does not exist
文章浏览阅读4.9k次。PostgreSQL是一款强大的关系型数据库,但在实际使用过程中,许多用户经常会遇到慢SQL的问题。这些问题不仅会降低数据库性能,还会直接影响业务流程和用户体验。因此,本文将会深入分析PostgreSQL慢SQL的原因和优化方案,帮助用户更好地利用这个优秀的数据库系统。无论你是初学者还是专业开发者,本文都将为你提供实用的技巧和方法,让你的PostgreSQL数据库始终保持高效快速。_postgresql数据库优化
文章浏览阅读1.6k次。Linux配置postgresql开机自启_linux 启动pgsql
文章浏览阅读2k次。本篇介绍如何在centos7系统搭建一个postgresql主备集群实现最近的HA(高可用)架构。后续更高级的HA模式都是基于这个最基本的主备搭建。_postgresql主备