天天看點

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

>>釋出會傳送門

了解産品詳情

MongoDB 在今年正式釋出了新的 4.4 大版本,這次的釋出包含衆多的增強 Feature,可以稱之為是一個維護性的版本,而且是一個使用者期待已久的維護性版本,MongoDB 官方也把這次釋出稱為「User-Driven Engineering」,說明新版本主要是針對使用者呼聲最高的一些痛點,重點進行了改進。

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

而阿裡雲作為 MongoDB 官方的全球戰略合作夥伴,也即将全網獨家上線 4.4 新版本,下面就由阿裡雲 MongoDB 團隊的工程師針對一些使用者關注度比較高的 Feature ,進行深度解讀。

可用性和容錯性增強

Mirrored Reads

在服務阿裡雲 MongoDB 客戶的過程中,筆者觀察到有很多的客戶雖然購買的是三節點的副本集,但是實際在使用過程中讀寫都是在 Primary 節點,其中一個可見的 Secondary 并未承載任何的讀流量。

那麼在偶爾的當機切換之後,客戶能明顯的感受到業務的通路延遲會有抖動,經過一段時間後才會恢複到之前的水準,抖動原因就在于,新選舉出的主庫之前從未提供過讀服務,并不了解業務的通路特征,沒有針對性的對資料做緩存,是以在突然提供服務後,讀操作會出現大量的「Cache Miss」,需要從磁盤重新加載資料,造成通路延遲上升。在大記憶體執行個體的情況下,這個問題更為明顯。

在 4.4 中,MongoDB 針對上述問題實作了「Mirrored Reads」功能,即,主庫會按一定的比例把讀流量複制到備庫上執行,來幫助備庫預熱緩存。這個執行是一個「Fire and Forgot」的行為,不會對主庫的性能産生任何實質性的影響,但是備庫負載會有一定程度的上升。

流量複制的比例是可動态配置的,通過 mirrorReads 參數設定,預設複制 1% 的流量。

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

此外,可以通過db.serverStatus( { mirroredReads: 1 } )來檢視 Mirrored Reads 相關的統計資訊,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

Resumable Initial Sync

在 4.4 之前的版本中,如果備庫在做全量同步,出現網絡抖動而導緻連接配接閃斷,那麼備庫是需要重頭開始全量同步的,導緻之前的工作全部白費,這個情況在資料量比較大時,比如 TB 級别,更加讓人崩潰。

而在 4.4 中,MongoDB 提供了,因網絡異常導緻全量同步中斷情況下,從中斷位置恢複全量同步的能力。在嘗試恢複一段時間後,如果仍然不成功,那麼會重新選擇一個同步源進行新的全量同步。這個嘗試的逾時時間預設是 24 小時,可以通過 replication.initialSyncTransientErrorRetryPeriodSeconds 在程序啟動時更改。

需要注意的是,對于全量同步過程中遇到的非網絡異常導緻的中斷,仍然需要重新發起全量同步。

Time-Based Oplog Retention

我們知道,MongoDB 中的 Oplog 集合記錄了所有的資料變更操作,除了用于複制,還可用于增量備份,資料遷移,資料訂閱等場景,是 MongoDB 資料生态的重要基礎設施。

Oplog 是作為 Capped Collection 來實作的,雖然從 3.6 開始,MongoDB 支援通過 replSetResizeOplog 指令動态修改 Oplog 集合的大小,但是大小往往并不能準确反映下遊對 Oplog 增量資料的需求,考慮如下場景,

• 計劃在淩晨的 2 - 4 點對某個 Secondary 節點進行停機維護,應避免上遊 Oplog 被清理而觸發全量同步。

• 下遊的資料訂閱元件可能會因為一些異常情況而停止服務,但是最慢會在 3 個小時之内恢複服務并繼續進行增量拉取,也應當避免上遊的增量缺失。

是以,在真實的應用場景下,很多時候是需要保留最近一個時間段内的 Oplog,這個時間段内産生多少的 Oplog 往往是很難确定的。

在 4.4 中,MongoDB 支援 storage.oplogMinRetentionHours 參數定義最少保留的 Oplog 時長,也可以通過 replSetResizeOplog 指令線上修改這個值,如下,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

擴充性和性能增強

Hidden Indexes

Hidden Index 是阿裡雲 MongoDB 和 MongoDB 官方達成戰略合作後共建的一個 Feature。我們都知道資料庫維護太多的索引會導緻寫性能的下降,但是往往業務上的複雜性決定了運維 MongoDB 的同學不敢輕易的删除一個潛在的低效率索引,擔心錯誤的删除會帶來業務性能的抖動,而重建索引往往代價也非常大。

