【背景】
最近MongoDB群里面有群友遇到2次重启MongoDB后一直处于实例恢复状态(应用OPLOG),多达几天甚至更长才完成重启,下图是群友重启后周末2天都没有完成重启,一直处于实例恢复状态,导致业务一直不可用状态。MongoDB这么弱吗?重启实例需要恢复这么久才能完成?那谁还敢用?通常MongoDB副本集三个实例作为标准,重启主库会发生重新选出新主节点(通常在12s内完成)重新对外服务,事与愿违通常不符合官方标准化或者内部发生异常导致的。经过了解副本集采用PSA架构且存在一个数据从节点不可达的情况(甚至有的从节点宕机几个月没有发现),来分析这些情况以及如何对应。主要包括如下内容(WT存储引擎下版本是3.2,3.4,3.6,4.0,4.2为主,4.4,5.0也存在)
1、PSA架构下从节点宕机后,重启主库为什么会这么久
2、PSA架构还有哪些问题
3、PSA架构下如何缓解内存压力以及推荐PSS方案
4、模拟PSA架构下重启主库实例后长时间等待的情况并通过不同方案来解决
【分析过程】
【1、PSA架构重启主库为什么会这么久】
备注:有个提前是从库宕机存在一定周期且在此期间产生大量的脏数据。
1、官方对PSA架构介绍
2、为什么会造成主库内存压力
1、用于从副本集或者集群中读取数据时,能够允许我们读取到被大多数节点收到并被确认的数据.对应关系型数据库里面提交读,注意这里只是允许而已(数据库提供的能力,3.2版本才开始支持),客户端是否需要根据实际readConcern级别,不管我们是否需要这种majorityReadConcern级别,数据库WT引擎已经维护这些信息在内存中(默认是开启)。
2、当PSA副本集中存在一个数据节点宕机时,主库内存中数据的Majority commit point是无法推进的,此时checkpoint是不能将这些数据持久化(内存中脏数据无法更新到数据文件中),同时OPLOG会保留所有变更操作,如果从库宕机时间长且主库很忙,OPLOG会增长很大(4.0版本开始会超过配置大小)
3、 PSA此时checkpoint不能对Majority commit point后的数据进行持久化,主库必须维护最近Majority commit point的快照提供给读,所以内存压力会增大和内存使用,最终这些内存数据溢出,此时MongoDB利用SWAP技术将内存中置换到磁盘上(将内存数据置换到磁盘上WiredTigerLAS.wt),所以性能会下降,如从库宕机时间长,此时主库性能也慢,同时磁盘空间也会暴涨.可能会考虑重启实例(通常情况下重启能解决大部分问题),那么实例可能重启需要等特几天甚至更长时间才能完成(背锅走起),因为数据没有持久化,重启的话就需要进行实例恢复,那么就会出现开头说重启好多天都没有完成的悲剧。重启过程这个问题会被无限循环。(4.4开始重构来缓解这个问题,使用WiredTigerHS.wt来替代).最坏的情况可能会导致实例OOM.
【2、PSA架构还存在哪些问题】
优点:
PSA相比PSS少一份副本数据,相对应就cost down.这个是最直接好处。例如三机房部署PSA架构的副本集或者分片.对应A的机器最低配即可,不需要消耗什么资源。通常三机房采用PSSSA或者PSSSS.发生故障时优先切换本地机房.
正常情况下PSA正常下运行与PSS架构下无差别.主要出现S节点不可达以及长延迟情况会存在异常:除了内存压力增大造成性能的影响以及跟超长等待时间重启外,还如下常见场景:
majority表示数据被副本集成员中大多数节点收到并被主确认.5.0之前版本默认w:1,表示被主节点确认后表示操作成功,此时此群出现故障可能会导致写入主节点被回滚,从而造成数据丢失.所以w:majority是保证集群数据故障时不丢失的必需配置.那么majority到底是多少个节点?
对于majority是怎么计算?为什么PSA架构下宕机一个数据节点就不满足majority?
majority节点数=最小值(副本集中所有数据节点具有的投票能力总数与副本集中1加上取整(1/2的具备投票节点总数包括仲裁节点)).默认情况下PSA中所有节点都具备投票能力,那么此时majority节点数=min(2,3(1+取整(0.5*3))) =2.从4.2.1版本开始.可以通过rs.status()中writeMajorityCount、majorityVoteCount来看.如果此时宕机一个数据节点或者不可达时,此时majority还是2,不会因为状态的改变而减少majority个数。此时需要满足w:majority的操作要不超时要不永不返回的状态.注意点:此时数据已经写入主节点,不管是超时还是永不返回,数据不会被回滚(不考虑事务的场景以及failover情况).同理3.2版本开始默认开启enableMajorityReadConcern,此时majority commit point也不会被推进.所以说PSA在一定程度上通过节约成本来降低系统高可用性.
当然你说我们没有显式开启majority,是不是就没有问题?当存在一个数据节点不可达时,有些潜在场景默认是majortiy配置且不能修改,例如5.0开始enableMajorityReadConcern这个不能被禁用.例如changestream要求数据被大多数节点应用.同时也影响分片集群部分功能。
如果从库已经宕机N时间,此时主库也宕机了,如果运维人员先启动老的从库,那么会"丢N时间"数据,这个数据存在在原主库,此时原主库启动后需要先回滚N时间数据才能重新加入到副本集中,通常回滚有限制,大概率会回滚失败。这个比较头疼事情。
【3、PSA如何缓解内存压力以及推荐PSS方案】
1、通过完善的监控及时发现节点异常(宕机、延迟),及时处理故障,否则无能为力
2、禁用MajorityReadConcern(PSA架构来避免内存压力,同时注意changeStream,4.2版本不管这个参数,对于出现问题的集群或者副本集,此时修改已经无能为力,下一次生效)
3、临时将异常从库的优先级别与投票都设置为0(5.0版本由于不能禁用MajorityReadConcern,注意这个只能修改下应对从库宕机或延迟时,来缓解主库内存压力以及解决一些配置majority场景,但失去高可用,因为从库不能被选为主.适用场景是数据库需要重启时存在大量脏数据刷盘或者应用配置w:majority时,修改宕机实例优先级别与投票为0后进行重启才可以,如果已经重启的实例,此时只能等待)
总结:PSA解决从库宕机后如何缓解主库内存压力,通过有效监控及时消除故障点,如果没有及时发现,在重启前通过方案3来避免长时间重启问题,针对方案2需要提前规划好,但对于majority场景以及分片模式下操作还是无能为力,如果从库宕机很久,此时已强制重启主库,此时只能进入躺平状态去等.其他解决方案需要具体问题具体分析,例如只要系统能写入数据即可,可以把从实例恢复起来或者搭建空实例.
【4、模拟PSA架构下重启主库实例后长时间等待的情况并通过不同方案来解决】
备注:搭建4.2 PSA副本集,手动S实例关闭并通过POC压测数据,构造20个字段1.1亿表.
1、查看数据情况
1.1亿 show dbs显示POCDB为0,这个显示不合理。
datasize:62G,磁盘上大小为12K(这个说明数据并没有写入到磁盘)
PRIMARY:[db]POCDB> show dbs
POCDB
POCDB 0.000GB
admin 0.000GB
config 0.001GB
local 36.804GB
[db]POCDB> db.POCCOLL.count();
115155968
[db]POCDB>db.POCCOLL.stats();
{
"ns" : "POCDB.POCCOLL",
"size" : 67368296749,
"count" : 115155968,
"avgObjSize" : 585,
"storageSize" : 12288
}
2、查看磁盘上文件大小
备注:LAS文件有28G.但POCCOLL磁盘上12k
[mongo@xiaoxu123 data]$ ls -lh WiredTigerLAS.wt
mongo mongo 28G 5月 12 16:35 WiredTigerLAS.wt
[mongo@vmt30129 data]$ ls -lh 0--6719021210235564209.wt
mongo mongo 12K 5月 12 17:44 0--6719021210235564209.wt
3、验证MajorityReadConcern能否读取数据
备注:选取最近的数据.使用readConcern:majority没有读取到数据.
查看MajorityReadConcern是否开启
db.serverStatus().storageEngine.supportsCommittedReads;
true--表示支持.
PRIMARY:[db]POCDB> db.POCCOLL.find({"_id" : { "w" : 3, "i" :
34238463 }}).
readConcern("majority").count();
0
PRIMARY:[db]POCDB>db.POCCOLL.find({"_id" : { "w" : 3, "i" :
34238463 }}).
count();
1
4、查看内存压力情况
备注:此时没有读写,但通过mongostat监控发现dirty很高且used基本上在95%.
5、查看rs.config里面信息
主要关注lastStableRecoveryTimestamp:1652326222(ISODate("2022-05-12T03:30:22Z")),当前时间5月13号9点49分,而recover时间点是12号11点30+0800
根据rs.config里面相关时间来判断:重启实例需要12号11点30分来恢复数据.同时根据checkpoint时间点来看:数据并没有持久化.但oplog持久化了。如果此时重启,需要追1天oplog,需要从库停了一周,那么需要更久实例恢复,通常来说,重启是一件很常见事情,此时坑就来了。我们来重启下。
【rs.config信息】
[db]POCDB> rs.status();
{
"set" : "shard22",
"date" : ISODate("2022-05-13T01:49:15.297Z"),
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"lastStableRecoveryTimestamp" : Timestamp(1652326222, 1),
"lastStableCheckpointTimestamp" : Timestamp(1652326222, 1)
}
6、在不知道存在这个坑情况下重启服务器.
6.1、重启实例进入等待模式
备注:启动实例后进入无限等待时间窗口,在默认情况下PSA中S宕机多久,重启主库就需要不一定等比例时间,取决于多少脏数据以及服务器性能,因为进行OPLOG回放是并行,也许就是一个简单维护重启操作造成业务可能停机以天为级别的等待窗口。之前群里面遇到重启需要2天+等待窗口,正常业务肯定无法接受.登陆到数据库直接关闭主实例会报错(因为此时数据只有1个主+1仲裁),此时就应该注意到异常,也可能忽略这个错误,只重启即可.如果知道重启会遇到问题,就不会冒然重启.
[db]admin> db.shutdownServer()
2022-05-13T09:56:21.930+0800 E QUERY [js] Error: shutdownServer failed: {
"operationTime" : Timestamp(1652406965, 1),
"ok" : 0,
"errmsg" : "No electable secondaries caught up as of 2022-05-13T09:56:21.907+0800. Please use the replSetStepDown command with the argument {force: true} to force node to step down."
如果使用这种方式进行shutdown后启动或者使用systemctl xx restart底层实际是kill进程.
6.2、完成重启实例
备注:重启花30分钟才完成重启。注意如果反复重启,每次都会从相同 recoveryTimestamp时间点开始,因为重启并不能推进 recoveryTimestamp时间点,因为开启enableMajorityReadConcern.
几个指标:
启动总共34分:09:59:59到10:33:48。
、 恢复时间点:WiredTiger recoveryTimestamp. Ts: Timestamp(1652326222, 1)
ISODate("2022-05-12T03:30:22Z"),当时时间为13号10点。
压测程序:5个小时构造115155968插入记录。
After 18001 seconds, 115155968 new documents inserted - collection has 115155968
6397 inserts per second on average
0 keyqueries per second on average
0 updates per second on average
0 rangequeries per second on average
恢复时间:并没有需要5小时,因为纯插入,恢复日志每一个batch接近5000.我们正常相对复杂点。所以真实业务恢复起来会慢的。Applied 115162250 operations in 23035 batches。
6.2、此时该如何做?
备注:此时系统重要指标:RPO、RTO以及最终SLA指标,
【对应情况如下】
1、需要多久能恢复到最新数据时间点来提供服务(不允许丢失数据).--需要天级别的恢复,这个对应级别就很低了。这个还不是容灾灾难恢复,此时只能等待,运维压力山大。
2、需要最快恢复系统来提供服务(系统可用性优先) --这个就简单。重新搞一个新实例就可以或者重启从库。对应丢失数据来换取系统可用性,例如日志类。
【后续如何解决这个问题】
备注:如无法使用PSS代替PSA架构,参考前面讲过2点.
1、disable majorityReadConcern--需要重启实例才生效(5.0之前PSA采用此方案,也是官方推荐的方案,另外注意changestream以及readConcern采用majority)
2、修改从节点的vote和priority为0,这个只适合临时针对实例宕机或者存在大延迟情况下降低主库的内存压力。否则此时PSA下主库宕机无法选出新主。失去天生高可用特性)
结论:经过前面讲解,我们知道PSA在宕机一个数据节点下才存在相关问题,如果PSA实例都是正常,我们无需担心这些问题。但实例宕机或者服务器故障不是我们能控制,需要考虑这种情况下,我们需要通过何种方式来解决这些问题.当系统出现问题时,第一种方案不太合适,只能适合下一次.相对第二种可以临时解决问题.
【方案1、禁用majorityReadConcern参数--来缓解内存压力但无法解决其他问题,需要重启实例下一次才生效】
结论:通过禁用majorityReadConcern后,第一次重启还是花费30分钟,第二次重启就瞬间完成启动.但无法解决第一次重启长时间等待问题,需要预先规划并修改参数,当遇到问题时直接重启即可.在PSA架构出现数据节点宕机避免对主节点内存压力.但存在majority的场景还是无法解决,甚至changestream、分片集群下部分功能失效.重要系统还建议PSS架构.
【方案2、临时将异常从库的优先级别与投票都设置为0来恢复】
1、查看config里面信息
几个重要时间:
当时北京时间:9号21点.(最新时间)
OPLOG最后提交时间:7号15点32分
readConcernMajorityOP时间:7号15点32分
OPLOG应用时间以及持久化时间:9号21点(最新时间)
上一个稳定恢复点时间:7号15点32分
上一个稳定checkpoint时间:7号15点32分
根据rs.config里面相关时间来判断:重启实例需要7号15点32分来恢复数据.同时根据checkpoint时间点来看:数据并没有持久化.但oplog持久化了。此时重启需要从7号开始。
【rs.config信息】
PRIMARY:[db]admin> new Date();
ISODate("2022-05-09T13:00:31.827Z")
[db]admin> new Date(1651908725*1000);
ISODate("2022-05-07T07:32:05Z")
:PRIMARY:[db]admin> rs.status();
{
"set" : "shard22",
"majorityVoteCount" : 2,
"writeMajorityCount" : 2,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1651908725, 3642),
"t" : NumberLong(16)
},
"lastCommittedWallTime" : ISODate("2022-05-07T07:32:05.621Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1651908725, 3642),
"t" : NumberLong(16)
},
"readConcernMajorityWallTime" : ISODate("2022-05-07T07:32:05.621Z"),
"appliedOpTime" : {
"ts" : Timestamp(1652101157, 1),
"t" : NumberLong(16)
},
"durableOpTime" : {
"ts" : Timestamp(1652101157, 1),
"t" : NumberLong(16)
},
"lastAppliedWallTime" : ISODate("2022-05-09T12:59:17.625Z"),
"lastDurableWallTime" : ISODate("2022-05-09T12:59:17.625Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1651908725, 3642),
"lastStableCheckpointTimestamp" : Timestamp(1651908725, 3642),
2、修改宕机的节点优先级别与投票都位0【方案1】
【修改从库的优先级与投票】
PRIMARY:[db]POCDB> cfg=rs.config()
{
"_id" : "shard22",
"protocolVersion" : NumberLong(1),
"writeConcernMajorityJournalDefault" : true,
"members" : [
{
"_id" : 0,
"host" : "10.160.100.149:21002",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 2,
"tags" : { },
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 1,
"host" : "10.160.100.150:21002",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" :{ },
"slaveDelay" : NumberLong(0),
"votes" : 1
},
{
"_id" : 2,
"host" : "10.160.100.151:21002",
"arbiterOnly" : true,
"buildIndexes" : true,
"hidden" : false,
"priority" : 0,
"tags" : {},
"slaveDelay" : NumberLong(0),
"votes" : 1
}
],
}
PRIMARY:[db]POCDB> cfg.members[1].priority=0
0
PRIMARY:[db]POCDB> cfg.members[1].votes=0
0
PRIMARY:[db]POCDB> rs.reconfig(cfg)
【查看MajorityReadConcern、Recovery等信息】
PRIMARY:[db]POCDB> rs.status();
{
"set" : "shard22",
"date" : ISODate("2022-05-09T13:32:08.214Z"),
"majorityVoteCount" : 2, --投票节点还是2
"writeMajorityCount" : 1,--writeMajority从2变1相应的MajorityReadConcern
也是变1
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1652103116, 1),
"t" : NumberLong(16)
},
"lastCommittedWallTime" : ISODate("2022-05-09T13:31:56.255Z")
--这个时间也变
"readConcernMajorityWallTime" : ISODate("2022-05-09T13:31:56.255Z"),
--这个时间也变了
"lastStableRecoveryTimestamp" : Timestamp(1651908725, 3642),
--恢复点还没有变
"lastStableCheckpointTimestamp" : Timestamp(1651908725, 3642),
--checkpoint点还没有变
【经过24分钟刷盘后,checkpoint终于成功推进】
PRIMARY:[db]admin> rs.status();
{
"set" : "shard22",
"date" : ISODate("2022-05-09T13:56:56.426Z"),
"majorityVoteCount" : 2,
"writeMajorityCount" : 1,
"optimes" : {
"lastCommittedWallTime" : ISODate("2022-05-09T13:56:49.036Z"),
"readConcernMajorityWallTime" : ISODate("2022-05-09T13:56:49.036Z")
"lastAppliedWallTime" : ISODate("2022-05-09T13:56:49.036Z"),
"lastDurableWallTime" : ISODate("2022-05-09T13:56:49.036Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1652104559, 1), --已经改变
"lastStableCheckpointTimestamp" : Timestamp(1652104559, 1),--已经改变
【查看checkpoint信息与集合信息】
备注:共20分钟刷97G.包括索引与集合2部分,其实刷时间也挺久的,比重启快10分钟,相对来说比重启影响小很多.
【重启实例】
结论:重启很快并没有把从从实例宕机后所有OPLOG都应用一遍,提示No oplog entries to apply.
总结:至此完成分析PSA架构存在问题以及对应方案,不管怎么应对,当单个数据节点宕机或者长延迟时,在一定程度上牺牲高可用性.在知道PSA架构优缺点后,需要在数据一致性与可用性做折中考虑,从5.0开始默认writeConcern从w:1变成majority.说明MongoDB在设计上更加关注数据一致性,这个改变实际从4.4版本就埋下种子,4.4版本开始oplog从默认拉取变成推送模式,在一定程度改善延迟问题.所以说在条件允许下,尽量采用PSS架构.但 5.0 PSA默认WriteConcern变成w:1.有个计算公式:
if [ (#arbiters > 0) AND (#non-arbiters <= majority(#voting-nodes)) ]
defaultWriteConcern = { w: 1 }
else
defaultWriteConcern = { w: "majority" }