• [问题求助] 8.14直播互动&社区互动问题答疑总结
    Q1:GaussDB(for Mongo)在使用上和社区版MongoDB有什么不一样的地方?有哪些不兼容的地方需要注意?ANS:GaussDB(for Mongo)对社区版本完全兼容,不过出于安全和防呆的考虑,禁用了一部分高危命令。Q2:啥时候出GaussDB(for PgSQL)啊?ANS:非常开心的告诉大家,GaussDB(for PostgreSQL)已经在研发中,预计年底将在官网上跟大家见面,感兴趣的开发者届时可以来试用。Q3:话说,副本集是个什么形态?ANS:这是MongoDB基础概念,具体可以参考官方文档,或者在中文社区上搜索相关资料           https://docs.mongodb.com/manual/replication/           https://mongoing.com/Q4:从节点的个数变多会影响主节点的写性能吗?ANS:对于社区版来说,由于从节点需要从主节点拉去oplog,所以多增加主节点的压力,会影响写性能。对于GaussDB(for Mongo)来说则不会影响主节点的写性能。Q5:GaussDB(for Mongo) 最高支持MongoDB4.0协议版本,那么后续社区版4.2, 4.4的分布式事务等新特性,GaussDB(for Mongo)会持续跟进吗?ANS:后续社区上的新特性,GaussDB(for Mongo)都会在当前版本兼容起来.Q6:不同mongod的cache如何解决?ANS:每个Mongod的cache独立处理采用统一的策略,互相之间没有影响。Q7:for mongo是啥意思?ANS:即兼容Mongo协议、语法、生态。Q8:共享文件系统里面是以chunk为单位的?ANS:这里提到的是Mongo层的chunk,分片集合的数据就是通过chunk管理的,在balance过程中迁移的最小单元。Q9:不同shard双写同一个key如何解冲突?ANS:应该是想问不同的shard同时写一个共享的数据文件是如何解决冲突的。一个数据文件只会归属shard,通过文件系统层的写锁提供保证。Q10:GaussDB(for Mongo) ,GaussDB(for MySQL)、还有未见面的GaussDB(for PgSQL) 比原生的是否都有什么类似的改进,都加个GaussDB?ANS:GaussDB是华为云面向政企、金融客户打造的自研数据库,兼容开源生态,自主可控。在底层统一采用了存算分离架构,可以达到分钟级甚至秒级水平扩容,无论是存储节点,还是计算节点。各云服务还提供了许多特有的价值特性。您可以关注华为云数据库社区&论坛(https://developer.huaweicloud.com/techfield/db.html)和“HW云数据库”公众号,我们持续在输出精品文章、技术沙龙、学习课程、培训认证、产品文档、免费试用等。Q11:采用的是分布式存储方式吧?ANS:是的。Q12:水平扩展性能这么好,那单节点读写性能和社区版MongoDB相比,是否具有优势?ANS:在新架构上,对于单节点收益不大,所以不提供单节点。本身单节点的可靠性,高可用都是有问题的,所以即使使用社区版也不建议采用这种部署方式。Q13:单独的存储池,GaussDB数据只存了2份?ANS:GaussDB(for Mongo)数据在存储层是3副本。Q14:GaussDB相较于传统数据库,最大的优势是什么?ANS:计算存储分离,性能更好,相同的业务需求有更高的性价比,以及更快的弹性伸缩能力,更高的可用性。Q15:存算分离,容灾备份机制目前采用哪种方式最好?会增加建设成本吗?ANS:关于建设成本主要影响因素是容灾的具体方案,存算分离本身不增加额外的成本。Q16:突然掉电,能保证计算节点在掉电瞬间的的数据存储的完整性吗?ANS:文件系统层提供了这样的保证,计算节点掉电并不会损坏数据文件。Q17:安全策略上,与其他产品或方案最大的区别在哪里?ANS:在社区版本安全策略的基础上,增加禁止了高危命令,用户权限控制,密码策略等一系列加固手段。并且华为云统一提供VPC、子网等安全策略。Q18:副本集轻写,能达到什么写量级?ANS:8U实例纯写可以达到38000 ops。Q19:计算存储分离以后存储的时延是否变大了?ANS:与EVS的存储性能相比更优(更大吞吐更低时延),与本地盘相比时延略高。Q20:老师为什么叫GoldenJohn?ANS:1、谐音 2、为了闪闪发亮、高大上的GoldenQ21:副本集和热备容灾有多大的区别?ANS:副本集提供实例级别内的高可用,高可靠的能力。容灾提供实例级别的高可用和高可靠能力。前者实时性更好,倒换全自动不需要外部干预,故障恢复时间短,社区提供的基础能力。对于热备容灾来说,受传输影响大,无法自动接管需要人力介入。故障恢复时间相关更长。
  • [技术干货] 【转载】一篇文章带你学习 MongoDB 中各种类型的索引(_id索引/复合索引/过期索引/全文索引/地理空间索引/复合地理空间
    一、_id 索引我们介绍过,我们往集合中添加文档时,默认情况下MongoDB都会帮助我们创建一个名为_id的字段,这个字段就是一个索引。默认情况下,一般的集合都会帮我们创建这个字段作为索引,但也有一些集合不会将_id默认作为索引,比如固定集合,这个我们后面会详细说到这个问题。二、复合索引如果我们的查询条件有多个的话,我们可以对这多个查询条件都建立索引,比如我们可以对文档中的x和y字段都建立索引,如下:db.sang_collect.ensureIndex({x:1,y:-1})1此时执行如下查询语句时就会用到这个复合索引:db.sang_collect.find({x:1,y:999})1小伙伴们也可以通过查看查询计划来确定确实使用到了上文创建好的索引。三、过期索引顾名思义,过期索引就是一种会过期的索引,在索引过期之后,索引对应的数据会被删除,创建方式如下:db.sang_collect.ensureIndex({time:1},{expireAfterSeconds:30})1expireAfterSeconds表示索引的过期时间,单位为秒。time表示索引的字段,time的数据类型必须是ISODate或者ISODate数组,否则的话,当索引过期之后,time的数据就不会被删除。四、全文索引全文索引虽然好用,可惜不支持中文,我们这里就先做一个简单的了解。比如,我的数据集如下:{    "_id" : ObjectId("59f5a3da1f9e8e181ffc3189"),    "x" : "Java C# Python PHP"}{    "_id" : ObjectId("59f5a3da1f9e8e181ffc318a"),    "x" : "Java C#"}{    "_id" : ObjectId("59f5a3da1f9e8e181ffc318b"),    "x" : "Java Python"}{    "_id" : ObjectId("59f5a3da1f9e8e181ffc318c"),    "x" : "PHP Python"}{    "_id" : ObjectId("59f5a4541f9e8e181ffc318d"),    "x" : "C C++"}1234567891011121314151617181920我们可以给x字段建立一个全文索引,创建方式如下:db.sang_collect.ensureIndex({x:"text"})1MongoDB会自动对x字段的数据进行分词,然后我们就可以通过如下语句进行查询:db.sang_collect.find({$text:{$search:"Java"}})1此时x中包含Java的文档都会被查询出来。如果想查询既包含Java又包含C#的文档,操作如下:db.sang_collect.find({$text:{$search:"\"Java C#\""}})1用一对双引号将查询条件括起来,如果想查询包含PHP或者Python的文档,操作如下:db.sang_collect.find({$text:{$search:"PHP Python"}})1如果想查询既有PHP,又有Python,但是又不包括Java的文档,如下:db.sang_collect.find({$text:{$search:"PHP Python -Java"}})1建立了全文索引之后,我们也可以查看查询结果的相似度,使用$meta,如下:db.sang_collect.find({$text:{$search:"PHP Python"}},{score:{$meta:"textScore"}})1此时查询结果中会多出一个score字段,该字段的值越大,表示相似度越高,我们可以根据score利用sort来对其进行排序,如下:db.sang_collect.find({$text:{$search:"PHP Python"}},{score:{$meta:"textScore"}}).sort({score:{$meta:"textScore"}})1全文索引目前看起来功能还是很强大,可惜暂时不支持中文,不过网上对此也有很多解决方案,小伙伴们可以自行搜索查看。五、地理空间索引1. 2d 索引2d索引,可以用来存储和查找平面上的点。,2d索引一般我们可以用在游戏地图中向集合中插入一条记录点的数据:db.sang_collect.insert({x:[90,0]})1插入数据的格式为[经度,纬度],取值范围,经度[-180,180],纬度[-90,90]。数据插入成功之后,我们先通过如下命令创建索引:db.sang_collect.ensureIndex({x:"2d"})1然后通过$near我们可以查询某一个点附近的点,如下:db.sang_collect.find({x:{$near:[90,0]}})1默认情况下返回该点附近100个点,我们可以通过$maxDistance来设置返回的最远距离:db.sang_collect.find({x:{$near:[90,0],$maxDistance:99}})1我们也可以通过$geoWithin查询某个形状内的点,比如查询矩形中的点:db.sang_collect.find({x:{$geoWithin:{$box:[[0,0],[91,1]]}}})1两个坐标点用来确定矩形的位置。查询圆中的点:db.sang_collect.find({x:{$geoWithin:{$center:[[0,0],90]}}})1参数分别表示圆的圆心和半径。查询多边形中的点:db.sang_collect.find({x:{$geoWithin:{$polygon:[[0,0],[100,0],[100,1],[0,1]]}}})1这里可以填入任意多个点,表示多边形中的各个点。2. 2d sphere 索引2dsphere适用于球面类型的地图,它的数据类型是GeoJSON格式的,我们可以在http://geojson.org/地址上查看GeoJSON格式的样式,比如我们描述一个点,GeoJSON如下:{    "_id" : ObjectId("59f5e0571f9e8e181ffc3196"),    "name" : "shenzhen",    "location" : {        "type" : "Point",        "coordinates" : [            90.0,            0.0        ]    }}1234567891011描述线,GeoJSON格式如下:{    "_id" : ObjectId("59f5e0d01f9e8e181ffc3199"),    "name" : "shenzhen",    "location" : {        "type" : "LineString",        "coordinates" : [            [                90.0,                0.0            ],            [                90.0,                1.0            ],            [                90.0,                2.0            ]        ]    }}123456789101112131415161718192021描述多边形,GeoJSON格式如下:{    "_id" : ObjectId("59f5e3f91f9e8e181ffc31d0"),    "name" : "beijing",    "location" : {        "type" : "Polygon",        "coordinates" : [            [                [                    0.0,                    1.0                ],                [                    0.0,                    2.0                ],                [                    1.0,                    2.0                ],                [                    0.0,                    1.0                ]            ]        ]    }}123456789101112131415161718192021222324252627还有其他的类型,具体小伙伴们可以参考http://geojson.org/。有了数据之后,我们可以通过如下操作来创建地理空间索引了:db.sang_collect.ensureIndex({location:"2dsphere"})1比如我想查询和深圳这个区域有交集的文档,如下:var shenzhen = db.sang_collect.findOne({name:"shenzhen"})db.sang_collect.find({location:{$geoIntersects:{$geometry:shenzhen.location}}})12这里的查询结果是和深圳这个区域有交集的都会查到(比如经过深圳的高速公路、铁路等),我们也可以只查询深圳市内的区域(比如深圳市内所有的学校),如下:var shenzhen = db.sang_collect.findOne({name:"shenzhen"})db.sang_collect.find({location:{$within:{$geometry:shenzhen.location}}})12也可以查询腾讯附近的其他位置,如下:var QQ = db.sang_collect.findOne({name:"QQ"})db.sang_collect.find({location:{$near:{$geometry:QQ.location}}})12六、复合地理空间索引位置往往只是我们查询的一个条件,比如我要查询深圳市内所有的学校,那我得再增加一个查询条件,如下:var shenzhen = db.sang_collect.findOne({name:"shenzhen"})db.sang_collect.find({location:{$within:{$geometry:shenzhen.location}},name:"QQ"})12其他的查询条件跟在后面就行了。
  • [技术干货] 一文读懂GaussDB(for Mongo)的计算存储分离架构
    1.摘要GaussDB(for Mongo)是华为云自主研发兼容MongoDB4.0接口的文档数据库。基于共享存储的存算分离架构,对于传统MongoDB社区版有如下优势:秒级添加Secondary节点(相比社区版Mongo小时级添加Secondary节点)基于WAL复制, Secondary节点无写IO,从根本上解决社区版Seconary节点Oplog脱节问题Primary/Seconary无任何IO交互,Secondary节点个数理论无上限, 支持百万OPS的读事务能力LSMTree Compaction 计算/IO卸载到Compaction统一调度池,集中管理,不浪费用户读写IO基于共享存储,Chunk分裂/迁移动作不引起真实IO,只更新路由元数据,秒级分裂/均衡2.GaussDB(for Mongo)技术架构1)容忍更多Shard宕机与社区版MongoDB的`Share-Nothing`模式不同的是,GaussDB(for Mongo)采用`Share-Storage`架构,计算存储分离。集群模式下,N个Shard节点,可以容忍N-1个Shard宕机。某个Shard节点宕机后,其负责的数据由于存在于共享的存储池中,因此不需要物理拷贝数据,只需要修改元数据路由信息,即可被其他分片节点接管。2)更快的分裂与均衡能力此外,由于Chunk数据在存储池中,Chunk的分裂与均衡不涉及到数据拷贝,可以做到分钟级分裂与扩容,分裂与扩容对用户的影响也远比社区版MongoDB小。3)百万级读OPS能力GaussDB(for Mongo)副本集模式下,Primary/Secondary节点之间共享同一份数据库文件。Secondary节点只复制Primary节点的WriteAheadLog以及LSMTree的结构变更信息,并应用到内存中。Secondary节点没有LSMTree的Compaction和Flush任务,因此对用户的读业务影响很小。此外,由于`Share-Storage`的架构优势,添加Secondary节点并不需要拷贝数据,添加Secondary节点的动作可以秒级完成。而Primary/Secondary之间只传递元数据变更,不传递WriteAheadLog,因此Secondary节点的个数即使变多,也不影响Primary节点的写性能。Secondary节点可以水平扩展,支撑百万级的读OPS。4)主节点IO卸载LSMTree的写压力来源于三部分:用户的业务写入导致的Memtable Flush后台SST文件CompactionWAL的持续写入根据线上业务的实际测算,三者的IO资源消耗占比为: 1:10:1。后台的SST文件Compaction占了绝大部分IO带宽,通过将Compaction任务集中化管理,从计算池卸载到存储池,进一步减少了用户计算节点的CPU和IO资源消耗。5)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对应的元数据模块。3.总结GaussDB(for Mongo) 基于Share-Storage架构,实现秒级Chunk分裂与均衡,对业务影响更小,水平扩展速度更快,能容忍更多节点宕机。只读节点功能,实现了一份数据多计算节点共用的功能。极大的提升了存储的利用效率,提高了计算节点的读取数据能力。为了让副本节点具有持续的读扩展能力,整个只读方案采用元数据的同步模式,在不降低主节点负载的情况下,极大的提升了整个系统的读数据的处理能力。为3节点,5节点,乃至于15节点以上的副本集的工作提供了可能。
  • [数据库] MongoDB经典故障系列【合集】
    MongoDB经典故障系列一:数据库频繁启动失败怎么办?MongoDB经典故障系列二:如何限制最大连接数?     MongoDB经典故障系列三:副本集延迟太高怎么办?MongoDB经典故障系列四:调整oplog大小,引起从库宕机怎么办?MongoDB经典故障系列五:sharding集群执行sh.stopBalancer()命令被卡住怎么办?MongoDB经典故障系列六:CPU利⽤率太⾼怎么办?
  • [技术干货] 【转载】MongoDB 事务,复制和分片的关系
    摘要:本文尝试对Mongo的复制和分布式事务的原理进行描述,在必要的地方,对实现的正确性进行论证,希望能为MongoDB内核爱好者提供一些参考。1.前言MongoDB基于wiredTiger提供的泛化SI的功能,重构了readHistory(readMajority)的能力基于wiredTiger提供的AllCommittedTimestamp API,重构了前缀一致的主从复制(Prefix-Consistent-Replication)引入混合逻辑时钟(HLC),每个节点(Mongos/Mongod)的逻辑时钟维持在接近的值,基于此实现ChangeStream, 结合HLC与CLOCK-SI,实现分布式事务,HLC和泛化SI,CLOCK-SI两篇Paper可以作为理解MongoDB的设计的理论参考(这里并没有说MongoDB是Paper的实现)。本文尝试对Mongo的复制和分布式事务的原理进行描述,在必要的地方,对实现的正确性进行论证,希望能为MongoDB内核爱好者提供一些参考。2.MongoDB副本集事务介绍MongoDB 副本集的事务MongoDB副本集的复制是基于raft协议,相比于Paxos,raft协议实现简单,但是raft协议只支持single-master,对应的,MongoDB的副本集是主从架构,而且只有主节点支持写入操作。MongoDB副本集的事务管理,包括冲突检测,事务提交等关键操作,都只在主节点上完成。也就是说副本集的事务在事务管理方面,跟单节点逻辑基本一致。MongoDB的事务,仍然是实现了 ACID 四个特性, MongoDB使用 SI 作为事务的隔离级别。3.SI的简介SI,即SnapshotIsolation,中文称为快照隔离,是一种mvcc的实现机制,它在1995年的A Critique of ANSI SQL Isolation Levels中被正式提出。因快照时间点的选取上的不同,又分为Conventional Si 和 Generalized SI。CSI(Convensional SI)CSI 选取当前最新的系统快照作为事务的读取快照就是在事务开始的时候,获得当前db最新的snapshot,作为事务的读取的snapshot,snapshot(Ti) = start(Ti)可以减少写事务冲突发生的概率,并且提供读事务读取最新数据的能力一般我们说一个数据库支持SI隔离级别,其实默认是说支持CSI。比如RocksDB支持的SI就是CSI,WiredTiger在3.0版本之前支持的SI也是CSI。GSI(Generalized SI)GSI选择历史上的数据库快照作为事务的读取快照,因此CSI可以看作GSI的一个特例。在复制集的情况下,考虑 CSI, 对于主节点上的事务,每次事务的开始时间选取的系统 最新的快照, 但是对于其他从节点来说, 并没有 统一的 “最新的” 快照这个概念。泛化的快照实际上是基于快照观测得到的,对于当前事务来说,我们通过选取合适的 更早时间的快照,可以让 从节点上的事务正确且无延迟的执行。举例如下:例如当前数据库的状态是, S={T1, T2, T3}, 现在要开始执行T4,如果我们知道T4要修改的值,在T3上没有被修改, 那么我们在执行T4的时候, 就可以按照 T2 commit后的snashot进行读取。如何选择更早的时间点,需要满足下面的规则,符号定义Ti: 事务iXi: 被事务i修改过的X变量snapshot(Ti): 事务i的选取的快照时间start(Ti): 事务i的开始时间commit(Ti): 事务i的提交时间abort(Ti): the time when Ti is aborted.end(Ti): the time when Ti is committed or aborted.公式解释读规则G1.1, 如果变量X被本事务修改了值且读取到了新的值, 那么 读操作一定在写操作后面;G1.2, 如果事务i读取了事务j更新的变量的X, 那么一定不会有事务i更新X的操作,在事务i读取了事务j更新的变量的X这个操作前面;G1.3, 事务j的提交时间早于事务i的快照时间;G1.4, 对于任意一个会更新变量X的事务k, 那么这个事务k一定满足, 要么事务k的提交时间小于事务j, 要么这个事务k的提交时间大于事务i。写规则G2, 对于任意已经在提交历史里的两个事务,Ci, Cj, 那么一定可以保证当 事务j的commit时间戳在 事务i的观测时间段内时(snapshot(Ti), commit(Ti)), 那么他们更新的变量交集一定为空。PCSI(PREFIX-CONSISTENT SNAPSHOT ISOLATION SI)GSI 只是定义了一个范围的range,都可以作为SI使用,并没有定义具体应该选择哪个SI。PCSI 是为了复制集而设计的。对于一个事务Ti 要开S节点开始运行, 那么 S节点将必须包含这个事务所需要的所有前置事务都必须运行且提交。相比较于GSI, PCSI的读规则,额外增加了 P1.5 规则。SI的提交时间戳设置,依据 A Critique of ANSI SQL Isolation Levels 中的描述, 提交时间戳的设置应该是单调递增的。新设置的时间戳,应该大于系统中已经存在的开始时间戳和提交时间戳。SI 读取时间戳的设置,必须保证比当前系统中正在运行的事务的最小的提交时间戳还要小, 因为一旦大于当前系统中正在运行事务的最小的提交时间戳,那么这个读事务读取到的数据就是未定义的, 取决于读事务启动的时间,而不是snapshot的时间,这违背了 一致性的要求。举例如下当前已经完成的事务是T1,正在运行的事务是T2, 将要运行的读事务是T3, 如果 T3的读时间戳大于T2事务提交时间戳, 并且T2事务正在运行,等到T2事务执行完后。我们观察这个 database,就会发现 他违背了GSI,事务执行顺序如下所示是:T1 commited and commitTs(1) -> T2 start -> T2 set commitTs(2) -> T3 start -> T3 set snapshotTs(3) -> T3 commit -> pointA -> T2 commit -> pointB那么可知, T3事务实际读取的值是 T1事务的值。但根据 pointB 点来看 GSI的读规则 1.4 的要求,会发现, 如果T3读到T1的事务的修改,那么必然要求, T3和T1之间没有空洞。但实际上 T2 是落在了 T3和T1之间的, 也就是说, 违反了 GSI 1.4的读规则。所以我们必须规定, SI 读取时间戳的设置,必须保证比当前系统中正在运行的事务的最小的提交时间戳还要小。4.MongoDB副本集时间戳应用MongoDB 4.0的复制也是利用时间戳特性解决了3.x系列MongoDB从节点复制造成从节点性能下降的关键方案。MongoDB oplog 乱序问题MongoDB主备节点的数据同步并不基于WiredTiger的wal日志来做的。相反,mongodb会将每次操作的数据变更写入到一个叫做oplog的集合里。oplog这个集合,虽然名字带有log,但实际上,它是一个MongoDB的表, 对oplog的写入,并不是 append的方式修改的, 而是呈现出一种尾部乱序的方式。对于oplog来说, oplog的读取顺序是按照TS字段来排序的, 跟上层的提交顺序无关。所以存在后开始的事务,在oplog先读取的场景。oplog 空洞因为出现了乱序,所以从节点在读取oplog的时候,就会在某些时间点出现空洞。举例如下:时间点1: oplog 顺序为: Ta -> Tb, 此时系统中还有一个事务Tc在运行时间点2: oplog 顺序为: Ta -> Tc -> Tb, 当Tc运行结束后, 因为ts的顺序, 看起来是将Tc插入到了Ta和Tb之间。那么当 从节点 在时间点1 reply 到 Tb的时候, 实际上是漏了 Tc的,这个就是oplog的空洞, 他产生的原因是因为,从节点如果每次读取oplog最新的数据,就有可能会得到一个不连续的数据, 例如 时间点1上 Ta-> Tb. 这就是oplog空洞。在具体复制逻辑中,我们必须想办法来从节点读取到含有空洞的oplog数据。这也是GSI的要求, snapshot的选取不能含有空洞。因为 oplog的Ts是mongo上层给的,我们很容易知道哪些事务有哪些ts, 我们再将这个ts 作为事务的commitTs 放到 oplog存储的事务里, 这样我们读取 oplog的顺序事务的可见性顺序相一致了,在这种情况下,我们就可以 根据 活跃事务列表, 就可以将oplog 分为两个部分,假设活跃commitTs列表的事务是 {T10, T11, T12}, 活跃事务列表是 {T10, T11, T12, T13, T14}, 那么意味着, 目前有 T10, T11, T12, T13, T14 再运行,并且 T10, T11, T12 已经设置了 commitTs, 又因为 上面讨论的 commitTs 是单调递增的, 那么我们可知, T13, T14 的commitTs 一定大于 maxCommitTs(T10, T11, T12), 而且我们还可知, minCommitTs(T10,T11,T12) 就是全局最小的 commitTs, 而小于这些的 commitTs的事务,因为不在 活跃事务列表里了, 表示已经提交了, 那么我们可以知道, oplog ts 在 全局最小的 commitTs 之前的, 就是都提交了的, oplog 按照 commitTs 排序后,如下所示… Tx | minCommitTs(T10,T11,T12) | …我们可以知道 T9, 或者说小于 minCommitTs(T10,T11,T12) 都是无空洞,因为系统不会再提交小于 minCommitTs(T10,T11,T12) 的事务到oplog里了, 所以从节点可以直接恢复这里的数据。上面说的oplog minCommitTs(T10,T11,T12) 在 mongodb里,就是特殊的timestamp, 这个后文会讲。通过上面的方案,我们可以解决空洞的问题。这个时候,从节点每次恢复数据的时候,将读取的snapshot,设置为上一次恢复的Ts(同样也是无空洞的Ts), 这样的话, 从节点的恢复数据和读取数据也就做到了互不冲突。从而解决了 3.x系列的 从节点同步数据造成节点性能下降的问题。转载自:华为云开发者论坛
  • 【鲲鹏翱翔】MongoDB-tools编译指南
    1.   简介Mongo-tools是使用MongoDB的一系列工具,每个工具有具有相应的功能:bsondump - 将BSON格式的文件转换为可读性更强的文件格式mongodump - MongoDB数据存储工具mongoexport - MongoDB导出工具mongofiles – MongoDB文件操作工具mongoimport - MongoDB导入程序mongorestore - MongoDB数据恢复工具mongostat – MongoDB状态检测工具mongotop – MongoDB监控工具 2.   部署环境Packet NameVersionDetailCentOSCentOS   7.5 64bit with ARMarm通用计算增强型   2vCPUs | 4GB | kc1.large.2Go1.12以上GCC4.8.5Yum安装 3.   安装部署3.1 配置Go环境wget https://obs-mirror-ftp4.obs.cn-north-4.myhuaweicloud.com/middleware/go1.14.5.linux-arm64.tar.gz tar -zxvf go1.14.5.linux-arm64.tar.gz -C /usr/local解压后配置环境变量vim /etc/profile //在文件的最后加上 export GOROOT=/usr/local/go export GOPATH=/usr/local/go export PATH=$PATH:$GOPATH/bin 完成编辑之后 source /etc/profile执行ln -s /usr/bin/go /usr/local/go/bin/go go version查看已配置的go版本3.2 构建Mongo-tools下载mongo-tools安装包wget https://obs-mirror-ftp4.obs.cn-north-4.myhuaweicloud.com/middleware/mongo-tools.tar.gz创建文件目录,将文件夹移动到对应位置tar -xvf mongo-tools.tar.gz mkdir -p /root/mongodb-tools-r4.2.0/src/github.com/mongodb mv mongo-tools /root/mongodb-tools-r4.2.0/src/github.com/mongodb建立GCC软链接mkdir -p /opt/mongodbtoolchain/v3/bin ln -s /usr/bin/gcc   /opt/mongodbtoolchain/v3/bin/aarch64-mongodb-linux-gcc构建mongo-toolscd /root/mongodb-tools-r4.2.0/src/github.com/mongodb/mongo-tools . ./set_goenv.sh set_goenv ./build.sh ./build.sh ssl ./build.sh ssl sasl4 示例测试查看bin目录,可见5 FAQ如果遇到下面的错误,请升级go版本,重新配置路径。在http://www.cloud-onlinelab.cn/有编译好的mongodb-tools可供下载。
  • [技术干货] MongoDB 事务,复制和分片的关系-3
    8.MongoDB rocks 4.0是如何支持时间戳的本章节内容介绍如何改造RocksDB来支持基于时间戳的事务。主要的接口UserKey与InternalKey为了在RocksDB中支持时间戳,通过不同的时间戳表示在数据库中数据的不同版本。在原InternalKey的基础上增加了一个新的内容CommitTimestamp,加上原来的UserKey,LSN和数据的类型三部分内容,共四部分,如下所示。UserKey + CommitTimestamp(8byte) + LSN + Type接口定义用于控制事务的时间戳,ToTransactionDB提供了两个主要的时间戳相关的接口与之配合,具体如下:  virtual Status SetReadTimeStamp(const RocksTimeStamp& timestamp) = 0;  virtual Status SetCommitTimeStamp(const RocksTimeStamp& timestamp) = 0;SetReadTimeStamp用于设置事务的读时间戳,同一个事务只能设置一次,在分布式事务的场景下,即使在不同分区上执行的查询也需要使用同一个读时间戳,来保证各分区上看到的数据版本的一致性。SetCommitTimeStamp用于设置事务的提交时间戳,提交时间戳的基本要求是要大于事务的读时间戳。此外,对于同一个事务来说,可以多次调用,并且设置的提交时间戳可以不同。对于调用该接口之后的更新操作都使用此时间戳作为asif_commit_timestamps,每个asif_commit_timestamps时间戳会作为对应更新操作的InternalKey的CommitTimestamp保存到Memtable中。主要数据结构RocksDB数据库同样需要维护下面几个数据结构,用来管理事务RocksDB实例级别的全局时间戳信息。uncommitted_keys(未提交事务的UserKey列表)uncommitted_keys是保存未提交事务中发生更新的UserKey的容器,在写事务更新数据时,RocksDB将每个更新操作对应的Key插入到队列中,在事务提交时从队列中删除。当不同的事务并发执行更新操作时,则会通过此队列中保存的UserKey信息进行写冲突检测,如果已存在未提交的写事务更新了某个UserKey,则后执行更新操作的事务,会根据fisrt-update-win策略返回失败。committed_keys(已提交事务的UserKeyKey列表)committed_keys是保存已提交的UserKey信息的容器。在事务提交后,Key信息从uncommitted_keys队列中清除,插入到此队列中。为了进行写冲突检测维护一个uncommitted_keys队列是显而易见的,但是为什么要维护一个committed_keys队列呢?这里通过一个简单的例子进行说明这个队列的必要性。例子:针对同一个Key的两个更新事务,事务A开始时间(也就是ReadTs)为t1,更新操作Put1(A)执行时间为t3,提交时间戳为t5。那么,事务B开始时间(也就是ReadTs)为t2。对于场景1,存在更新操作Put2(B)发生在t4,则通过uncommitted_keys则可以发现存在冲突,事务B返回失败。对于场景2,存在操作Put2(B)发生在t6,这个场景刚好对应GSI中提交规则中snapshot(Ti) < commit(Tj) < commit(Ti)描述的场景,此时,事务B需要返回写冲突失败。如果在t6这个时间点,如果没有事务A的相关信息,那么则无法判断事务B是否应该成功提交还是失败。read_q(读时间戳队列)前面对于committed_keys只描述了插入的规则,但是这个队列什么时候进行清理呢?会不会不断变大?答案是“不会”。从上面的例子可知,事务B只关心CommitTs比ReadTs(B)大的事务,那么,我们就可以根据这一点,制定committed_keys队列的清理规则。数据库要维护一个读时间戳队列read_q,通过它找到最小的未提交事务的ReadTs,CommitTs小于此时间戳的事务,都可以从committed_keys中清理掉,从而解决此问题。oldest_ts(最早可见时间戳)上面对于committed_keys的清理机制其实会有一个漏洞,就是如果事务A的信息在清理之后,如果存在一个新事务设置的ReadTs比事务A的CommitTs要早的化,则又会出现违背GSI提交规则的情况。为了解决这个问题,这里增加一个“最早的可见时间戳”oldest_ts,在调用SetReadTimeStamp接口设置ReadTs时,如果设置的时间戳小于oldest_ts,则返回失败。那么committed_keys队列清理的机制则修改为清理掉min(oldest_ts,read_q.begin().ts)之前的所有已提交事务的信息。“最早可见时间戳”的值由Mongo层进行设置,更新oldest_ts时,如果oldest_ts大于read_q.begin().ts,则取自动获取中read_q较小的为提交的ReadTs作为oldest_ts。通过增加上面几个接口和数据结构,使用RocksDB具备了基本的支持 基于时间戳事务的能力。基本操作流程由于引入了时间戳后,关系到数据可见性,数据清理的流程,都会涉及到对应代码逻辑的调整,在下面几个关键步骤中需要把InternalKey中的Timestamp取出进行逻辑判断。写操作每个写事务开始后,首先要设置ReadTs,然后通过接口TOTransaction::Put接口进行更新操作,在执行时通过对uncommitted_keys和committed_keys的检索,进行写冲突判断。如果无写冲突,则缓存在事务的writebatch中,在事务提交时,通过DBImpl::WriteImpl把每个更新操作的UserKey进行重写,在其中增加CommitTs作为InternalKey保存到Memtable中。读操作对于事务中包含更新操作的读事务,需要先从WriteBatch中write_batch_.GetFromBatchAndDB读最新数据,如果WriteBatch中无数据,则继续从Memtable和SST中继续查询,对于UserKey相同的数据,需要进行时间戳比对,当InternalKey中的时间戳大于ReadTs时,则继续遍历该UserKey的更早版本的数据,直到找到或者结束。Compaction操作在Compaction的过程中,旧版本数据的清洗过滤:对于哪些key的数据需要保留,哪些数据需要清除的判断逻辑中,增加了对时间戳因素的处理,需要保证大于oldest_ts的数据不会被清理。
  • [技术干货] MongoDB 事务,复制和分片的关系-2
    5.WiredTiger对GSI的实现上文中我们介绍了GSI的基本概念,WiredTiger在不使用timestampAPI时,实现了CSI。它的冲突检测算法如下:Transaction's localViewTransaction -> {    // Global monotonicly increasing id, allocated when transaction starts.    txnId: int    // The local view of the global transaction list.    snapshots: []int}Global Transaction ListGTL -> {    // used for allocating one txn's txnId txnIdAllocator: atomic<int> // uncommitted transactions activeTxns: List<Transaction*> // protect GTL from concurrent visit rwlock: RwLock}Transaction's mvcc prodecure Begin -> {     With(GTL.rwlock.wlock()) {         txn = Transaction {             txnId: GTL.txnIdAllocator++              snapshots: GTL.activeTxns.copy()         }         GTL.activeTxns.add(txn)     }     // txns are naturalled sorted in this way. } Update(key, value)-> { // get the updatelist of a key from the btree // pick its header    upd = GetUpdateList(key).header    if (upd.txnId > self.snapshots.back()) {        throw Conflict    }    if (txn.snapshots.find(upd.txnId) {        throw Conflict    }    GetUpdateList(key).insertAtFront({self.txnId, value}) } Commit -> {     With(GTL.rwlock.wlock()) {        GTL.activeTxns.remove(self)     } }在事务开始时,对将全局未提交的事务id copy 到事务的localView中的snapshots链表中。采用firstUpdateWin的冲突检测策略,在更新记录时如果违背SI,当且仅当:如果key的mvcc链头的txnId 大于localView的snapshots中的最大值,如果key的mvcc链头的txnId在本事务的localView的snapshots上述规则是经典的CSI的实现,在PG中也是类似的做法,正确性证明请见这篇文章WiredTiger timestamp 与 GSIWiredTiger中引入readTimestamp和commitTimestamp,readTimestamp即GSI中的snapshotTimestamp。Transaction -> {    // Global monotonicly increasing id, allocated when transaction starts.    txnId: int    // The local view of the global transaction list.    snapshots: []int    read_timestamp: int    commit_timestamp: int}相应的冲突检测判断部分,也引入了GSI的规则即本事务的read_timestamp 小于已提交key的commit_timestamp 即认为违背GSI这里默认本事务的commit_timestamp 一定大于key的mvcc链中的最大的commit_timestamp,即对于某个特定的key,该key的commit_timestamp的顺序一定要和物理提交顺序一致,这是由application-level(mongoServer层)保证的,那么Mongo层是如何保证这一点的呢,请继续往下看。 Update(key, value)-> { // get the updatelist of a key from the btree // pick its header    upd = GetUpdateList(key).header    if (upd.txnId > self.snapshots.back()) {        throw Conflict    }    if (txn.snapshots.find(upd.txnId) {        throw Conflict    }    if (txn.read_timestamp < upd.commit_timetamp) {        throw Conflict    }    GetUpdateList(key).insertAtFront({self.txnId, value}) }前缀一致的复制(PrefixConsistent Replication)MongoDB从节点拉取主节点的Oplog来实现复制,主节点上的Oplog是并行提交的,因此在Oplog的末尾存在(时间上的)乱序。如果不屏蔽乱序的话,从节点读到的Oplog就会产生空洞。举个例子:T0: txn1.start(); txn1.setCommitTimestamp(1)T1: txn2.start(); txn2.setCommitTimestamp(2)T2: txn3.start(); txn3.setCommitTimestamp(3)T3: txn3.commit()  // local.oplog.rs: [3]T4: txn1.commit()  // local.oplog.rs: [1,3]T5: txn2.commit()  // local.oplog.rs: [1,2,3]从空间上来讲,oplog的顺序就是事务的commitTimestamp顺序,即[1,2,3]的顺序。但是从时间上来讲,物理提交顺序不可控,尽管三条oplog最终会生成,但是不同时间点看到的oplog,是有空洞的。比如T4时刻的oplog是[1,3],如果从节点此刻去遍历Oplog,就会丢失commitTimetamp=2的记录。CommitQueue 与 AllCommitTimestamp的计算从本质上讲,所谓前缀一致的复制,指的是,如果我读到了某条commitTimestamp = X的数据,就不允许在我读这条数据后,对数据库做出commitTimestamp < X的更改。因此,不难想到我们可以维护一个最大的没有空洞的commitTimestamp,叫做AllCommitTimestamp, 形式化的定义如下, 也就是GSI读规则的第4点,描述了不会出现空洞:上面我们分析过,事务有commitTimestamp属性。在wiredTiger中,事务被按照commitTimestamp排序,即TAILQ_HEAD(__wt_txn_cts_qh, __wt_txn) commit_timestamph;我们知道,事务开始和设置commitTimestamp不在一个临界区中,因此事务的txnId的顺序和事务的commitTimestamp顺序并不完全一样,这也是为什么wiredTiger除了维护全局的active_transaction_list这个活跃事务链之外,还维护了一个commit_timestamph的队列。active_transaction_list可以看作按照txnId有序的队列,commit_timestamph可以看作按照commitTimestamp有序的队列。commit_timestamph 队列可以用来辅助AllCommitTimestamp的计算。相关伪代码如下:for (auto v : commit_timestamph) {  if (v.committed || v.rollbacked) {    continue;  }  return v.commit_timestamp - 1;}根据上述伪代码,wiredTiger中,从左往右遍历commit_timestamph队列,找到第一个没有提交的事务T,allCommitTimestamp = T.commitTimestamp - 1; 这也很容易理解,T为commitTimestamp最小的未提交事务,那么比它更小的commitTimestamp所属的事务必然都已经提交了。这里有个假设,对commit_timestamph的插入操作必然都是插入到末尾,换句话说,commitTimestamp的分配必然是递增的,这一点是由application-level(mongodb-server层)保证的,那么Mongo层是如何保证这一点的呢,请继续往下看。MongoDB用来做复制的,读oplog的读事务,它的readTimestamp由AllCommitTimestamp得到,因此能够做到前缀一致的复制。遗留问题的解决上面我们留了两个遗留问题Mongo如何做到对于某个特定的key,该key的commit_timestamp的顺序和物理提交顺序一致Mongo如何做到对commit_timestamph的插入操作必然都是插入到末尾,换句话说,commitTimestamp的分配必然是递增的这两个问题,是wiredTiger不做任何保证的,如果违背了,数据就错乱了。MongoDB的同一段代码保证了上述两点,或者说,上述问题其实是一个问题。就是所谓的PBMA策略 1. // the global timestamp allocator 2. GTA -> { 3.     lock     Lock 4.     globalTs int 5. }   6. // the timestamp allocation prodecure 7. Transaction txn 8. txn.Begin() 9. with(GTA.lock) {10.      txn.setCommitTimestamp(globalTs++)11. }12. ......13. txn.Commit()PBMA策略的关键要素是事务先开始,再分配提交时间戳分配提交时间戳要保证全局递增(加锁)PBMA为何能一箭双雕的保证上面两个遗留问题呢,正确性的证明在这里简单的说,对于某个特定的key,如果有两个事务都在修改, 那么根据事务冲突的规则可知, 这两个事务,一定是一个提交后,一个才开始的,满足 commit(Ti) < snapshot(Tj)的关系,对于本事务,也满足 snapshot(Tj) < commit(Tj) 的关系, 那么可知, commit(Ti) 是小于 commit(Tj)的, 即commit 顺序跟物理顺序相一致。上面介绍了MongoDB副本集系统跟4.0的时间戳的一些实现, 底下我们需要了解一下在MongoDB 4.2 的分布式事务又是如何使用时间戳来实现的。6.分布式系统的时钟分布式系统,各个节点时钟的是不一致:1,分布式各节点,时钟不一致,会导致分布式事务,无法定序。2,对于分布式系统,一般都有时钟同步服务来保证各个节点的时间一致性。3,但是即使存在时钟同步服务,任一时间点的绝对一致是很难达到的。这对于分布式系统来说,是不可依赖的。4,分布式事务处理,如果不想为节点时间差专门去做逻辑处理,则需要基于一个稳定一致的时钟系统。— 即:逻辑时钟。7.MongoDB sharding与时间戳的关系HLC与ChangeStream从3.6之后,MongoDB就引入了HLC的概念,在实现上的体现是,Mongos/Mongod的每次操作都会带ClusterTime,ClusterTime也作为Mongos和Client交互传递的Token。HLC在MongoDB中的体现在以下几点:ClusterTime在Client/Mongos/Mongod的所有操作(读/写/命令)之间传递,每个进程收到更大的ClusterTime后就更新本地的ClusterTime。Mongod侧,Oplog的分配与物理时间的递增会作为ClusterTime推进的驱动力。Mongos/Mongod侧,物理时间的偏斜会作为ClusterTime推进的驱动力。ClusterTime传递给Client时是被签名的,以防止被篡改使得Server端的ClusterTime无限制变大。Client本身只传递不推进ClusterTime。相关代码在 logical_clock.h::advanceClusterTime,mongos和mongod在rpc的处理函数处均有调用。void processCommandMetadata {    return logicalClock->advanceClusterTime(signedTime.getTime());}通过上述HLC的机制,mongos/mongod各个进程之间的物理时钟不需要协调,集群的ClusterTime会随着mongos/mongod/configsvr的每次交互相互接近。如果没有HLC机制,各个shard之间的写操作的OplogTime相互独立,各个shard上的oplogTime没有totalOrder,全局的ChangeStream就无法实现。CLOCK-SI与MongoDB 的2PC分布式事务MongoDB的2PC和CLOCK-SI paper中的算法非常接近(由于官方并未明确说明,因此并不可以说,MongoDB的2PC是对CLOCK-SI paper的实现)。MongoDB的分布式事务流程介绍可以参考这篇文章CLOCK-SI分布式系统中,考虑到多事务并发,对每个单个事务的开始时间是严格要求的,只有在不同节点上的单个事务都在同一个时间戳开始执行,那么多个事务在每个节点上的顺序才是一致的。考虑到分布式系统会有 clock skew (时钟偏斜) 的场景, 我们就需要在其他节点等待时间流逝到这个时间,才可以开始执行, 如下图所示:上图中事务 T1,因为在P1节点是在 t时刻开始的, 所以在事务在 P2节点运行时,也需要等到t时刻才能运行, 如上图所示 P2 节点时间早于P1节点, 那么事务在P2节点,就需要等这个延迟时间。这样会造成事务执行过程中的延迟的增大。CLOCK-SI就是为了提出了一个方法,用来解决上面事务执行过程中的延迟问题的,读规则如下CLOCK-SI 的读规则提供了一个多分区情况下的一致的snapshot, 主要有两点1> 如何设置事务T的SnapshotTime2> 如何延迟读操作知道每个节点都有一个一致的snapshot第2行,表示我们可以将snapshotTime适当的向前调整,这样,可以减少这个事务在远端节点的等待时间, 远端节点等待操作,见 第 21 和 22 行。第7-9行, 如果查看的oid,发现是事务T’修改的,且T’事务正在提交,并且T’事务的提交时间小于事务T(对事物T可见的话), 那么应该等到T’提交成功后,再读取。因为 commiting 也可能会提交失败, 所以事务T不能直接读取;因为 已经判断了,T’ 对事务T可见,所以事务T不能忽略。综上所述,事务T的读操作只能等待。第 11- 15行, 与上面类似,如果 T’ 已经 prepared,且 T’ 对事务T可见,那么 读操作需要等待到T’成功提交后,再读取。原因与上面描述类似21-22 行, 规定了读请求在每个节点上,都需要等到远端物理时钟到了后,才可执行。写规则如下写规则针对事务是单点的,还是跨节点的,又分为 local commit, 和 distributed commit采取两阶段的提交方式先发送 prepare T 请求到所有事务参与节点等待所有事务参与节点返回 T 事务已经被 prepared的结果设置T 状态为 commiting状态选取最大的 prepare timestamps 作为 commit timestamp, 并分配给事务T记录事务T的提交时间和提交决策设置事务T为commited状态将 commit T 发送给所有事务参与节点事务参与节点,一旦收到 prepare T的请求,检查 事务 T 在本节点是否可以被修改记录事务的写操作结果和协调者的id将事务T状态设置为 prepared设置T.prepared 时间为本地时间发送 T 事务已经prepared成功的信息给事务T的协调者事务参与节点, 收到 commit T的消息后记录事务T 的commitTime将事务T 设置为 commited 状态MongoDB如何处理读操作读取了处于prepare状态的值可以看出,CLOCK-SI中明确定义了, 当分布式事务处于Prepare 状态时, 如果其他读事务可以看见这个 Prepare, 那么需要等到分布式事务状态变为提交后,才可以继续执行。这对应到 mongodb 4.2 代码中,在wt的 当读事务 碰到了 正在 Prepare的事务,会返回一个 WT_PREPARE_CONFLICT 来表示当前并不是一个合适时机, 我们需要等到这个写事务完成。而上层的MongoDB如果碰到 WT_PREPARE_CONFLICT, 则会等待事务提交后,继续尝试读取,int ret = wiredTigerPrepareConflictRetry(opCtx, [&] { return c->search(c); });所有对底层 cursor的遍历和查询操作,都会被封装到 wiredTigerPrepareConflictRetry 函数中, wiredTigerPrepareConflictRetry 会根据传入的函数对象的执行结果进行retry,如果返回结果不是 WT_PREPARE_CONFLICT, 那么 wiredTigerPrepareConflictRetry 直接返回,如果返回对象是 wiredTigerPrepareConflictRetry, 那么会下一次有事务提交或者abort时,再次调用函数对象检查一次。更多关于 WT 层修改,参考这篇文章为什么 commitTs=max{prepareTs}首先我们需要明确的是,分布式事务哪怕在多个节点运行,最终的 commit timestamp 只能有一个。因为 commit timestamp 必须大于 prepare timestamp, 所以 commitTs=max{prepareTs} 是一个合理的规定。如果不认同上面规则, 认为 commit timestamp 可以小于某个节点的 prepare timestamp, 那会违反事务的原子性要求的,举例如下:prepareTs(P1, T1) = commitTs(T1) < snapshotTs(T2) < prepareTs(P2, T1)那么T2事务在节点1,可以看见T1的修改,但是在节点2, 是看不到 T1修改的, 这就违背了 事务的原子性的要求。
  • [技术干货] MongoDB 事务,复制和分片的关系-2
    5.WiredTiger对GSI的实现上文中我们介绍了GSI的基本概念,WiredTiger在不使用timestampAPI时,实现了CSI。它的冲突检测算法如下:Transaction's localViewTransaction -> {    // Global monotonicly increasing id, allocated when transaction starts.    txnId: int    // The local view of the global transaction list.    snapshots: []int}Global Transaction ListGTL -> {    // used for allocating one txn's txnId txnIdAllocator: atomic<int> // uncommitted transactions activeTxns: List<Transaction*> // protect GTL from concurrent visit rwlock: RwLock}Transaction's mvcc prodecure Begin -> {     With(GTL.rwlock.wlock()) {         txn = Transaction {             txnId: GTL.txnIdAllocator++              snapshots: GTL.activeTxns.copy()         }         GTL.activeTxns.add(txn)     }     // txns are naturalled sorted in this way. } Update(key, value)-> {  // get the updatelist of a key from the btree  // pick its header    upd = GetUpdateList(key).header    if (upd.txnId > self.snapshots.back()) {        throw Conflict    }    if (txn.snapshots.find(upd.txnId) {        throw Conflict    }    GetUpdateList(key).insertAtFront({self.txnId, value}) } Commit -> {     With(GTL.rwlock.wlock()) {        GTL.activeTxns.remove(self)     } }在事务开始时,对将全局未提交的事务id copy 到事务的localView中的snapshots链表中。采用firstUpdateWin的冲突检测策略,在更新记录时如果违背SI,当且仅当:如果key的mvcc链头的txnId 大于localView的snapshots中的最大值,如果key的mvcc链头的txnId在本事务的localView的snapshots上述规则是经典的CSI的实现,在PG中也是类似的做法,正确性证明请见这篇文章WiredTiger timestamp 与 GSIWiredTiger中引入readTimestamp和commitTimestamp,readTimestamp即GSI中的snapshotTimestamp。Transaction -> {    // Global monotonicly increasing id, allocated when transaction starts.    txnId: int    // The local view of the global transaction list.    snapshots: []int    read_timestamp: int    commit_timestamp: int}相应的冲突检测判断部分,也引入了GSI的规则即本事务的read_timestamp 小于已提交key的commit_timestamp 即认为违背GSI这里默认本事务的commit_timestamp 一定大于key的mvcc链中的最大的commit_timestamp,即对于某个特定的key,该key的commit_timestamp的顺序一定要和物理提交顺序一致,这是由application-level(mongoServer层)保证的,那么Mongo层是如何保证这一点的呢,请继续往下看。 Update(key, value)-> {  // get the updatelist of a key from the btree  // pick its header    upd = GetUpdateList(key).header    if (upd.txnId > self.snapshots.back()) {        throw Conflict    }    if (txn.snapshots.find(upd.txnId) {        throw Conflict    }    if (txn.read_timestamp < upd.commit_timetamp) {        throw Conflict    }    GetUpdateList(key).insertAtFront({self.txnId, value}) }前缀一致的复制(PrefixConsistent Replication)MongoDB从节点拉取主节点的Oplog来实现复制,主节点上的Oplog是并行提交的,因此在Oplog的末尾存在(时间上的)乱序。如果不屏蔽乱序的话,从节点读到的Oplog就会产生空洞。举个例子:T0: txn1.start(); txn1.setCommitTimestamp(1)T1: txn2.start(); txn2.setCommitTimestamp(2)T2: txn3.start(); txn3.setCommitTimestamp(3)T3: txn3.commit()  // local.oplog.rs: [3]T4: txn1.commit()  // local.oplog.rs: [1,3]T5: txn2.commit()  // local.oplog.rs: [1,2,3]从空间上来讲,oplog的顺序就是事务的commitTimestamp顺序,即[1,2,3]的顺序。但是从时间上来讲,物理提交顺序不可控,尽管三条oplog最终会生成,但是不同时间点看到的oplog,是有空洞的。比如T4时刻的oplog是[1,3],如果从节点此刻去遍历Oplog,就会丢失commitTimetamp=2的记录。CommitQueue 与 AllCommitTimestamp的计算从本质上讲,所谓前缀一致的复制,指的是,如果我读到了某条commitTimestamp = X的数据,就不允许在我读这条数据后,对数据库做出commitTimestamp < X的更改。因此,不难想到我们可以维护一个最大的没有空洞的commitTimestamp,叫做AllCommitTimestamp, 形式化的定义如下, 也就是GSI读规则的第4点,描述了不会出现空洞:上面我们分析过,事务有commitTimestamp属性。在wiredTiger中,事务被按照commitTimestamp排序,即TAILQ_HEAD(__wt_txn_cts_qh, __wt_txn) commit_timestamph;我们知道,事务开始和设置commitTimestamp不在一个临界区中,因此事务的txnId的顺序和事务的commitTimestamp顺序并不完全一样,这也是为什么wiredTiger除了维护全局的active_transaction_list这个活跃事务链之外,还维护了一个commit_timestamph的队列。active_transaction_list可以看作按照txnId有序的队列,commit_timestamph可以看作按照commitTimestamp有序的队列。commit_timestamph 队列可以用来辅助AllCommitTimestamp的计算。相关伪代码如下:for (auto v : commit_timestamph) {  if (v.committed || v.rollbacked) {    continue;  }  return v.commit_timestamp - 1;}根据上述伪代码,wiredTiger中,从左往右遍历commit_timestamph队列,找到第一个没有提交的事务T,allCommitTimestamp = T.commitTimestamp - 1; 这也很容易理解,T为commitTimestamp最小的未提交事务,那么比它更小的commitTimestamp所属的事务必然都已经提交了。这里有个假设,对commit_timestamph的插入操作必然都是插入到末尾,换句话说,commitTimestamp的分配必然是递增的,这一点是由application-level(mongodb-server层)保证的,那么Mongo层是如何保证这一点的呢,请继续往下看。MongoDB用来做复制的,读oplog的读事务,它的readTimestamp由AllCommitTimestamp得到,因此能够做到前缀一致的复制。遗留问题的解决上面我们留了两个遗留问题Mongo如何做到对于某个特定的key,该key的commit_timestamp的顺序和物理提交顺序一致Mongo如何做到对commit_timestamph的插入操作必然都是插入到末尾,换句话说,commitTimestamp的分配必然是递增的这两个问题,是wiredTiger不做任何保证的,如果违背了,数据就错乱了。MongoDB的同一段代码保证了上述两点,或者说,上述问题其实是一个问题。就是所谓的PBMA策略 1. // the global timestamp allocator 2. GTA -> { 3.     lock     Lock 4.     globalTs int 5. }   6. // the timestamp allocation prodecure 7. Transaction txn 8. txn.Begin() 9. with(GTA.lock) {10.      txn.setCommitTimestamp(globalTs++)11. }12. ......13. txn.Commit()PBMA策略的关键要素是事务先开始,再分配提交时间戳分配提交时间戳要保证全局递增(加锁)PBMA为何能一箭双雕的保证上面两个遗留问题呢,正确性的证明在这里简单的说,对于某个特定的key,如果有两个事务都在修改, 那么根据事务冲突的规则可知, 这两个事务,一定是一个提交后,一个才开始的,满足 commit(Ti) < snapshot(Tj)的关系,对于本事务,也满足 snapshot(Tj) < commit(Tj) 的关系, 那么可知, commit(Ti) 是小于 commit(Tj)的, 即commit 顺序跟物理顺序相一致。上面介绍了MongoDB副本集系统跟4.0的时间戳的一些实现, 底下我们需要了解一下在MongoDB 4.2 的分布式事务又是如何使用时间戳来实现的。6.分布式系统的时钟分布式系统,各个节点时钟的是不一致:1,分布式各节点,时钟不一致,会导致分布式事务,无法定序。2,对于分布式系统,一般都有时钟同步服务来保证各个节点的时间一致性。3,但是即使存在时钟同步服务,任一时间点的绝对一致是很难达到的。这对于分布式系统来说,是不可依赖的。4,分布式事务处理,如果不想为节点时间差专门去做逻辑处理,则需要基于一个稳定一致的时钟系统。— 即:逻辑时钟。7.MongoDB sharding与时间戳的关系HLC与ChangeStream从3.6之后,MongoDB就引入了HLC的概念,在实现上的体现是,Mongos/Mongod的每次操作都会带ClusterTime,ClusterTime也作为Mongos和Client交互传递的Token。HLC在MongoDB中的体现在以下几点:ClusterTime在Client/Mongos/Mongod的所有操作(读/写/命令)之间传递,每个进程收到更大的ClusterTime后就更新本地的ClusterTime。Mongod侧,Oplog的分配与物理时间的递增会作为ClusterTime推进的驱动力。Mongos/Mongod侧,物理时间的偏斜会作为ClusterTime推进的驱动力。ClusterTime传递给Client时是被签名的,以防止被篡改使得Server端的ClusterTime无限制变大。Client本身只传递不推进ClusterTime。相关代码在 logical_clock.h::advanceClusterTime,mongos和mongod在rpc的处理函数处均有调用。void processCommandMetadata {    return logicalClock->advanceClusterTime(signedTime.getTime());}通过上述HLC的机制,mongos/mongod各个进程之间的物理时钟不需要协调,集群的ClusterTime会随着mongos/mongod/configsvr的每次交互相互接近。如果没有HLC机制,各个shard之间的写操作的OplogTime相互独立,各个shard上的oplogTime没有totalOrder,全局的ChangeStream就无法实现。CLOCK-SI与MongoDB 的2PC分布式事务MongoDB的2PC和CLOCK-SI paper中的算法非常接近(由于官方并未明确说明,因此并不可以说,MongoDB的2PC是对CLOCK-SI paper的实现)。MongoDB的分布式事务流程介绍可以参考这篇文章CLOCK-SI分布式系统中,考虑到多事务并发,对每个单个事务的开始时间是严格要求的,只有在不同节点上的单个事务都在同一个时间戳开始执行,那么多个事务在每个节点上的顺序才是一致的。考虑到分布式系统会有 clock skew (时钟偏斜) 的场景, 我们就需要在其他节点等待时间流逝到这个时间,才可以开始执行, 如下图所示:上图中事务 T1,因为在P1节点是在 t时刻开始的, 所以在事务在 P2节点运行时,也需要等到t时刻才能运行, 如上图所示 P2 节点时间早于P1节点, 那么事务在P2节点,就需要等这个延迟时间。这样会造成事务执行过程中的延迟的增大。CLOCK-SI就是为了提出了一个方法,用来解决上面事务执行过程中的延迟问题的,读规则如下CLOCK-SI 的读规则提供了一个多分区情况下的一致的snapshot, 主要有两点1> 如何设置事务T的SnapshotTime2> 如何延迟读操作知道每个节点都有一个一致的snapshot第2行,表示我们可以将snapshotTime适当的向前调整,这样,可以减少这个事务在远端节点的等待时间, 远端节点等待操作,见 第 21 和 22 行。第7-9行, 如果查看的oid,发现是事务T’修改的,且T’事务正在提交,并且T’事务的提交时间小于事务T(对事物T可见的话), 那么应该等到T’提交成功后,再读取。因为 commiting 也可能会提交失败, 所以事务T不能直接读取;因为 已经判断了,T’ 对事务T可见,所以事务T不能忽略。综上所述,事务T的读操作只能等待。第 11- 15行, 与上面类似,如果 T’ 已经 prepared,且 T’ 对事务T可见,那么 读操作需要等待到T’成功提交后,再读取。原因与上面描述类似21-22 行, 规定了读请求在每个节点上,都需要等到远端物理时钟到了后,才可执行。写规则如下写规则针对事务是单点的,还是跨节点的,又分为 local commit, 和 distributed commit采取两阶段的提交方式先发送 prepare T 请求到所有事务参与节点等待所有事务参与节点返回 T 事务已经被 prepared的结果设置T 状态为 commiting状态选取最大的 prepare timestamps 作为 commit timestamp, 并分配给事务T记录事务T的提交时间和提交决策设置事务T为commited状态将 commit T 发送给所有事务参与节点事务参与节点,一旦收到 prepare T的请求,检查 事务 T 在本节点是否可以被修改记录事务的写操作结果和协调者的id将事务T状态设置为 prepared设置T.prepared 时间为本地时间发送 T 事务已经prepared成功的信息给事务T的协调者事务参与节点, 收到 commit T的消息后记录事务T 的commitTime将事务T 设置为 commited 状态MongoDB如何处理读操作读取了处于prepare状态的值可以看出,CLOCK-SI中明确定义了, 当分布式事务处于Prepare 状态时, 如果其他读事务可以看见这个 Prepare, 那么需要等到分布式事务状态变为提交后,才可以继续执行。这对应到 mongodb 4.2 代码中,在wt的 当读事务 碰到了 正在 Prepare的事务,会返回一个 WT_PREPARE_CONFLICT 来表示当前并不是一个合适时机, 我们需要等到这个写事务完成。而上层的MongoDB如果碰到 WT_PREPARE_CONFLICT, 则会等待事务提交后,继续尝试读取,int ret = wiredTigerPrepareConflictRetry(opCtx, [&] { return c->search(c); });所有对底层 cursor的遍历和查询操作,都会被封装到 wiredTigerPrepareConflictRetry 函数中, wiredTigerPrepareConflictRetry 会根据传入的函数对象的执行结果进行retry,如果返回结果不是 WT_PREPARE_CONFLICT, 那么 wiredTigerPrepareConflictRetry 直接返回,如果返回对象是 wiredTigerPrepareConflictRetry, 那么会下一次有事务提交或者abort时,再次调用函数对象检查一次。更多关于 WT 层修改,参考这篇文章为什么 commitTs=max{prepareTs}首先我们需要明确的是,分布式事务哪怕在多个节点运行,最终的 commit timestamp 只能有一个。因为 commit timestamp 必须大于 prepare timestamp, 所以 commitTs=max{prepareTs} 是一个合理的规定。如果不认同上面规则, 认为 commit timestamp 可以小于某个节点的 prepare timestamp, 那会违反事务的原子性要求的,举例如下:prepareTs(P1, T1) = commitTs(T1) < snapshotTs(T2) < prepareTs(P2, T1)那么T2事务在节点1,可以看见T1的修改,但是在节点2, 是看不到 T1修改的, 这就违背了 事务的原子性的要求。
  • [技术干货] MongoDB 事务,复制和分片的关系-1
    1.前言MongoDB基于wiredTiger提供的泛化SI的功能,重构了readHistory(readMajority)的能力基于wiredTiger提供的AllCommittedTimestamp API,重构了前缀一致的主从复制(Prefix-Consistent-Replication)引入混合逻辑时钟(HLC),每个节点(Mongos/Mongod)的逻辑时钟维持在接近的值,基于此实现ChangeStream, 结合HLC与CLOCK-SI,实现分布式事务,HLC和泛化SI,CLOCK-SI两篇Paper可以作为理解MongoDB的设计的理论参考(这里并没有说MongoDB是Paper的实现)。本文尝试对Mongo的复制和分布式事务的原理进行描述,在必要的地方,对实现的正确性进行论证,希望能为MongoDB内核爱好者提供一些参考。2.MongoDB副本集事务介绍MongoDB 副本集的事务MongoDB副本集的复制是基于raft协议,相比于Paxos,raft协议实现简单,但是raft协议只支持single-master,对应的,MongoDB的副本集是主从架构,而且只有主节点支持写入操作。MongoDB副本集的事务管理,包括冲突检测,事务提交等关键操作,都只在主节点上完成。也就是说副本集的事务在事务管理方面,跟单节点逻辑基本一致。MongoDB的事务,仍然是实现了 ACID 四个特性, MongoDB使用 SI 作为事务的隔离级别。3.SI的简介SI,即SnapshotIsolation,中文称为快照隔离,是一种mvcc的实现机制,它在1995年的A Critique of ANSI SQL Isolation Levels中被正式提出。因快照时间点的选取上的不同,又分为Conventional Si 和 Generalized SI。CSI(Convensional SI)CSI 选取当前最新的系统快照作为事务的读取快照就是在事务开始的时候,获得当前db最新的snapshot,作为事务的读取的snapshot,snapshot(Ti) = start(Ti)可以减少写事务冲突发生的概率,并且提供读事务读取最新数据的能力一般我们说一个数据库支持SI隔离级别,其实默认是说支持CSI。比如RocksDB支持的SI就是CSI,WiredTiger在3.0版本之前支持的SI也是CSI。GSI(Generalized SI)GSI选择历史上的数据库快照作为事务的读取快照,因此CSI可以看作GSI的一个特例。在复制集的情况下,考虑 CSI, 对于主节点上的事务,每次事务的开始时间选取的系统 最新的 快照, 但是对于其他从节点来说, 并没有 统一的 “最新的” 快照这个概念。泛化的快照实际上是基于快照观测得到的,对于当前事务来说,我们通过选取合适的 更早时间的快照,可以让 从节点上的事务正确且无延迟的执行。举例如下:例如当前数据库的状态是, S={T1, T2, T3},  现在要开始执行T4,如果我们知道T4要修改的值,在T3上没有被修改, 那么我们在执行T4的时候, 就可以按照 T2 commit后的snashot进行读取。如何选择更早的时间点,需要满足下面的规则,符号定义Ti: 事务iXi: 被事务i修改过的X变量snapshot(Ti): 事务i的选取的快照时间start(Ti): 事务i的开始时间commit(Ti): 事务i的提交时间abort(Ti): the time when Ti is aborted.end(Ti): the time when Ti is committed or aborted.公式解释读规则G1.1, 如果变量X被本事务修改了值且读取到了新的值, 那么 读操作一定在写操作后面;G1.2, 如果事务i读取了事务j更新的变量的X, 那么一定不会有事务i更新X的操作,在事务i读取了事务j更新的变量的X这个操作前面;G1.3, 事务j的提交时间早于事务i的快照时间;G1.4, 对于任意一个会更新变量X的事务k, 那么这个事务k一定满足, 要么事务k的提交时间小于事务j, 要么这个事务k的提交时间大于事务i。写规则G2, 对于任意已经在提交历史里的两个事务,Ci, Cj, 那么一定可以保证当 事务j的commit时间戳在 事务i的观测时间段内时(snapshot(Ti), commit(Ti)), 那么他们更新的变量交集一定为空。PCSI(PREFIX-CONSISTENT SNAPSHOT ISOLATION SI)GSI 只是定义了一个范围的range,都可以作为SI使用,并没有定义具体应该选择哪个SI。PCSI 是为了复制集而设计的。对于一个事务Ti 要开S节点开始运行, 那么 S节点将必须包含这个事务所需要的所有前置事务都必须运行且提交。相比较于GSI, PCSI的读规则,额外增加了 P1.5 规则。SI的提交时间戳设置,依据 A Critique of ANSI SQL Isolation Levels 中的描述, 提交时间戳的设置应该是单调递增的。新设置的时间戳,应该大于系统中已经存在的开始时间戳和提交时间戳。SI 读取时间戳的设置,必须保证比当前系统中正在运行的事务的最小的提交时间戳还要小, 因为一旦大于当前系统中正在运行事务的最小的提交时间戳,那么这个读事务读取到的数据就是未定义的, 取决于读事务启动的时间,而不是snapshot的时间,这违背了 一致性的要求。举例如下当前已经完成的事务是T1,正在运行的事务是T2, 将要运行的读事务是T3,  如果 T3的读时间戳大于T2事务提交时间戳, 并且T2事务正在运行,等到T2事务执行完后。我们观察这个 database,就会发现 他违背了GSI,事务执行顺序如下所示是:  T1 commited and commitTs(1) -> T2 start -> T2 set commitTs(2) -> T3 start -> T3 set snapshotTs(3) -> T3 commit -> pointA -> T2 commit -> pointB那么可知, T3事务实际读取的值是 T1事务的值。但根据 pointB 点来看 GSI的读规则 1.4 的要求,会发现, 如果T3读到T1的事务的修改,那么必然要求, T3和T1之间没有空洞。但实际上 T2 是落在了 T3和T1之间的, 也就是说, 违反了 GSI 1.4的读规则。所以我们必须规定, SI 读取时间戳的设置,必须保证比当前系统中正在运行的事务的最小的提交时间戳还要小。4.MongoDB副本集时间戳应用MongoDB 4.0的复制也是利用时间戳特性解决了3.x系列MongoDB从节点复制造成从节点性能下降的关键方案。MongoDB oplog 乱序问题MongoDB主备节点的数据同步并不基于WiredTiger的wal日志来做的。相反,mongodb会将每次操作的数据变更写入到一个叫做oplog的集合里。oplog这个集合,虽然名字带有log,但实际上,它是一个MongoDB的表, 对oplog的写入,并不是 append的方式修改的, 而是呈现出一种尾部乱序的方式。对于oplog来说, oplog的读取顺序是按照TS字段来排序的, 跟上层的提交顺序无关。所以存在后开始的事务,在oplog先读取的场景。oplog 空洞因为出现了乱序,所以从节点在读取oplog的时候,就会在某些时间点出现空洞。举例如下:时间点1: oplog 顺序为: Ta -> Tb, 此时系统中还有一个事务Tc在运行时间点2: oplog 顺序为: Ta -> Tc -> Tb, 当Tc运行结束后, 因为ts的顺序, 看起来是将Tc插入到了Ta和Tb之间。那么当 从节点 在时间点1 reply 到 Tb的时候, 实际上是漏了 Tc的,这个就是oplog的空洞, 他产生的原因是因为,从节点如果每次读取oplog最新的数据,就有可能会得到一个不连续的数据, 例如 时间点1上 Ta-> Tb. 这就是oplog空洞。在具体复制逻辑中,我们必须想办法来从节点读取到含有空洞的oplog数据。这也是GSI的要求, snapshot的选取不能含有空洞。因为 oplog的Ts是mongo上层给的,我们很容易知道哪些事务有哪些ts, 我们再将这个ts 作为事务的commitTs 放到 oplog存储的事务里, 这样我们读取 oplog的顺序事务的可见性顺序相一致了,在这种情况下,我们就可以 根据 活跃事务列表, 就可以将oplog 分为两个部分,假设活跃commitTs列表的事务是 {T10, T11, T12}, 活跃事务列表是 {T10, T11, T12, T13, T14}, 那么意味着, 目前有 T10, T11, T12, T13, T14 再运行,并且 T10, T11, T12 已经设置了 commitTs, 又因为 上面讨论的 commitTs 是单调递增的, 那么我们可知, T13, T14 的commitTs 一定大于 maxCommitTs(T10, T11, T12), 而且我们还可知, minCommitTs(T10,T11,T12) 就是全局最小的 commitTs, 而小于这些的 commitTs的事务,因为不在 活跃事务列表里了, 表示已经提交了, 那么我们可以知道, oplog ts 在 全局最小的 commitTs 之前的, 就是都提交了的, oplog 按照 commitTs 排序后,如下所示… Tx | minCommitTs(T10,T11,T12) | …我们可以知道 T9, 或者说小于 minCommitTs(T10,T11,T12) 都是无空洞,因为系统不会再提交小于 minCommitTs(T10,T11,T12) 的事务到oplog里了, 所以从节点可以直接恢复这里的数据。上面说的oplog minCommitTs(T10,T11,T12) 在 mongodb里,就是特殊的timestamp, 这个后文会讲。通过上面的方案,我们可以解决空洞的问题。这个时候,从节点每次恢复数据的时候,将读取的snapshot,设置为上一次恢复的Ts(同样也是无空洞的Ts), 这样的话, 从节点的恢复数据和读取数据也就做到了互不冲突。从而解决了 3.x系列的 从节点同步数据造成节点性能下降的问题。
  • [技术干货] 【DBA系列】026 MongoDB经典故障系列六:CPU利⽤率太⾼怎么办?
  • [技术干货] MongoDB数据库基础
  • [技术干货] MongoDB经典故障系列【合集】
    MongoDB经典故障系列一:数据库频繁启动失败怎么办?MongoDB经典故障系列二:如何限制最大连接数?     MongoDB经典故障系列三:副本集延迟太高怎么办?MongoDB经典故障系列四:调整oplog大小,引起从库宕机怎么办?MongoDB经典故障系列五:sharding集群执行sh.stopBalancer()命令被卡住怎么办?MongoDB经典故障系列六:CPU利⽤率太⾼怎么办?
  • [技术干货] MongoDB经典故障系列五:sharding集群执行sh.stopBalancer()命令被卡住怎么办?
    华为云数据库精选特惠活动火热进行中,爆款产品低至4.5折,新人享3.9折迁移价,机“惠”难得,购买请戳下图。
  • [技术干货] MongoDB经典故障系列三:副本集延迟太高怎么办?
    MongoDB副本集延迟太高,数据读取时间过长怎么办?不要慌,菊长教您一个小妙招:在集合创建的时候,就建立好索引,然后按照索引去寻找您所需要的数据。如果觉得比较麻烦,华为云文档数据库服务DDS了解一下。DDS服务100%兼容MongoDB,云上托管实例,无需客户操心。云数据库特惠专场来袭,10元尝鲜高性能云数据库1个月,单品包年3折起,迁移用户额外赠送6个月使用时长;还有云服务器、存储、域名等爆款组合包年低至34折,速来抢购!点击抢购