Hidden Index 正是為了解決 DBA 同學面臨的上述困境,它支援通過 collMod 指令對現有的索引進行隐藏,保證後續的 Query 都不會利用到該索引,在觀察一段時間後,确定業務沒有異常,可以放心的删除該索引。

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

需要注意的是,索引被隐藏之後隻是對 MongoDB 的執行計劃器不可見,并不會改變索引本身的一些特殊行為,比如唯一鍵限制,TTL 淘汰等。

索引在隐藏期間,如果新的寫入,也是會被更新的,是以也可以通過取消隐藏,很友善的讓索引立刻變的可用。

Refinable Shard Keys

當使用 MongoDB 分片叢集時,相信大家都知道選擇一個好的 Shard key 是多麼的重要,因為它決定了分片叢集在指定的 Workload 下是否有良好的擴充性。但是在實際使用 MongoDB 的過程中,即使我們事先仔細斟酌了要選擇的 Shard Key,也會因為 Workload 的變化而導緻出現 Jumbo Chunk,或者業務流量都打向單一 Shard 的情況。

在 4.0 及之前的版本中,集合標明的 Shard Key 及其對應的 Value 都是不能更改的,在 4.2 版本,雖然可以修改 Shard Key 的 Value,但是資料的跨 Shard 遷移以及基于分布式事務的實作機制導緻性能開銷很大,而且并不能完全解決 Jumbo Chunk 或通路熱點的問題。比如,現在有一個訂單表,Shard Key 為 {customer_id:1},在業務初期每個客戶不會有很多的訂單,這樣的 Shard Key 完全可以滿足需求,但是随着業務的發展,某個大客戶累積的訂單越來越多,進而對這個客戶訂單的通路成為某個單一 Shard 的熱點,由于訂單和customer_id天然的關聯關系,修改customer_id并不能改善通路不均的情況。

針對上述類似場景,在 4.4 中,你可以通過 refineCollectionShardKey 指令給現有的 Shard Key 增加一個或多個 Suffix Field 來改善現有的文檔在 Chunk 上的分布問題。比如,在上面描述的訂單業務場景中,通過refineCollectionShardKey指令把 Shard key 更改為{customer_id:1, order_id:1},即可避免單一 Shard 上的通路熱點問題。

需要了解的是,refineCollectionShardKey 指令性能開銷非常低,隻是更改 Config Server 上的中繼資料,不需要任何形式的資料遷移(因為單純的添加 Suffix 并不會改變資料在現有chunk 上的分布),資料的打散仍然是在後續正常的 Chunk 自動分裂和遷移的流程中逐漸進行的。此外,Shard Key 需要有對應的 Index 來支撐,是以refineCollectionShardKey 要求提前建立新 Shard Key 對應的 Index。

因為并不是所有的文檔都存在新增的 Suffix Field(s),是以在 4.4 中實際上隐含支援了「Missing Shard Key」的功能,即新插入的文檔可以不包含指定的 Shard Key Field。但是,筆者不建議這麼做,很容易産生 Jumbo Chunk。

Compound Hashed Shard Keys

在 4.4 之前的版本中,隻能指定單字段的哈希片鍵,原因是此時 MongoDB 不支援複合哈希索引,這樣導緻的結果是,很容易出現集合資料在分片上分布不均。

而在 4.4 中支援了複合哈希索引,即,可以在複合索引中指定單個哈希字段,位置不限,可以作為字首,也可以作為字尾,進而也就提供了對複合哈希片鍵的支援,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

有這個新功能之後,會帶來很多好處,比如在如下兩個場景下,

• 因為法律法規的要求,需要使用 MongoDB 的 zone sharding 功能,把資料盡量均勻打散在某個地域的多個分片上。

• 集合指定的片鍵的值是遞增的,比如在上文中舉的例子,{customer_id:1, order_id:1} 這個片鍵,如果customer_id 是遞增的,而業務也總是通路最新的顧客的資料,導緻的結果是大部分的流量總是通路單一分片。

在沒有「複合哈希片鍵」支援的情況下,隻能由業務對需要的字段提前計算哈希值,存儲到文檔中的某個特殊字段中,然後再通過「範圍分片」的方式指定這個預先計算出哈希值的特殊字段及其他字段作為片鍵來解決上述問題。

而在 4.4 中直接把需要的字段指定為為哈希的方式即可輕松解決上述問題,比如,對于上文描述的第二個問題場景,片鍵設定為 {customer_id:'hashed', order_id:1} 即可,大大簡化了業務邏輯的複雜性。

Hedged Reads

通路延遲的升高可能會帶來直接的經濟損失,Google 有一個研究報告表明,如果網頁的加載時間超過 3 秒,使用者的跳出率會增加 50%。是以,在 4.4 中 MongoDB 提供了 Hedged Reads 的功能,即在分片叢集場景下,mongos 會把一個讀請求同時發送到某個分片的兩個副本內建員,然後選擇最快的傳回結果回複用戶端,來減少業務上的 P95 和 P99 延遲。

