自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

「生產(chǎn)事故」MongoDB復合索引引發(fā)的災難

運維 數(shù)據(jù)庫運維 MongoDB
11月末我司商品服務的MongoDB主庫曾出現(xiàn)過嚴重抖動、頻繁鎖庫等情況。由于諸多業(yè)務存在插入MongoDB、然后立即查詢等邏輯,因此項目并未開啟讀寫分離。

[[356516]]

前情提要

11月末我司商品服務的MongoDB主庫曾出現(xiàn)過嚴重抖動、頻繁鎖庫等情況。

由于諸多業(yè)務存在插入MongoDB、然后立即查詢等邏輯,因此項目并未開啟讀寫分離。

最終定位問題是由于:服務器自身磁盤 + 大量慢查詢導致

基于上述情況,運維同學后續(xù)著重增強了對MongoDB慢查詢的監(jiān)控和告警

幸運的一點:在出事故之前剛好完成了緩存過期時間的升級且過期時間為一個月,C端查詢都落在緩存上,因此沒有造成P0級事故,僅僅阻塞了部分B端邏輯

事故回放

我司的各種監(jiān)控做的比較到位,當天突然收到了數(shù)據(jù)庫服務器負載較高的告警通知,于是我和同事們就趕緊登錄了Zabbix監(jiān)控,如下圖所示,截圖的時候是正常狀態(tài),當時事故期間忘記留圖了,可以想象當時的數(shù)據(jù)曲線反正是該高的很低,該低的很高就是了。

Zabbix 分布式監(jiān)控系統(tǒng)官網(wǎng):https://www.zabbix.com/

開始分析

我們研發(fā)是沒有操控服務器權(quán)限的,因此委托運維同學幫助我們抓取了部分查詢記錄,如下所示:

  1. ---------------------------------------------------------------------------------------------------------------------------+ 
  2. Op          | Duration | Query                                                                                                                   ---------------------------------------------------------------------------------------------------------------------------+ 
  3. query       | 5 s      | {"filter": {"orgCode": 350119, "fixedStatus": {"$in": [1, 2]}}, "sort": {"_id": -1}, "find""sku_main"}                
  4. 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"
  5. ... 

查詢很慢的話所有研發(fā)應該第一時間想到的就是索引的使用問題,所以立即檢查了一遍索引,如下所示:

  1. ### 當時的索引 
  2.  
  3. db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true}); 
  4. db.sku_main.ensureIndex({"orgCode": 1, "upcCode": 1},{background:true}); 
  5. .... 

我屏蔽了干擾項,反正能很明顯的看出來,這個查詢是完全可以命中索引的,所以就需要直面第一個問題:

上述查詢記錄中排首位的慢查詢到底是不是出問題的根源?

我的判斷是:它應該不是數(shù)據(jù)庫整體緩慢的根源,因為第一它的查詢條件足夠簡單暴力,完全命中索引,在索引之上有一點其他的查詢條件而已,第二在查詢記錄中也存在相同結(jié)構(gòu)不同條件的查詢,耗時非常短。

在運維同學繼續(xù)排查查詢?nèi)罩緯r,發(fā)現(xiàn)了另一個比較驚爆的查詢,如下:

  1. ### 當時場景日志 
  2.  
  3. 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 } } }  
  4.  
  5. # 耗時 
  6. 179530ms 

# 耗時耗時180秒且基于查詢的執(zhí)行計劃可以看出,它走的是_id_索引,進行了全表掃描,掃描的數(shù)據(jù)總量為:37567133,不慢才怪。

迅速解決

定位到問題后,沒辦法立即修改,第一要務是:止損

結(jié)合當時的時間也比較晚了,因此我們發(fā)了公告,禁止了上述查詢的功能并短暫暫停了部分業(yè)務,,過了一會之后進行了主從切換,再去看Zabbix監(jiān)控就一切安好了。

分析根源

我們回顧一下查詢的語句和我們預期的索引,如下所示:

  1. ### 原始Query 
  2. db.getCollection("sku_main").find({  
  3.         "orgCode" : NumberLong(337451),  
  4.         "fixedStatus" : {  
  5.             "$in" : [ 
  6.                 1.0,  
  7.                 2.0 
  8.             ] 
  9.         },  
  10.         "shopCategories" : {  
  11.             "$exists" : false 
  12.         },  
  13.         "_id" : {  
  14.             "$lt" : NumberLong(2038092587) 
  15.         } 
  16.     } 
  17. ).sort( 
  18.     {  
  19.         "_id" : -1.0 
  20.     } 
  21. ).skip(1000).limit(1000); 
  22.  
  23. ### 期望的索引 
  24. db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true}); 

乍一看,好像一切都很Nice啊,字段orgCode等值查詢,字段_id按照創(chuàng)建索引的方向進行倒序排序,為啥會這么慢?

但是,關(guān)鍵的一點就在 $lt 上

知識點一:索引、方向及排序

在MongoDB中,排序操作可以通過從索引中按照索引的順序獲取文檔的方式,來保證結(jié)果的有序性。

如果MongoDB的查詢計劃器沒法從索引中得到排序順序,那么它就需要在內(nèi)存中對結(jié)果排序。

注意:不用索引的排序操作,會在內(nèi)存超過32MB時終止,也就是說MongoDB只能支持32MB以內(nèi)的非索引排序

知識點二:單列索引不在乎方向

無論是MongoDB還是MySQL都是用的樹結(jié)構(gòu)作為索引,如果排序方向和索引方向相反,只需要從另一頭開始遍歷即可,如下所示:

  1. # 索引 
  2. db.records.createIndex({a:1});  
  3.  
  4. # 查詢 
  5. db.records.find().sort({a:-1}); 
  6.  
  7. # 索引為升序,但是我查詢要按降序,我只需要從右端開始遍歷即可滿足需求,反之亦然 
  8. MIN 0 1 2 3 4 5 6 7 MAX 

