我如何吸引Elastic创始人一起对高并发写入进行优化?
導語:在騰訊金融科技數據應用部的全民 BI 項目里,我們每天面對超過 10?億級的數據寫入,提高 ES 寫入性能迫在眉睫,在最近的一次優化中,有幸參與到了 Elasticsearch 開源社區中。
背景
為了更便捷地分析數據,騰訊金融科技數據應用部去年推出了全民 BI 的系統。這個系統通過 Elasticsearch 進行基礎的統計,超過 10?億級的數據量需要盡可能快速地導入到 ES 系統中。即使經過多次的參數優化,我們依然需要幾個小時才能完成導入,這是系統此前存在的一大瓶頸。
在這樣的背景下,我們開始決定進一步深入 ES,尋找優化點。
優化前的準備
我們準備了 1000?萬的數據,并在原程序(Spark 程序寫入)上進行了幾輪單機壓測,得到了一些基本的性能數據。
機器配置:CPU 24核,內存?64GES 基本配置:堆內存?31G
其他參數調整包括?lock memory,translog.durability?調整成?async?等(更詳細的策略可以參見
CPU:80%+
尋找理論值
在往下進入深水區之前,我們需要先回顧一下 ES 本身,ES 本身是在 Lucene 基礎上設計的分布式搜索系統,在寫入方面主要提供了:事務日志和成組提交的機制提高寫入性能并保證可靠性
提供?schema?的字段定義(映射到 Lucene?的字段類型)
要進行優化,首先得驗證一個問題:Lucene 的極限速率能到達多少,所以我在我的本機上構建了這樣的一個測試。
Macbook Pro 15,6核12線程數據量?1000?萬,每個?document 400?個字段,10?個線程并發(考慮?mac cpu Turbo 4.5G?,服務器?2.4G(24核),所以只采用?10?線程并發)
驗證寫入耗時 549s(約 10?分鐘)。
26 分鐘?—> 10?分鐘,意味著理論上是可行的。那剩下的就看如何接近這個極限。因為那說明一定是 ES 本身的一些默認特性導致了寫入速率無法提升。
下面的介紹忽略了一些相對簡單的參數調優,比如關閉 docvalues,這個對于非 text 字段,ES 默認開啟,對于不需要 groupby 的場景,是不必要的,這個可以減少不少性能。
經過初步的參數優化寫入耗時降低到了?18?分鐘,這是后面繼續往下優化的基礎。
理解 ES?寫入的機制
ES?的寫入流程(主分片節點)主要有下面的幾步根據文檔 ID?獲取文檔版本信息,判斷進行?add?或?update?操作
寫 Lucene:這里只寫內存,會定期進行成組提交到磁盤生成新分段
寫 translog:寫入文件
▲ translog?作用
除了上面的直接流程,還有三個相關的異步流程
定期進行?flush,對 Lucene?進行?commit
定期對?translog?進行滾動(生成新文件),更新?check point?文件
定期執行 merge 操作,合并 Lucene 分段,這是一個比較消耗資源的操作,但默認情況下都是配置了一個線程。
優化第一步 —?參數調優
寫 Lucene 前面已經優化過,那么第一步的文檔查找其實是在所有分段中進行查找,因為只提供了一個線程進行 merge,如果 merge 不及時,導致分段過的,必然影響文檔版本這一塊的耗時。
所以我們觀察了寫入過程中分段數的變化:▲ 寫入過程中分段的變化
觀察發現,分段的增長速度比預期的快很多。按照默認配置,index_buffer=10%,堆內存 31G 的情況,按 Lucene 的寫分段機制,平均到每個線程,也有 125M,分段產生的速度不應該那么快。而這個問題的根源就是 flush_threshold_size 默認值只有 512M ,這個參數表示在當未提交的 translog 日志達到該閾值的時候進行一次刷盤操作。
▲ 小分段的產生
▲ 調整后比較緩和的分段增長
測試結果一看:18?分鐘!基本沒有效果!
理論上可行的方案,為什么卻沒有效果,帶著這個疑問繼續潛入深水區。
優化繼續 —?線程分析
這時候就需要進行堆棧分析了,多次取樣后,發現了下面的一個頻繁出現的現象:▲ 被堵塞的線程發現很多線程都停在了獲取鎖的等待上,而 writeLock 被 rollGeneration 占用了。
寫線程需要獲取?readLock
而在高 flush_threshold_size 的配置下,rollGeneration 發生了 300+?次,每次平均耗時 560ms,浪費了超過 168s,而這個時間里寫入線程都只能等待,小分段的優化被這個抵消了。這里有很多的關聯關系,lush 操作和 rollGeneration 操作是互斥的,因為 flush 耗時較長(5~10?秒左右),在默認 flush_threshold_size 配置下,rollGeneration 并沒有這么頻繁在 100?次左右,提高 flush_threshold 放大了這個問題。初步優化方案提交
因為我們在寫入過程中使用的 translog 持久化策略是 async,所以我很自然地想到了把寫日志和刷盤異步化。▲ 初版提交社區的方案
一開始的方案則想引入disruptor,消除寫線程之間的競爭問題,后面因為es的第三方組件檢查禁止使用sun.misc.Unsafe (disruptor無鎖機制基于Unsafe實現)而放棄。基于這個方案,測試結果終于出現了跨越:13分鐘。
初版的方案問題比較多,但是它有兩個特點:足夠激進:在配置為?async 策略時,將底層都異步化了
凸顯了原方案的問題:讓大家看到了?translog 寫入的影響
Elastic?創始人加入討論
沒想到的是,在社區提交幾次優化后,竟然吸引了大佬 Simon Willnauer 的加入。
Simon WillnauerElastic?公司創始人之一和技術?Leader
Lucene Core Commiter and PMC Member
Simon 的加入讓我們重新復盤了整個問題。
通過對關鍵的地方增加統計信息,我最終明確了關鍵的問題點在于 FileChannel.force 方法,這個操作是最耗時的一步。
sync 操作會調用 FileChannel.force,但沒有在 writer 的對象鎖范圍中,所以影響較小。但是因為 rollGeneration 在 writeLock 中執行,所以阻塞的影響范圍就變大了
跟社區討論后,Simon 最后建議了一個折中的小技巧,就是在關閉原 translog 文件之前(writeLock 之外),先執行一次刷盤操作。▲ 代碼修改
這個調整的效果可以讓每次 rollGeneration 操作的耗時從平均 570ms 降低到 280ms,在我的基準測試中(配置 flush_threhold_size=30G,該參數僅用于單索引壓測設計,不能在生產環境使用),耗時會從 18 分鐘下降到 15 分鐘。
事實上,這并不是一個非常令人滿意的解決方案,這里選擇這個方案主要出于兩點考慮:1.未來新的版本將考慮不使用 translog?進行副分片的?recovery,translog?的滾動策略會進行調整(具體方案?elasitc未透露)2.這個修改非常的風險非常小提交社區
最后根據討論的最終結論,我們重新提交了 PR,提交了這個改動,并合并到了主干中。
總結和待續
下面是 ES 寫入中的影響關系和調用關系圖,從圖中可以看到各個因素直接的相互影響。▲ InternalEngine?中的影響關系
最近提交的優化實時上只優化了 rollGeneration,而實際上這里還有一些優化空間 trimUnreferenceReader,這個也在跟社區溝通中,并需要足夠的測試數據證明調整的效果,這個調整還在測試中。
而在我們目前實際應用場景中,我們通過調整下面兩個參數提高性能:
index.translog.flush_threshold_size 默認 512M,可以適當調大,但不能超過 indexBufferSize*1.5 倍/(可能并發寫的大索引數量),否則會觸發限流,并導致 JVM 內存不釋放!
index.translog.generation_threshold_size(默認?64M,系統支持,但官方文檔沒有的參數,超過該閾值會產生新的?translog?文件),要小于?index.translog.flush_threshold_size,否則會影響?flush,進而觸發限流機制
張超《Elasticsearch源碼解析與優化實戰》
總結
以上是生活随笔為你收集整理的我如何吸引Elastic创始人一起对高并发写入进行优化?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 打不死我的,终将使我强大!DevOps黑
- 下一篇: 微软+开源,那些亲爱的以及热爱的