Hedged Reads 功能是作為 Read Preference 的一部分來提供的, 是以可以是在 Operation 粒度上做配置,當 Read Preference 指定 nearest 時,預設啟用 Hedged Reads 功能,當指定為 primary 時,不支援 Hedged Reads 功能,當指定為其他時,需要顯示的指定 hedgeOptions,如下,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

此外,Hedged Reads 也需要 mongos 開啟支援,配置 readHedgingMode 參數為 on,預設 mongos 開啟該功能支援。

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

降低複制延遲

主備複制的延遲對 MongoDB 的讀寫有非常大的影響,一方面,在一些特定的場景下,讀寫需要等待,備庫需要及時的複制并應用主庫的增量更新,讀寫才能繼續,另一方面,更低的複制延遲,也會帶來備庫讀時更好的一緻性體驗。

Streaming Replication

在 4.4 之前的版本中,備庫通過不斷的輪詢主庫來擷取增量更新操作。每次輪詢時,備庫主動給主庫發送一個 getMore 指令讀取其上的 Oplog 集合,如果有資料,傳回一個最大 16MB 的 Batch,如果沒有資料,備庫也會通過 awaitData 選項來控制備庫無謂的 getMore 開銷,同時能夠在有新的增量更新時,第一時間擷取到對應的 Oplog。

拉取是由單個 OplogFetcher 線程來完成,每個 Batch 的擷取都需要經曆一個完整的 RTT,在副本集網絡狀況不好的情況下,複制的性能就嚴重受限于網絡延遲。是以,在 4.4 中,增量的 Oplog 是不斷的“流向”備庫的,而不是依靠備庫主動輪詢,相比于之前的方式,至少在 Oplog 擷取上節省了一半的 RTT。

當使用者的寫操作指定了 “majority” writeConcern 的時候,寫操作需要等待足夠多的備庫傳回複制成功的确認,MongoDB 内部的一個測試表明,在新的複制機制下,在高延遲的網絡環境中,可以平均提升 50% 的 majority 寫性能。

另外一個場景是使用者使用了Causal Consistency,為了保證可以在備庫讀到自己的寫操作(Read Your Write),同樣強依賴備庫對主庫 Oplog 的及時複制。

Simultaneous Indexing

在 4.4 之前的版本中,索引建立需要在主庫完成之後,才會複制到備庫上執行。備庫上的建立動作,在不同的版本中,因為建立機制和建立方式(前台、背景)的不同,對備庫 Oplog 的應用影響也大為不同。

但是,即使在 4.2 中,統一了前背景索引建立機制,使用了相當細粒度的加鎖機制——隻在索引建立的開始和結束階段對集合加獨占鎖,也會因為索引建立本身的性能開銷(CPU、IO),導緻複制延遲,或者因為一些特殊操作,比如 collMod 指令修改集合元資訊,而導緻 Oplog 的應用阻塞,甚至會因為主庫曆史 Oplog 被覆寫掉而進入 Recovering 狀态。

在 4.4 中,主庫和備庫上的索引建立操作是同時進行的,可以大幅減少因為上述情況所帶來的主備延遲,盡量保證即使在索引建立過程中,備庫讀也可以通路到最新的資料。

此外,新的索引建立機制是在 majority 的具備投票權限的資料承載節點傳回成功後,索引才會真正生效。是以,也可以減輕在讀寫分離場景下,因為索引不同而導緻的性能差異。

查詢能力和易用性增強

傳統的關系型資料庫(RDBMS)普遍以 SQL 語言為接口,用戶端可以在本地編寫融入部分業務邏輯的複雜 SQL 語句,來實作強大的查詢能力。MongoDB 作為一個新型的文檔資料庫系統,也有自定義的 MQL 語言,複雜查詢能力主要借助于 Aggregation Pipeline 來實作,雖弱于 RDBMS,但在最近的幾個大版本中也在持續不斷的打磨,最終的目的是使使用者在享受到 MongoDB 靈活性和擴充性的同時,也能享受到豐富的功能性。

Union

在多表聯合查詢能力上,4.4 之前隻提供了一個 $lookup stage 用于實作類似于 SQL 中的「left outer join」功能,在 4.4 中新增的 $unionWith stage 又提供了類似 SQL 中的「union all」功能,使用者把兩個集合中的資料聚合到一個結果集中,然後做指定的查詢和過濾。差別于 $lookup stage 的是,$unionWith stage 支援分片集合。當在 Aggregate Pipeline 中使用了多個 $unionWith stage 的時候,可以對多個集合資料做聚合,使用方式如下,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