MongoDB的復合索引結(jié)構(gòu)

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

復合索引結(jié)構(gòu)示意圖如下所示:

該索引剛好和我們討論的是一樣的,userid順序,score倒序。

我們需要直面第二個問題:復合索引在使用時需不需要在乎方向?

假設兩個查詢條件:

  1. # 查詢 一 
  2. db.getCollection("records").find({  
  3.   "userid" : "ca2" 
  4. }).sort({"score" : -1.0}); 
  5.  
  6.  
  7. # 查詢 二 
  8. db.getCollection("records").find({  
  9.   "userid" : "ca2" 
  10. }).sort({"score" : 1.0}); 

上述的查詢沒有任何問題,因為受到score字段排序的影響,只是數(shù)據(jù)從左側(cè)還是從右側(cè)遍歷的問題,那么下面的一個查詢呢?

  1. # 錯誤示范 
  2. db.getCollection("records").find({  
  3.   "userid" : "ca2"
  4.   "score" : {  
  5.     "$lt" : NumberLong(2038092587) 
  6.   } 
  7. }).sort({"score" : -1.0}); 

錯誤原因如下:

  • 由于score字段按照倒序排序,因此為了使用該索引,所以需要從左側(cè)開始遍歷
  • 從倒序順序中找小于某個值的數(shù)據(jù),勢必會掃描很多無用數(shù)據(jù),然后丟棄,當前場景下找大于某個值才是最佳方案
  • 所以MongoDB為了更多場景考慮,在該種情況下,放棄了復合索引,選用其他的索引,如 score 的單列索引

針對性修改

仔細閱讀了根源之后,再回顧線上的查詢語句,如下:

  1. ### 原始Query 
  2. db.getCollection("sku_main").find({  
  3.         "orgCode" : NumberLong(337451),  
  4.         "fixedStatus" : {  
  5.             "$in" : [ 
  6.                 1.0,  
  7.                 2.0 
  8.             ] 
  9.         },  
  10.         "shopCategories" : {  
  11.             "$exists" : false 
  12.         },  
  13.         "_id" : {  
  14.             "$lt" : NumberLong(2038092587) 
  15.         } 
  16.     } 
  17. ).sort( 
  18.     {  
  19.         "_id" : -1.0 
  20.     } 
  21. ).skip(1000).limit(1000); 
  22.  
  23. ### 期望的索引 
  24. db.sku_main.ensureIndex({"orgCode": 1, "_id": -1},{background:true}); 

犯的錯誤一模一樣,所以MongoDB放棄了復合索引的使用,該為單列索引,因此進行針對性修改,把 $lt 條件改為 $gt 觀察優(yōu)化結(jié)果:

  1. # 原始查詢 
  2. [TEMP INDEX] => lt: {"limit":1000,"queryObject":{"_id":{"$lt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}} 
  3.  
  4. # 原始耗時 
  5. [TEMP LT] => 超時 (超時時間10s) 
  6.  
  7. # 優(yōu)化后查詢 
  8. [TEMP INDEX] => gt: {"limit":1000,"queryObject":{"_id":{"$gt":2039180008},"categoryId":23372,"orgCode":351414,"fixedStatus":{"$in":[1,2]}},"restrictedTypes":[],"skip":0,"sortObject":{"_id":-1}} 
  9.  
  10. # 優(yōu)化后耗時 
  11. [TEMP GT] => 耗時: 383ms , List Size: 999 

總結(jié)

分析了小2000字,其實改動就是兩個字符而已,當然真正的改動需要考慮業(yè)務的需要,但是問題既然已經(jīng)定位,修改什么的就不難了,回顧上述內(nèi)容總結(jié)如下:

  • 學習數(shù)據(jù)庫知識的時候可以用類比的方式,但是需要額外注意其不同的地方(MySQL、MongoDB索引、索引的方向)
  • MongoDB數(shù)據(jù)庫單列索引可以不在乎方向,如對無索引字段排序需要控制數(shù)據(jù)量級(32M)
  • MongoDB數(shù)據(jù)庫復合索引在使用中一定要注意其方向,要完全理解其邏輯,避免索引失效

本文轉(zhuǎn)載自微信公眾號「是Kerwin啊」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系是Kerwin啊公眾號。

 

責任編輯:武曉燕 來源: 是Kerwin啊
相關(guān)推薦

2020-05-07 11:00:24

Go亂碼框架

2022-10-25 18:00:00

Redis事務生產(chǎn)事故

2021-10-08 08:55:23

FacebookBGP工具

2013-08-08 10:20:04

云計算災難恢復反思

2023-02-16 08:55:13

2020-12-31 10:30:12

AI數(shù)據(jù)分析人工智能

2024-08-20 21:27:04

docker部署容器

2019-01-16 09:20:42

架構(gòu)設計JVM FullGC宕機事故

2023-01-06 10:52:30

SQL索引存儲

2019-07-31 10:08:19

人工多線程數(shù)據(jù)

2013-03-22 09:34:13

2009-08-07 15:45:26

ASP.NET復合控件數(shù)據(jù)綁定

2025-03-10 08:20:53

代碼線程池OOM

2020-04-02 07:31:53

RPC超時服務端

2022-04-12 08:43:04

生產(chǎn)故障Dubbo調(diào)用

2010-11-02 13:01:26

2024-08-28 14:55:41

2017-01-03 14:53:30

服務器宕機

2021-01-20 10:16:26

高并發(fā)數(shù)據(jù)服務

2021-12-29 07:01:53

Mysql復合索引
點贊
收藏

51CTO技術(shù)棧公眾號