文章目錄
- 系列文章索引
- 十一、MongoDB開發規范
- 十二、MongoDB調優
- 1、三大導致MongoDB性能不佳的原因
- 2、影響MongoDB性能的因素
- 3、MongoDB性能監控工具
- (1)mongostat
- (2)mongotop
- (3)Profiler模塊
- (4)查看操作日志
- (5)db.currentOp()
- (6)性能問題排查案例
- 十三、建模案例分析
- 1、建模案例分析1
- (1)需求
- (2)建模 - 不好的設計
- (3)建模 - 推薦的設計
- 2、建模案例分析2:多列數據結構
- (1)需求
- (2)建模 - 不好的模型設計
- (3)建模 - 一般的設計
- (4)建模 - 合理的設計
- 3、建模案例分析3 - 物聯網時序數據庫建模
- (1)需求
- (2)建模
- (3)總結
- 十四、MongoDB高級集群架構
- 1、兩地三中心集群架構
- 2、全球多寫集群架構
系列文章索引
MongoDB基礎入門到深入(一)安裝、文檔操作
MongoDB基礎入門到深入(二)聚合高級操作
MongoDB基礎入門到深入(三)索引高級操作
MongoDB基礎入門到深入(四)復制(副本)集
MongoDB基礎入門到深入(五)分片集群
MongoDB基礎入門到深入(六)多文檔事務
MongoDB基礎入門到深入(七)建模、調優
MongoDB基礎入門到深入(八)MongoDB整合SpringBoot、Chang Streams
十一、MongoDB開發規范
(1)命名原則。數據庫、集合命名需要簡單易懂,數據庫名使用小寫字符,集合名稱使用統一命名風格,可以統一大小寫或使用駝峰式命名。數據庫名和集合名稱均不能超過64個字符。
(2)集合設計。對少量數據的包含關系,使用嵌套模式有利于讀性能和保證原子性的寫入。對于復雜的關聯關系,以及后期可能發生演進變化的情況,建議使用引用模式。
(3)文檔設計。避免使用大文檔,MongoDB的文檔最大不能超過16MB
。如果使用了內嵌的數組對象或子文檔,應該保證內嵌數據不會無限制地增長。在文檔結構上,盡可能減少字段名的長度,MongoDB會保存文檔中的字段名,因此字段名稱會影響整個集合的大小以及內存的需求。一般建議將字段名稱控制在32個字符以內。
(4)索引設計。在必要時使用索引加速查詢
。避免建立過多的索引,單個集合建議不超過10個索引。MongoDB對集合的寫入操作很可能也會觸發索引的寫入,從而觸發更多的I/O操作。無效的索引會導致內存空間的浪費,因此有必要對索引進行審視,及時清理不使用或不合理的索引。遵循索引優化原則,如覆蓋索引、優先前綴匹配等,使用explain命令分析索引性能。
(5)分片設計。對可能出現快速增長或讀寫壓力較大的業務表考慮分片
。分片鍵的設計滿足均衡分布的目標,業務上盡量避免廣播查詢。應盡早確定分片策略,最好在集合達到256GB之前就進行分片。如果集合中存在唯一性索引,則應該確保該索引覆蓋分片鍵,避免沖突。為了降低風險,單個分片的數據集合大小建議不超過2TB
。
(6)升級設計。應用上需支持對舊版本數據的兼容性,在添加唯一性約束索引之前,對數據表進行檢查并及時清理冗余的數據。新增、修改數據庫對象等操作需要經過評審,并保持對數據字典進行更新。
(7)考慮數據老化問題
,要及時清理無效、過期的數據,優先考慮為系統日志、歷史數據表添加合理的老化策略。
(8)數據一致性方面,非關鍵業務使用默認的WriteConcern:1(更高性能寫入);對于關鍵業務類,使用WriteConcern:majority保證一致性(性能下降)
。如果業務上嚴格不允許臟讀,則使用ReadConcern:majority選項。
(9)使用update、findAndModify對數據進行修改時,如果設置了upsert:true,則必須使用唯一性索引避免產生重復數據。
(10)業務上盡量避免短連接,使用官方最新驅動的連接池實現,控制客戶端連接池的大小,最大值建議不超過200。
(11)對大量數據寫入使用Bulk Write批量化API,建議使用無序批次更新。
(12)優先使用單文檔事務保證原子性
,如果需要使用多文檔事務,則必須保證事務盡可能小,一個事務的執行時間最長不能超過60s。
(13)在條件允許的情況下,利用讀寫分離降低主節點壓力
。對于一些統計分析類的查詢操作,可優先從節點上執行。
(14)考慮業務數據的隔離,例如將配置數據、歷史數據存放到不同的數據庫中。微服務之間使用單獨的數據庫,盡量避免跨庫訪問。
(15)維護數據字典文檔并保持更新,提前按不同的業務進行數據容量的規劃。
十二、MongoDB調優
1、三大導致MongoDB性能不佳的原因
1. 慢查詢
2. 阻塞等待
3. 硬件資源不足
1,2通常是因為模型/索引設計不佳導致的
排查思路:按1-2-3依次排查
2、影響MongoDB性能的因素
https://www.mongodb.com/docs/v4.4/tutorial/model-embedded-one-to-one-relationships-between-documents/
https://www.mongodb.com/docs/v4.4/reference/connection-string/
https://www.mongodb.com/docs/manual/administration/production-notes/#hardware-considerations
3、MongoDB性能監控工具
(1)mongostat
mongostat是MongoDB自帶的監控工具,其可以提供數據庫節點或者整個集群當前的狀態視圖。該功能的設計非常類似于Linux系統中的vmstat命令,可以呈現出實時的狀態變化。不同的是,mongostat所監視的對象是數據庫進程。mongostat常用于查看當前的QPS/內存使用/連接數,以及多個分片的壓力分布
。mongostat采用Go語言實現,其內部使用了db.serverStatus()命令,要求執行用戶需具備clusterMonitor角色權限。
mongostat -h 192.168.56.10 --port 28017 -uroot -proot --authenticationDatabase=admin --discover -n 300 2
參數說明:
-h:指定監聽的主機,分片集群模式下指定到一個mongos實例,也可以指定單個mongod,或者復制集的多個節點。
–port:接入的端口,如果不提供則默認為27017。
-u:接入用戶名,等同于-user。
-p:接入密碼,等同于-password。
–authenticationDatabase:鑒權數據庫。
–discover:啟用自動發現,可展示集群中所有分片節點的狀態。
-n 300 2:表示輸出300次,每次間隔2s。也可以不指定“-n 300”,此時會一直保持輸出。
指標說明
mongostat需要關注的指標主要有如下幾個:
插入、刪除、修改、查詢的速率是否產生較大波動,是否超出預期。
qrw、arw:隊列是否較高,若長時間大于0則說明此時讀寫速度較慢。
conn:連接數是否太多。
dirty:百分比是否較高,若持續高于10%則說明磁盤I/O存在瓶頸。
netIn、netOut:是否超過網絡帶寬閾值。
repl:狀態是否異常,如PRI、SEC、RTR為正常,若出現REC等異常值則需要修復。
使用交互模式
mongostat一般采用滾動式輸出,即每一個間隔后的狀態數據會被追加到控制臺中。從MongoDB 3.4開始增加了--interactive
選項,用來實現非滾動式的監視,非常方便。
mongostat -h 192.168.56.10 --port 28017 -uroot -proot --authenticationDatabase=admin --discover --interactive -n 2
(2)mongotop
mongotop命令可用于查看數據庫的熱點表
,通過觀察mongotop的輸出,可以判定是哪些集合占用了大部分讀寫時間
。mongotop與mongostat的實現原理類似,同樣需要clusterMonitor角色權限。
mongotop -h 192.168.56.10 --port=28017 -uroot -proot --authenticationDatabase=admin
默認情況下,mongotop會持續地每秒輸出當前的熱點表
指標說明
mongotop通常需要關注的因素主要包括:
熱點表操作耗費時長是否過高
。這里的時長是在一定的時間間隔內的統計值,它代表某個集合讀寫操作所耗費的時間總量。在業務高峰期時,核心表的讀寫操作一般比平時高一些,通過mongotop的輸出可以對業務尖峰做出一些判斷。
是否存在非預期的熱點表
。一些慢操作導致的性能問題可以從mongotop的結果中體現出來
#mongotop的統計周期、輸出總量都是可以設定的
#最多輸出100次,每次間隔時間為2s
mongotop -h 192.168.56.10 --port=28017 -uroot -proot --authenticationDatabase=admin -n 100 2
(3)Profiler模塊
Profiler模塊可以用來記錄、分析MongoDB的詳細操作日志。默認情況下該功能是關閉的,對某個業務庫開啟Profiler模塊之后,符合條件的慢操作日志會被寫入該庫的system.profile集合中
。Profiler的設計很像代碼的日志功能,其提供了幾種調試級別:
對當前的數據庫開啟Profiler模塊:
# 將level設置為2,此時所有的操作會被記錄下來。
db.setProfilingLevel(2)
#檢查是否生效
db.getProfilingStatus()
slowms是慢操作的閾值,單位是毫秒;
sampleRate表示日志隨機采樣的比例,1.0則表示滿足條件的全部輸出。
# 如果希望只記錄時長超過500ms的操作,則可以將level設置為1
db.setProfilingLevel(1,500)
# 還可以進一步設置隨機采樣的比例
db.setProfilingLevel(1,{slowms:500,sampleRate:0.5})
(4)查看操作日志
# 開啟Profiler模塊之后,可以通過system.profile集合查看最近發生的操作日志
db.system.profile.find().limit(5).sort({ts:-1}).pretty()
這里需要關注的一些字段主要如下所示:
op:操作類型,描述增加、刪除、修改、查詢。
ns:名稱空間,格式為{db}.{collection}。
Command:原始的命令文檔。
Cursorid:游標ID。
numYield:操作數,大于0表示等待鎖或者是磁盤I/O操作。
nreturned:返回條目數。
keysExamined:掃描索引條目數,如果比nreturned大出很多,則說明查詢效率不高。docsExamined:掃描文檔條目數,如果比nreturned大出很多,則說明查詢效率不高。
locks:鎖占用的情況。
storage:存儲引擎層的執行信息。
responseLength:響應數據大小(字節數),一次性查詢太多的數據會影響性能,可以使用limit、batchSize進行一些限制。
millis:命令執行的時長,單位是毫秒。
planSummary:查詢計劃的概要,如IXSCAN表示使用了索引掃描。
execStats:執行過程統計信息。
ts:命令執行的時間點。
根據這些字段,可以執行一些不同維度的查詢。比如查看執行時長最大的10條操作記錄
#查看某個集合中的update操作日志
db.system.profile.find().limit(10).sort({millis:-1}).pretty()
#查看某個集合中的update操作日志
db.system.profile.find({op:"update",ns:"shop.user"})
注意事項
system.profile是一個1MB的固定大小的集合
,隨著記錄日志的增多,一些舊的記錄會被滾動刪除。
在線上開啟Profiler模塊需要非常謹慎,這是因為其對MongoDB的性能影響比較大。建議按需部分開啟,同時slowms的值不要設置太低
。
sampleRate的默認值是1.0,該字段可以控制記錄日志的命令數比例,但只有在MongoDB 4.0版本之后才支持。
Profiler模塊的設置是內存級的,重啟服務器后會自動恢復默認狀態
。
(5)db.currentOp()
Profiler模塊所記錄的日志都是已經發生的事情,db.currentOp()命令則與此相反,它可以用來查看數據庫當前正在執行的一些操作
。想象一下,當數據庫系統的CPU發生驟增時,我們最想做的無非是快速找到問題的根源,這時db.currentOp就派上用場了。
db.currentOp()讀取的是當前數據庫的命令快照,該命令可以返回許多有用的信息
,比如:
操作的運行時長,快速發現耗時漫長的低效掃描操作。
執行計劃信息,用于判斷是否命中了索引,或者存在鎖沖突的情況。
操作ID、時間、客戶端等信息,方便定位出產生慢操作的源頭。
對示例操作的解讀如下:
(1)從ns、op字段獲知,當前進行的操作正在對test.items集合執行update命令。
(2)command字段顯示了其原始信息。其中,command.q和command.u分別展示了update的查詢條件和更新操作。
(3)“planSummary”:“COLLSCAN” 說明情況并不樂觀,update沒有利用索引而是正在全表掃描。
(4)microsecs_running:NumberLong(186070)表示操作運行了186ms,注意這里的單位是微秒。
優化方向:
value字段加上索引
如果更新的數據集非常大,要避免大范圍update操作,切分成小批量的操作
#opid表示當前操作在數據庫進程中的唯一編號。如果已經發現該操作正在導致數據庫系統響應緩慢,則可以考慮將其“殺”死
db.killOp(4001)
db.currentOp默認輸出當前系統中全部活躍的操作,由于返回的結果較多,我們可以指定一些過濾條件:
# 查看等待鎖的增加、刪除、修改、查詢操作
db.currentOp({waitingForLock:true,$or:[{op:{$in:["insert","update","remove"]}},{"query.findandmodify":{$exists:true}}]
})#查看執行時間超過1s的操作
db.currentOp({secs_running:{$gt:1}
})
#查看test數據庫中的操作
db.currentOp({ns:/test/
})
currentOp命令輸出說明
currentOp.type:操作類型,可以是op、idleSession、idleCursor的一種,一般的操作信息以op表示。其為MongoDB 4.2版本新增功能。
currentOp.host:主機的名稱。currentOp.desc:連接描述,包含connectionId。currentOp.connectionId:客戶端連接的標識符。currentOp.client:客戶端主機和端口。currentOp.appName:應用名稱,一般是描述客戶端類型。
currentOp.clientMetadata:關于客戶端的附加信息,可以包含驅動的版本。currentOp.currentOpTime:操作的開始時間。MongoDB 3.6版本新增功能。
currentOp.lsid:會話標識符。MongoDB 3.6版本新增功能。
currentOp.opid:操作的標志編號。
currentOp.active:操作是否活躍。如果是空閑狀態則為false。
currentOp.secs_running:操作持續時間(以秒為單位)。
currentOp.microsecs_running:操作持續時間(以微秒為單位)。
currentOp.op:標識操作類型的字符串。可能的值是:“none” “update” “insert”“query”“command” “getmore” “remove” “killcursors”。其中,command操作包括大多數命令,如createIndexes和findAndModify。
currentOp.ns:操作目標的集合命名空間。
currentOp.command:操作的完整命令對象的文檔。如果文檔大小超過1KB,則會使用一種$truncate形式表示。
currentOp.planSummary:查詢計劃的概要信息。
currentOp.locks:當前操作持有鎖的類型和模式。
currentOp.waitingForLock:是否正在等待鎖。
currentOp.numYields:當前操作執行yield(讓步)的次數。一些鎖互斥或者磁盤I/O讀取都會導致該值大于0。
currentOp.lockStats:當前操作持有鎖的統計。
currentOp.lockStats.acquireCount:操作以指定模式獲取鎖的次數。
currentOp.lockStats.acquireWaitCount:操作獲取鎖等待的次數,等待是因為鎖處于沖突模式。acquireWaitCount小于或等于acquireCount。
currentOp.lockStats.timeAcquiringMicros:操作為了獲取鎖所花費的累積時間(以微秒為單位)。timeAcquiringMicros除以acquireWaitCount可估算出平均鎖等待時間。
currentOp.lockStats.deadlockCount:在等待鎖獲取時,操作遇到死鎖的次數。
注意事項
db.currentOp返回的是數據庫命令的瞬時狀態
,因此,如果數據庫壓力不大,則通常只會返回極少的結果。
如果啟用了復制集,那么currentOp還會返回一些復制的內部操作(針對local.oplog.rs),需要做一些篩選。
db.currentOp的結果是一個BSON文檔,如果大小超過16MB,則會被壓縮。可以使用聚合操作$currentOp
獲得完整的結果。
(6)性能問題排查案例
記一次 MongoDB 占用 CPU 過高問題的排查
MongoDB線上案例:一個參數提升16倍寫入速度
十三、建模案例分析
1、建模案例分析1
(1)需求
朋友圈評論內容管理
社交類的APP需求,一般都會引入“朋友圈”功能,這個產品特性有一個非常重要的功能就是評論體系
。
先整理下需求:
這個APP希望點贊和評論信息都要包含頭像信息:
點贊列表,點贊用戶的昵稱,頭像;
評論列表,評論用戶的昵稱,頭像;
數據查詢則相對簡單:
根據分享ID,批量的查詢出10條分享里的所有評論內容;
(2)建模 - 不好的設計
跟據上面的內容,先來一個非常非常"樸素"的設計:
{"_id": 41,"username": "小白","uid": "100000","headurl": "http://xxx.yyy.cnd.com/123456ABCDE","praise_list": ["100010","100011","100012"],"praise_ref_obj": {"100010": {"username": "小一","headurl": "http://xxx.yyy.cnd.com/8087041AAA","uid": "100010"},"100011": {"username": "mayun","headurl": "http://xxx.yyy.cnd.com/8087041AAB","uid": "100011"},"100012": {"username": "penglei","headurl": "http://xxx.yyy.cnd.com/809999041AAA","uid": "100012"}},"comment_list": ["100013","100014"],"comment_ref_obj": {"100013": {"username": "小二","headurl": "http://xxx.yyy.cnd.com/80232041AAA","uid": "100013","msg": "good"},"100014": {"username": "小三","headurl": "http://xxx.yyy.cnd.com/11117041AAB","uid": "100014","msg": "bad"}}
}
可以看到,comment_ref_obj與praise_ref_obj兩個字段,有非常重的關系型數據庫痕跡
,猜測,這個系統之前應該是放在了普通的關系型數據庫上,或者設計者被關系型數據庫的影響較深。而在MongoDB這種文檔型數據庫里,實際上是沒有必要這樣去設計,這種建模只造成了多于的數據冗余
。
另外一個問題是,url占用了非常多的信息空間,這點在壓測的時候會有體現,帶寬會過早的成為瓶頸。同樣username信息也是如此,此類信息相對來說是全局穩定的,基本不會做變化。并且這類信息跟隨評論一起在整個APP中流轉,也無法處理”用戶名修改“的需求。
(3)建模 - 推薦的設計
{"_id": 41,"uid": "100000","praise_uid_list": ["100010","100011","100012"],"comment_msg_list": [{"100013": "good"},{"100014": "bad"}]
}
對比可以看到,整個結構要小了幾個數量級,并且可以發現url,usrname信息都全部不見了。那這樣的需求應該如何去實現呢?
從業務抽象上來說,url,username這類信息實際上是非常穩定的,不會發生特別大的頻繁變化。并且這兩類信息實際上都應該是跟uid綁定的,每個uid含有指定的url,username,是最簡單的key,value模型。所以,這類信息非常適合做一層緩存加速讀取查詢。
進一步的,每個人的好友基本上是有限的,頭像,用戶名等信息,甚至可以在APP層面進行緩存,也不會消耗移動端過多容量。但是反過來看,每次都到后段去讀取,不但浪費了移動端的流量帶寬,也加劇了電量消耗。
總結
MongoDB的文檔模型固然強大,但絕對不是等同于關系型數據庫的粗暴聚合,而是要考慮需求和業務,合理的設計
。有些人在設計時,也會被文檔模型誤導,三七二十一一股腦的把信息塞到一個文檔中,反而最后會帶來各種使用問題。
2、建模案例分析2:多列數據結構
(1)需求
基于電影票售賣的不同渠道價格存儲。某一個場次的電影,不同的銷售渠道對應不同的價格。整理需求為:
數據字段:
場次信息;
播放影片信息;
渠道信息,與其對應的價格;
渠道數量最多幾十個;
業務查詢有兩種:
根據電影場次,查詢某一個渠道的價格;
根據渠道信息,查詢對應的所有場次信息;
(2)建模 - 不好的模型設計
我們先來看其中一種典型的不好建模設計:
{"scheduleId": "0001","movie": "你的名字","price": {"gewala": 30,"maoyan": 50,"taopiao": 20}
}
數據表達上基本沒有字段冗余,非常緊湊。再來看業務查詢能力:
1、根據電影場次,查詢某一個渠道的價格;
建立createIndex({scheduleId:1, movie:1})
索引,雖然對price來說沒有創建索引優化,但通過前面兩個維度,已經可以定位到唯一的文檔,查詢效率上來說尚可;
2、根據渠道信息,查詢對應的所有場次信息;
為了優化這種查詢,需要對每個渠道分別建立索引,例如:
createIndex({“price.gewala”:1})
createIndex({“price.maoyan”:1})
createIndex({“price.taopiao”:1})
但渠道會經常變化,并且為了支持此類查詢,肯能需要創建幾十個索引,對維護來說簡直就是噩夢;
此設計行不通,否決。
(3)建模 - 一般的設計
{"scheduleId": "0001","movie": "你的名字","channel": "gewala","price": 30
}{"scheduleId": "0001","movie": "你的名字","channel": "maoyan","price": 50
}{"scheduleId": "0001","movie": "你的名字","channel": "taopiao","price": 20
}
與上面的方案相比,把整個存儲對象結構進行了平鋪展開,變成了一種表結構,傳統的關系數據庫多數采用這種類型的方案。信息表達上,把一個對象按照渠道維度拆成多個,其他的字段進行了冗余存儲。如果業務需求再復雜點,造成的信息冗余膨脹非常巨大。膨脹后帶來的副作用會有磁盤空間占用上升,內存命中率降低等缺點。對查詢的處理呢:
1、根據電影場次,查詢某一個渠道的價格;
建立createIndex({scheduleId:1, movie:1, channel:1})
索引;
2、根據渠道信息,查詢對應的所有場次信息;
建立createIndex({channel:1})
索引;
更進一步的優化呢?
(4)建模 - 合理的設計
{"scheduleId": "0001","movie": "你的名字","provider": [{"channel": "gewala","price": 30},{"channel": "maoyan","price": 50},{"channel": "taopiao","price": 20}]
}
這里使用了在MongoDB建模中非常容易忽略的結構——”數組“。查詢方面的處理,是可以建立Multikey Index索引
1、根據電影場次,查詢某一個渠道的價格;
建立createIndex({scheduleId:1, movie:1, "provider.channel":1})
索引;
2、根據渠道信息,查詢對應的所有場次信息;
建立createIndex({"provider.channel":1})
索引;
總結
這個案例并不復雜,需求也很清晰,但確實非常典型的MongoDB建模設計,開發人員在進行建模設計時經常也會受傳統數據庫的思路影響,沿用之前的思維慣性,而忽略了“文檔”的價值。
3、建模案例分析3 - 物聯網時序數據庫建模
(1)需求
本案例非常適合與IoT場景的數據采集,結合MongoDB的Sharding能力,文檔數據結構等優點,可以非常好的解決物聯網使用場景。
需求:
案例背景是來自真實的業務,美國州際公路的流量統計。數據庫需要提供的能力:
存儲事件數據
提供分析查詢能力
理想的平衡點:
內存使用
寫入性能
讀取分析性能
可以部署在常見的硬件平臺上
(2)建模
每個事件用一個獨立的文檔存儲
{segId: "I80_mile23",speed: 63,ts: ISODate("2013-10-16T22:07:38.000-0500")
}
非常“傳統”的設計思路,每個事件都會寫入一條同樣的信息。多少的信息,就有多少條數據,數據量增長非常快。
數據采集操作全部是Insert語句;
每分鐘的信息用一個獨立的文檔存儲(存儲平均值)
{segId: "I80_mile23",speed_num: 18,speed_sum: 1134,ts: ISODate("2013-10-16T22:07:00.000-0500")
}
對每分鐘的平均速度計算非常友好(speed_sum/speed_num);
數據采集操作基本是Update語句;
數據精度降為一分鐘;
每分鐘的信息用一個獨立的文檔存儲(秒級記錄)
{segId: "I80_mile23",speed: {0:63, 1:58, ... , 58:66, 59:64},ts: ISODate("2013-10-16T22:07:00.000-0500")
}
每秒的數據都存儲在一個文檔中;
數據采集操作基本是Update語句;
每小時的信息用一個獨立的文檔存儲(秒級記錄)
{segId: "I80_mile23",speed: {0:63, 1:58, ... , 3598:54, 3599:55},ts: ISODate("2013-10-16T22:00:00.000-0500")
}
相比上面的方案更進一步,從分鐘到小時:
每小時的數據都存儲在一個文檔中;
數據采集操作基本是Update語句;
更新最后一個時間點(第3599秒),需要3599次迭代(雖然是在同一個文檔中)
進一步優化下:
{segId: "I80_mile23",speed: {0: {0:47, ..., 59:45},...,59: {0:65, ... , 59:56}}ts: ISODate("2013-10-16T22:00:00.000-0500")
}
用了嵌套的手法把秒級別的數據存儲在小時數據里;
數據采集操作基本是Update語句;
更新最后一個時間點(第3599秒),需要59+59次迭代;
嵌套結構正是MongoDB的魅力所在,稍動腦筋把一維拆成二維,大幅度減少了迭代次數;
每個事件用一個獨立的文檔存儲VS每分鐘的信息用一個獨立的文檔存儲
從寫入上看:后者每次修改的數據量要小很多,并且在WiredTiger引擎下,同一個文檔的修改一定時間窗口下是可以在內存中合并的;
從讀取上看:查詢一個小時的數據,前者需要返回3600個文檔,而后者只需要返回60個文檔,效率上的差異顯而易見;
從索引上看:同樣,因為穩定數量的大幅度減少,索引尺寸也是同比例降低的,并且segId,ts這樣的冗余數據也會減少冗余。容量的降低意味著內存命中率的上升,也就是性能的提高;
每小時的信息用一個獨立的文檔存儲VS每分鐘的信息用一個獨立的文檔存儲
從寫入上看:因為WiredTiger是每分鐘進行一次刷盤,所以每小時一個文檔的方案,在這一個小時內要被反復的load到PageCache中,再刷盤;所以,綜合來看后者相對更合理;
從讀取上看:前者的數據信息量較大,正常的業務請求未必需要這么多的數據,有很大一部分是浪費的;
從索引上看:前者的索引更小,內存利用率更高;
(3)總結
那么到底選擇哪個方案更合理呢?從理論分析上可以看出,不管是小時存儲,還是分鐘存儲,都是利用了MongoDB的信息聚合的能力。
每小時的信息用一個獨立的文檔存儲:設計上較極端,優勢劣勢都很明顯;
每分鐘的信息用一個獨立的文檔存儲:設計上較平衡,不會與業務期望偏差較大;
落實到現實的業務上,哪種是最優的?最好的解決方案就是根據自己的業務情況進行性能測試,以上的分析只是“理論”基礎,給出“實踐”的方向,但千萬不可以此論斷。
十四、MongoDB高級集群架構
1、兩地三中心集群架構
雙中心雙活+異地熱備=兩地三中心