技术小百科 | GaussDB(for Mongo)时间漫游读能力

2022-04-13 00:00:00 数据 用户 时间 快照 漫游

简介



GaussDB(for Mongo)是华为云自主研发兼容MongoDB4.0接口的文档数据库。


GaussDB(for Mongo)近期发布了一个新特性:时间漫游读能力。对于MongoDB的Find操作,用户可以指定 一个readAt参数,指定读取数据库历史上的某个时间点的数据。这个特性的用途有:


  • 对历史数据做报表,省去了用户自己按时间分库分表的麻烦

  • 在不影响业务读写的情况下,对数据做Point in Time的备份


该特性使用户可以在一个数据库实例,甚至同一个表空间上,管理多个历史时态的数据。时态数据库的概念,在SQL2011中被正式定义,主流数据库,如Oracle/MSSQL都支持该功能,MongoDB官方社区版/企业版目前还不支持这一能力。


用户接口



创建快照命令                                                                              

mongos> db.runCommand({snapshot:1, op:"create", name:"s1"})

该命令创建了一个名为s1的快照。


通过快照查询Point In Time 的数据      

mongos> db.runCommand({find:"foo",readAt:"s1"})


在find命令中,指定readAt : s1,表示从s1这个快照中读取数据。如下所示,在s1创建之后,我们插入的 {a:3,c:3}这条记录对用户是不可见的。

mongos> db.foo.insert({a:1, c:1}) mongos> db.foo.insert({a:2, c:2}) mongos> db.runCommand({snapshot:1, op:"create", name:"s1"}) mongosdb.foo.insert({a:3c:3}) 
mongos> db.runCommand({find:"foo",readAt:"s1"}){"cursor" :{"firstBatch":                 [{"_id" : ObjectId("5dd75662b9c50d4bdca52721"), "a" : 1, "c" : 1},                                                        {"_id" : ObjectId("5dd75687b9c50d4bdca52723"), "a" : 2"c" : 2}]}}



与MongoDB4.0/4.2提供的readAtClusterTime 能力不同的是,GaussDB(for Mongo)的时间漫游读提供的快照是持久化的,可以长期保留在系统中。用户可以通过命令删除快照,删除后快照会被自动清理。


删除快照命令         

                                                                     

db.runCommand({snapshot:1, op:"delete", name:"s1"})


实现思路                                                                      

概括而言,MongoDB的每个读操作都attach在特性的readSource上面,这部分逻辑在RocksRecoveryUnit::_txnOpen中。

一个readSource可以理解为是一个数据库快照。在一般情况下,MongoDB的快照如下:

  • kMajorityCommitted,从Raft Common Point快照读

  • kLastApplied,从系统新快照读 allCommitTs,从没有oplog空洞的快照读

时间漫游读的能力,就是将readSourceattach在用户创建的快照上。

GaussDB(for Mongo)使用RocksDB作为底层存储引擎。RocksDB中,每一个快照可以用一个SequenceNumber来标识,用户创建的快照作为一个键值对存储在RocksDB的元数据文件中。


Map<String,SequenceNumber> // snapshotName <-> SequenceNumber(aka snapshot)

当进程初始化后,持久化的快照被加载到内存中,在RocksDB的GC(CompactionIterator)流程中,每个key的多个版本被快照分割为若干区间,每个区间只保留大的版本。




上图中,userKey=A的数据有9个版本,分别由9LSN标识。系统中有两个活跃快照(5,7)。活跃快照将数据分成[lsn=1...lsn=5],[lsn=6...lsn=7], [lsn=8...lsn=9(current)]三个区间,对于每个区间,只有大的版本是可见的,因此RocksDB做完GC后,会保留ALsn=(5,7,9)三个版本的数据。

假设用户的时间漫游快照为"s1" -> (lsn=2)RocksDBGC流程不仅需要考虑ActiveSnapshots,也需要考虑时间漫游快照需要Pin住对应版本的数据。




上述是时间漫游读的思路。在具体的工程实践中,还有很多细节需要考虑,比如如何保证集群中多个Shard的快照一致性,如何防止过多的时间漫游快照影响性能等等。

案例介绍


某政企用户使用MongoDB存储TB级别的地图块的瓦片信息(tiles),瓦片信息相对静态,用户每隔一周更新部分地图块。在更新的过程中,业务不能看到部分是新数据,部分是老数据。


如上图,由于Update的过程漫长,持续几个小时,业务在遍历Tiles的过程中,会读到Tile0Version1Tile1Version0,然而业务想要访问的是Tile0Version0Tile1Version0。
用户有考虑过MongoDB的多文档事务自带的快照隔离能力,然而用户的更新过程很长,一次要达到2-3 个小时,MongoDB的多文档事务无法满足要求(一分钟的事务时长限制)。
在分析完业务的诉求后,华为云工程师为用户提供了时间漫游读的方案,在用户做批量更新之前,先为当前数据库做一个时间漫游快照,业务从时间漫游快照读数据,如下图:



当用户漫长的Update执行完成后,释放掉时间漫游快照,MongoDB又会从当前新快照读数据,如下图:

试想如果没有时间漫游读能力,用户需要先把全量数据拷贝到另一张表,对部分数据完成更新后再Rename回来,操作复杂,且浪费IO容易造成业务受损。时间漫游读能力简化了用户的操作,降低了用户的运维成本。


总结


GaussDB(for Mongo) 时间漫游读功能提供了历史时间点的快照读能力,用户不需要担心快照时间驻留过长而被Abort掉。API简单灵活,用途广泛。欢迎有类似需求的DBA进一步交流。

-END-

作者:GoldenJohn,华为云GaussDB(for Mongo)研发负责人,十年数据库内核研发与运维经验,致力于做好的云原生文档数据库。


相关文章