关于java:生产事故MongoDB复合索引引发的灾难

6次阅读

共计 5000 个字符,预计需要花费 13 分钟才能阅读完成。

前情提要

  1. 11 月末 我司商品服务 MongoDB 主库 曾呈现过重大抖动、频繁锁库等状况。
  2. 因为诸多业务存在插入MongoDB、而后立刻查问等逻辑,因而我的项目并未开启读写拆散。
  3. 最终定位问题是因为:服务器本身磁盘 + 大量 慢查问 导致
  4. 基于上述情况,运维同学后续着重加强了对 MongoDB 慢查问 的监控和告警

侥幸的一点 :在出事变之前刚好实现了缓存过期工夫的降级且过期工夫为一个月, C 端查问 都落在缓存上,因而没有造成 P0 级 事变,仅仅阻塞了局部 B 端逻辑

<br/>

事变回放

我司的各种监控做的比拟到位,当天忽然收到了数据库服务器负载较高的告警告诉,于是我和共事们就连忙登录了Zabbix 监控,如下图所示,截图的时候是失常状态,过后事变期间遗记留图了,能够设想过后的数据曲线反正是该高的很低,该低的很高就是了。

Zabbix 分布式监控零碎官网:https://www.zabbix.com/

<br/>

开始剖析

咱们研发是没有操控服务器权限的,因而委托运维同学帮忙咱们抓取了局部查问记录,如下所示:

---------------------------------------------------------------------------------------------------------------------------+
Op          | Duration | Query                                                                                                                   ---------------------------------------------------------------------------------------------------------------------------+
query       | 5 s      | {"filter": {"orgCode": 350119, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}               
query       | 5 s      | {"filter": {"orgCode": 350119, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}               query       | 4 s      | {"filter": {"orgCode": 346814, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}               query       | 4 s      | {"filter": {"orgCode": 346814, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}              query       | 4 s      | {"filter": {"orgCode": 346814, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find": "sku_main"}
...

查问很慢的话所有研发应该第一工夫想到的就是 索引 的应用问题,所以立刻查看了一遍索引,如下所示:

### 过后的索引

db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true});
db.sku_main.ensureIndex({"orgCode": 1, "upcCode": 1},{background:true});
....

我屏蔽了烦扰项,反正能很显著的看进去,这个查问是齐全能够命中索引的,所以就须要直面第一个问题:

<font color=”red”> 上述查问记录中排首位的慢查问到底是不是出问题的本源?</font>

我的判断是:它应该不是数据库整体迟缓的本源,因为第一它的查问条件足够简略暴力,齐全命中索引,在索引之上有一点其余的查问条件而已,第二在查问记录中也存在雷同构造不同条件的查问,耗时十分短。

在运维同学持续排查查问日志时,发现了另一个比拟惊爆的查问,如下:

### 过后场景日志

query: {$query: { shopCategories.0: { $exists: false}, orgCode: 337451, fixedStatus: {$in: [ 1, 2] }, _id: {$lt: 2038092587} }, $orderby: {_id: -1} } planSummary: IXSCAN {_id: 1} ntoreturn:1000 ntoskip:0 keysExamined:37567133 docsExamined:37567133 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:293501 nreturned:659 reslen:2469894 locks:{Global: { acquireCount: { r: 587004} }, Database: {acquireCount: { r: 293502} }, Collection: {acquireCount: { r: 293502} } } 

# 耗时
179530ms

耗时 180 秒且基于查问的 执行打算 能够看出,它走的是 _id_ 索引,进行了全表扫描,扫描的数据总量为:37567133,不慢才怪。

<br/>

迅速解决

定位到问题后,没方法立刻批改,第一要务是:止损

联合过后的工夫也比拟晚了,因而咱们发了布告,禁止了上述查问的性能并短暂暂停了局部业务,,过了一会之后进行了 主从切换 ,再去看Zabbix 监控 就所有安好了。

<br/>

剖析本源

咱们回顾一下查问的语句和咱们预期的索引,如下所示:

### 原始 Query
db.getCollection("sku_main").find({"orgCode" : NumberLong(337451), 
        "fixedStatus" : { 
            "$in" : [
                1.0, 
                2.0
            ]
        }, 
        "shopCategories" : {"$exists" : false}, 
        "_id" : {"$lt" : NumberLong(2038092587)
        }
    }
).sort(
    {"_id" : -1.0}
).skip(1000).limit(1000);

### 冀望的索引
db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true});

乍一看,如同所有都很 Nice 啊,字段 orgCode 等值查问,字段 _id 依照创立索引的方向进行倒序排序,为啥会这么慢?

然而,要害的一点就在 $lt

知识点一:索引、方向及排序

在 MongoDB 中,排序操作能够通过从索引中依照索引的程序获取文档的形式,来保障后果的有序性。

如果 MongoDB 的查问打算器没法从索引中失去排序程序,那么它就须要在内存中对后果排序。

留神:不必索引的排序操作,会在内存超过 32MB 时终止,也就是说 MongoDB 只能反对 32MB 以内的非索引排序

知识点二:单列索引不在乎方向

无论是 MongoDB 还是 MySQL 都是用的树结构作为索引,如果 排序方向 索引方向 相同,只须要从另一头开始遍历即可,如下所示:

# 索引
db.records.createIndex({a:1}); 

# 查问
db.records.find().sort({a:-1});

# 索引为升序,然而我查问要按降序,我只须要从右端开始遍历即可满足需要,反之亦然
MIN 0 1 2 3 4 5 6 7 MAX

MongoDB 的复合索引构造

官网介绍:MongoDB supports compound indexes, where a single index structure holds references to multiple fields within a collection’s documents.

复合索引构造示意图如下所示:

该索引刚好和咱们探讨的是一样的,userid 程序score 倒序

咱们须要直面第二个问题:<font color=”red”> 复合索引在应用时需不需要在乎方向?</font>

假如两个查问条件:

# 查问 一
db.getCollection("records").find({"userid" : "ca2"}).sort({"score" : -1.0});


# 查问 二
db.getCollection("records").find({"userid" : "ca2"}).sort({"score" : 1.0});

上述的查问没有任何问题,因为受到 score 字段排序的影响,只是数据从左侧还是从右侧遍历的问题,那么上面的一个查问呢?

# 谬误示范
db.getCollection("records").find({ 
  "userid" : "ca2",
  "score" : {"$lt" : NumberLong(2038092587)
  }
}).sort({"score" : -1.0});

谬误起因如下:

  • <font color=”red”> 因为 score 字段依照倒序排序,因而为了应用该索引,所以须要从左侧开始遍历 </font>
  • <font color=”red”> 从倒序程序中找小于某个值的数据,势必会扫描很多无用数据,而后抛弃,以后场景下找大于某个值才是最佳计划 </font>
  • <font color=”red”> 所以 MongoDB 为了更多场景思考,在该种状况下,放弃了复合索引,选用其余的索引,如 score 的单列索引 </font>

针对性批改

仔细阅读了本源之后,再回顾线上的查问语句,如下:

### 原始 Query
db.getCollection("sku_main").find({"orgCode" : NumberLong(337451), 
        "fixedStatus" : { 
            "$in" : [
                1.0, 
                2.0
            ]
        }, 
        "shopCategories" : {"$exists" : false}, 
        "_id" : {"$lt" : NumberLong(2038092587)
        }
    }
).sort(
    {"_id" : -1.0}
).skip(1000).limit(1000);

### 冀望的索引
db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true});