可以在 pipeline 參數中指定不同的 stage,用于在對集合資料聚合前,先進行一定的過濾,使用起來非常靈活,下面舉一個簡單的例子,比如業務上對訂單資料按表拆分存儲到不同的集合,第二季度有如下資料(示範目的),

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

現在假設業務上需要知道,二季度不同産品的銷量,在 4.4 之前,可能需要業務自己把資料都讀出來,然後在應用層面做聚合才能解決這個問題,或者依賴某種資料倉庫産品來做分析,但是需要有某種資料的同步機制。

而在 4.4 中隻需要如下一條 Aggregate 語句即可解決問題,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

Custom Aggregation Expressions

4.4 之前的版本中可以通過 find 指令中的 $where operator 或者 MapReduce 功能來實作在 Server 端執行自定義的 JavaScript 腳本,進而提供更為複雜的查詢能力,但是這兩個功能并沒有做到和 Aggregation Pipeline 在使用上的統一。

是以,在 4.4 中,MongoDB 提供了兩個新的 Aggregation Pipeline Operator,$accumulator 和 $function 用來取代 $where operator 和 MapReduce,借助于「Server Side JavaScript」來實作自定義的 Aggregation Expression,這樣做到複雜查詢的功能接口都集中到 Aggregation Pipeline 中,完善接口統一性和使用者體驗的同時,也可以把Aggregation Pipeline 本身的執行模型利用上,實作所謂 「1+1 > 2」 的效果。

$accumulator 和 MapReduce 功能有些相似,會先通過init 函數定義一個初始的狀态,然後對于每一個輸入的文檔,根據指定的 accumate 函數更新狀态,然後會根據需要決定是否執行 merge 函數,比如,如果在分片集合上使用了 $accumulator operator,那麼最後需要把不同分片上執行完成的結果做 merge,最後,如果指定了 finalize 函數,在所有輸入文檔處理完成後,會根據該函數把狀态轉換為一個最終的輸出。

$function 和 $where operator 在功能上基本一緻,但是強大之處是可以和其他的 Aggregation Pipeline Operator 配合使用,此外也可以在 find 指令中借助于 $expr operator 來使用 $function operator,等價于之前的 $where operator,MongoDB 官方在文檔中也建議優先使用 $function operator。

其他易用性增強

Some Other New Aggregation Operators and Expressions

除了上述的 $accumulator 和 $function operator,4.4 中還新增了其他多個 Aggregation Pipeline Operator,比如做字元串處理的,擷取數組收尾元素的,還有用來擷取文檔或二進制串大小的操作符,具體見如下清單,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

Connection Monitoring and Pooling

4.4 的 Driver 中增加了對用戶端連接配接池的行為監控和自定義配置,通過标準的 API 來訂閱和連接配接池相關的事件,包括連接配接的關閉和打開,連接配接池的清理。也可以通過 API 來配置連接配接池的一些行為,比如,擁有的最大/最小連接配接數,每個連接配接的最大空閑時間,線程等待可用連接配接時的逾時時間,具體可以參考 MongoDB 官方的設計文檔。

Global Read and Write Concerns

在 4.4 之前的版本中,如果操作的執行沒有顯式指定 readConcern 或者 writeConcern,也會有預設行為,比如readConcern 預設是 local,而 writeConcern 預設是 {w: 1}。但是,這個預設行為并不可以變更,如果使用者想讓所有的 insert 操作的 writeConcern 預設都是是 {w: majority},那麼隻能所有通路 MongoDB 的代碼都顯式去指定該值。

在 4.4 中可以通過 setDefaultRWConcern 指令來配置全局預設的 readConcern 和 writeConcern,如下,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

也可以通過 getDefaultRWConcern 指令擷取目前預設的readConcern 和 writeConcern。

此外,這次 MongoDB 做的更加貼心,在記錄慢日志或診斷日志的時候,會記錄目前操作的 readConcern 或者 writeConcern 設定的來源,二者相同的來源定義有如下三種,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

對于 writeConcern 來說,還有如下一種來源,

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

New MongoDB Shell (beta)

對于運維 MongoDB 的同學來說,使用最多的工具可能就是 mongo shell,4.4 提供了新版本的 mongo shell,增加了像代碼高亮,指令自動補全,更加可讀的錯誤資訊等非常人性化的功能,不過,目前還是 beta 版本,很多指令還不支援,僅供嘗鮮。

深度解讀 MongoDB 最全面的增強版本 4.4 新特性

其他

這次的 4.4 釋出,前面講了主要是一個維護性的版本,是以除了上述解讀,還有很多其他小的優化,像 $indexStats 優化,TCP Fast Open 支援優化建連,索引删除優化等等,還有一些相對大的增強,像新的結構化日志LogV2,新的安全機制支援等,這些可能不是使用者最優先去關注的,在這裡就不一一描述了,感興趣的讀者可以自行參考官方的 Release Notes。