原文標題:Changing Fundamental Behavior With Two Lines of Code(用兩行代碼改變基礎行爲)html
做者:Oren Eini, CEO RavenDB數據庫
一直以來咱們對 RavenDB 的啓動時間並無太多關注 —— 5 秒或者 15 秒都不是什麼問題。可是若是超過 15 秒,甚至長達 3 分鐘的話,那咱們就必須關注了。性能
咱們的一個用戶提供了一個有意思的用例。他們的系統運行在 Azure 上,並充分利用了多設備存儲,也就是將數據庫臨時文件(journals)放在高性能存儲服務上,而數據自己放在更大(且相對較慢)的普通存儲上。這麼作是由於他們的數據量很大,好比某條索引的大小就超過了 256GB。大數據
在他們系統當中,RavenDB 的啓動慢到沒法接受。咱們通過排查發現根本緣由在於啓動過程當中的數據恢復階段,此時數據庫會從新執行臨時文件中的最近事務,以保證數據完整性。一般來說這步花不了多少時間,由於默認狀況下,即便數據庫負載較大時,臨時文件大小也只有 256MB 左右。但咱們這位客戶的使用場景比較特殊,咱們觀察到臨時文件中一個事務的數據量能達到幾個 GB 的大小,再加上臨時文件的內容是通過壓縮的,因此當數據恢復到主存儲當中時,實際的數據量會達到 10GB 以上。雖然那些已經執行過的事務不須要重複執行,但咱們必需要先掃描臨時文件,來找到哪部分是已經執行過的。spa
在這個基礎上,考慮到數據庫的個數,以及每一個數據庫的索引個數,RavenDB 啓動時的總體消耗就變得十分可觀了。顯然這不是咱們所要的。若是咱們遇到系統崩潰,那麼目前尚未什麼好的辦法來避免從新執行這些事務;但問題是就算沒有遇到崩潰,就算是正常關閉,從新啓動的過程仍是同樣的緩慢。htm
這個問題本質上是由於 RavenDB 沒有在臨時文件中標記哪一個位置是已經同步過的,因此在啓動過程當中咱們必須掃描整個臨時文件,來找到須要從新執行的部分。固然咱們能夠在臨時文件中補上這個標記,可是咱們不能直接去更改客戶現有系統的數據文件格式,這種解決方案不但感受彆扭,並且可能帶來兼容性問題。索引
咱們也考慮修改數據庫的行爲,使得在對某個大數據量的事務同步成功後,更加主動的切換到另外一個臨時文件。今天我在查看相關代碼時,發現了一個有問題的地方。每當咱們處理一個大事務(事務大小超過臨時文件最大大小)時,爲了有足夠的空間,咱們會在磁盤上擴充臨時文件的大小,而咱們分配空間的計算方式頗有意思:圖片
如圖所示,若是當前的臨時文件大小小於須要的最小空間,咱們會要增長其空間;同時爲了不過於頻繁的擴充文件操做,咱們還會考慮給下一個事務預留足夠的空間。如今咱們假設當前臨時文件大小爲 256MB(也是系統預設的最大值),而事務大小爲 1.56GB。事務
這種狀況下,臨時文件將會擴充到 2GB 大小,而其中只有 1.56GB 用到了。原本剩餘的空間咱們是能夠繼續利用的,除非下一個事務很大,好比有 800MB,咱們就會要重建一個新的 1GB 大小的文件。get
這個時候問題來了。對於這個 2GB 大小的臨時文件,假設咱們已經成功將其內容同步到了主存儲,那麼剩下還有 440MB 的空間可用,咱們就會保留這個臨時文件用它來存儲下一個事務。若是在這個點上數據庫進行了重啓,那麼在啓動階段就必須掃描整個 2GB 的臨時文件來確保沒有丟失數據。要修復這個問題也超簡單:
咱們要作的就是當擴充後的大小大於臨時文件最大大小時,取臨時文件最大大小和實際須要的大小之間的最大值做爲最終的實際大小。這樣擴充後的臨時文件就恰好只能容納一個大事務。當這個事務成功同步後,由於沒有額外的空間再容納另外一個事務,因而 Voron 便會立刻清理這個文件。這樣臨時文件中就不存在殘留的大事務數據。這個改動既簡潔又有效,很是棒,我很是喜歡。