犯的谬误截然不同,所以 MongoDB 放弃了复合索引的应用,该为单列索引,因而进行针对性批改,把 $lt 条件改为 $gt 察看优化后果:

# 原始查问
[TEMP INDEX] => lt: {"limit":1000,"queryObject":{"_id":{"$lt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}}

# 原始耗时
[TEMP LT] => 超时(超时工夫 10s)# 优化后查问
[TEMP INDEX] => gt: {"limit":1000,"queryObject":{"_id":{"$gt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}}

# 优化后耗时
[TEMP GT] => 耗时: 383ms , List Size: 999

总结

剖析了小 2000 字,其实改变就是两个字符而已,当然真正的改变须要思考业务的须要,然而问题既然曾经定位,批改什么的就不难了,回顾上述内容总结如下:

  • 学习数据库常识的时候能够用类比的形式,然而须要额定留神其不同的中央(MySQL、MongoDB 索引、索引的方向)
  • MongoDB 数据库单列索引能够不在乎方向,如对无索引字段排序须要控制数据量级(32M)
  • MongoDB 数据库复合索引在应用中肯定要留神其方向,要齐全了解其逻辑,防止索引生效

最初

如果你感觉这篇内容对你挺有帮忙的话:

  1. 当然要点赞反对一下啦~
  2. 搜寻并关注公众号「是 Kerwin 啊」,一起唠唠嗑~
  3. 再来看看最近几篇的「查漏补缺」系列吧,该系列会继续输入~

    • 「查缺补漏」坚固你的 Nginx 常识体系
    • 「查缺补漏」坚固你的 RocketMQ 常识体系
    • 「查缺补漏」坚固你的 Redis 常识体系(笑)
    • 悄咪咪进步团队幸福感 & Surprise!
正文完
 0