GaussDB(for Mongo)只读副本的设计与实现

2022-04-13 00:00:00 数据 文件 节点 复制 副本


相关资讯:6月30日,华为openGauss正式开源,开放openGauss数据库源代码,并成立openGauss开源社区。

GaussDB(for Mongo)是华为云自主研发兼容MongoDB4.0接口的文档数据库。其领先的副本集计算存储架构,对于传统MongoDB社区版有如下优势:

  • 秒级添加Secondary节点(相比社区版Mongo小时级添加Secondary节点)

  • 基于WAL复制, Secondary节点无写IO,从根本上解决社区版Seconary节点Oplog脱节问题

  • Primary/Seconary无任何IO交互,Secondary节点个数理论无上限, 支持百万OPS的读事务能力

  • LSMTree Compaction 计算/IO卸载到Compaction统一调度池,集中管理,不浪费用户读写IO




GaussDB(for Mongo) 架构介绍



  • GaussDB(for mongo) 架构介绍

GaussDB(for Mongo) 是兼容mongodb4.0接口的计算存储分离架构的高性能数据库。通过底层存储来保证多副本的数据的安全可靠,上层计算节点可以做到无状态的运行。


GaussDB(for Mongo) 有以下几个优点:


1.秒级的节点扩容能力

GaussDB(for Mongo) 具有 秒级的节点扩容能力。在业务流量突发的时候,可以迅速采取扩容措施,应对业务高峰。传统的 mongodb 因为采用的是 计算节点读取本地数据的方式, 扩容节点后,新加节点还需要等待节点同步完主节点数据才可以对外提供服务。如果数据量很大, 新增节点的数据同步会花费很多时间,在这段时间内,新增节点非但不能提供服务,反而因为需要从集群同步数据的缘故,进一步加重了数据库的负载。


2. 从根源上杜绝了社区版MongoDB数据被Rollback的问题

社区版mongodb副本集基于Raft协议,当Primary宕机后,Secondary节点选举成新的Primary,老Primary有数据回滚的风险,而GaussDB(for Mongo)基于共享存储则不存在这种问题。


3. 计算节点写压力卸载

传统的数据库通过只读副本可以做到业务数据的读写分离。基于共享存储的GaussDB(for Mongo) 不仅仅能做到业务数据的读写分离,还能分离主节点的LSMTree Compaction压力。 将主节点的后台Compaction压力托管给CompactionService微服务,从而节约主节点的IO与CPU资源,提升主节点的写吞吐量,为客户业务提供更好的数据抗压能力。这个特性称为 'Compaction Offload'(Compaction卸载)


4. 主从复制占用的资源少

传统的数据库使用 MongoDB层面的Oplog进行数据同步,所有主节点上的写IO在从节点必须被镜像式的回放一遍。GaussDB(for Mongo) 在数据同步技术上,充分利用了共享存储的优势, 仅需要主备之间同步存储池数据文件的元数据变更信息,以及读取变更数据的Write Ahead Log到内存的操作。避免了Secondary节点数据的Double甚至Triple Write,为用户的业务让出更多的CPU和IO资源。




GaussDB(for Mongo) 只读节点设计



  • 只读节点功能设计

    • 传统社区版MongoDB副本集基于Oplog做数据复制,只读节点需要镜像主节点的所有写IO操作。GaussDB(for Mongo) 的只读节点和主节点共享同一份底层数据库文件(LSMTree的SST文件),只读节点并不自己生成SST文件。

    • 随着业务数据的写入,Compaction的不断执行,LSMTree的当前版本(包含哪些SST文件)不断更新,LSMTree的元数据更新(增删SST文件的记录)被同步到只读节点执行。

    • RocksDB中,数据的变更被持久化到WAL里,元数据的变更(增删文件的操作, 叫做VersionEdit)被持久化到Mainifest里。RocksDB的数据和元数据是分开的,WAL流和VersionEdit流是并行的,没有严格的先后顺序。为了保证只读节点和主节点完全一致的事件回放顺序,WAL和VersionEdit流必须要合并成一个流,在双流合并后,通过LSN就可以为每个事件(WAL的写操作/VersionEdit)定序。


    • 基于WAL+VersionEdit复制,而不基于Oplog复制

    • 共享文件(sst/wal)的生命周期管理由主节点负责

      sst文件和wal的文件的生命周期由主节点负责。RocksDB中,SST文件通过层级的引用计数来维持不被删除。如下图,RocksDB的每个游标会维持SuperVersion,如下图中的S0,S1,S2。每个SuperVersion会引用一个Version,一个Version代表LSMTree在不断变形(通过增删SST文件变形)的过程中,某个时间点的形状,新的Version就代表LSMTree当前的形状。


    • 在GaussDB(for Mongo)中,主节点会记录所有只读节点在使用的Version,并为这些Version增加引用计数从而维持SST文件的生命周期。

      对于WAL,主节点会记录所有只读节点中老的LSN(oldestLsn),老的LSN来自于复制慢的只读节点。并删除比oldestLsn还旧的WAL文件。


    • 元数据变更通知

      无论是oldestLsn还是只读节点的当前在用的活跃的Version,都需要及时推进,这些元数据的变更是通过主从节点的定期心跳上报到主节点上的。主节点利用心跳数据对垃圾版本与WAL做清理。

      如下图所示,在经历一次心跳后,主节点发现Secondary0的Version0和Secondary1的Version0不再使用。删除这两个Version后,SST0的引用计数为0,表示SST0可以被删除。OldestLsn也从100推进到了250,可以清理掉250之前的WAL。


    • 只读节点的memtable的释放

      主节点的Memtable不会实时Flush为SST文件。如果只读节点不处理主节点的Memtable的话,只读节点的数据就不是实时的,且存在数据一致性问题。只读节点通过回放WAL到内存的Memtable中,来覆盖SST文件与主节点的Memtable的Gap。

      上文介绍了只读节点是不往共享存储写入数据的, 所以只读节点上的 Memtable 后的结局一定是被丢弃掉。但什么时候丢弃这个 Memtable 就是一个问题。过早的丢弃,会造成SST文件与Memtable之间的数据不连续,存在Gap,过晚的丢弃会造成内存的浪费。只有当只读节点识别到SST的数据已经完全能够Cover某个Memtable时,这个Memtable才可以被丢弃。

      GaussDB(for Mongo)的只读节点在每次应用VersionEdit后,检查所有SST中的大的LSN与Memtable的小的LSN的关系,来决定是否要丢弃某个Memtable。


  • 内存元数据的反向更新

    传统的复制,数据流从Oplog来,走一遍完整的数据库Server层CRUD接口,再落到引擎层。这种逻辑和主节点上业务的写入逻辑是一致的,因此Server层的一些内存元数据结构,在这个过程中就自然而然的得到更新了。但是当采用基于WAL的复制后,整个WritePath并不经过只读节点的Server层。因此Server层的内存元数据更新,就是一个很大的挑战。在这里,只读节点对每一条WAL做分析,如果WAL的内容会影响Mongo内存元数据,就会reload对应的元数据模块。




GaussDB(for Mongo) 实现



考虑到只读节点和主节点,以及 GaussDB(for Mongo)的软件分层, 我们将只读副本方案的实现,分为四个部分, 如下图所示:


三大流程如图所示:


  • 开始复制

  • 持续复制


  • 心跳处理




总结


GaussDB(for Mongo) 只读节点功能,实现了一份数据多计算节点共用的功能。极大的提升了存储的利用效率,提高了计算节点的读取数据能力。为了让副本节点具有持续的读扩展能力,整个只读方案采用元数据的同步模式,在不降低主节点负载的情况下,极大的提升了整个系统的读数据的处理能力。为3节点,5节点,乃至于15节点以上的副本集的工作提供了可能。

GaussDB(for Mongo) 只读节点功能,极大的提升了计算节点的弹性能力, 秒级扩缩容的能力在应对负载高峰和降低数据库成本方面效果显著。真正的做到了按需使用,弹性扩容的核心能力。也为客户业务面对热点爆发流量数据提供了更加高效安全的保证。




作者:高强 (微信: gaoqiang0218)

华为云Gemini MongoDB研发负责人,十年数据库内核研发与运维经验,致力于做好的云原生文档数据库。




相关文章