第一屆阿里雲PolarDB數據庫性能大賽總結

阿里雲第一屆數據性能大賽-初賽總結

只參加了初賽,複賽沒時間參加。最終初賽結果top 59/1653,第一次參加這種性能比賽,收穫頗豐。html

ali

1、題目

比賽整體分紅了初賽和複賽兩個階段,總體要求實現一個簡化、高效的 kv 存儲引擎
初賽要求支持 Write、Read 接口。
複賽在初賽題目基礎上,還須要額外實現一個 Range 接口。
程序評測邏輯 分爲2個階段:
1)Recover 正確性評測:
此階段評測程序會併發寫入特定數據(key 8B、value 4KB)同時進行任意次 kill -9 來模擬進程意外退出(參賽引擎須要保證進程意外退出時數據持久化不丟失),接着從新打開 DB,調用 Read、Range 接口來進行正確性校驗java

2)性能評測
隨機寫入:64 個線程併發隨機寫入,每一個線程使用 Write 各寫 100 萬次隨機數據(key 8B、value 4KB)
隨機讀取:64 個線程併發隨機讀取,每一個線程各使用 Read 讀取 100 萬次隨機數據
順序讀取:64 個線程併發順序讀取,每一個線程各使用 Range 有序(增序)遍歷全量數據 2 次 注: 2.2 階段會對全部讀取的 kv 校驗是否匹配,如不經過則終止,評測失敗; 2.3 階段除了對迭代出來每條的 kv校 驗是否匹配外,還會額外校驗是否嚴格字典序遞增,如不經過則終止,評測失敗。git

2、思路

1.初步試水

第一次參加這種性能大賽,想着是先實現幾種基本的kv模型,再調優。因而通過網上資料調研,先初步實現了幾種基本的存儲模型:BitCask、LSM。這時key,value是存在一塊兒的,單獨維護了索引文件,LSM樹中採用超過閾值則進行compaction操做,即k路歸併操做。github

設計要點:
  • 內存中的Immutable MemTable:只能讀不能寫的內存表,爲了避免阻塞寫操做,在一個MemTable滿了的時候會將其設爲Immutable MemTable並開啓新的MemTable;
  • 磁盤中的SSTable:存儲的就是一系列的鍵值對;
  • LSM Tree中分層: 使用雙端隊列進行MemTable的存放,這樣在查找時能夠遵循LRU原則,增長了內存hit,必定程度上優化了查找效率;
  • 查詢時增長了布隆過濾器以加速查找,在每一個SSTable內進行二分查找;
  • WAL:write ahead log:先寫log,再寫kv,以防止斷電數據的丟失
實現要點:
  • 使用讀寫鎖進行讀寫操做的同步;
  • 使用synchronized同步tableCreationLock進行表建立的同步;
  • 設置一些參數,好比每層鵝扇出數目,使得性能可調;

2.經歷掙扎

因而早早地寫好了第一天開評測就線上測試,結果老是超時(官方限定一小時內沒跑完則超時)。因而就沉迷優化代碼,去掉重量級鎖,改成輕量級鎖,線下使用JProfile等工具進行性能調優。奈何不管如何調優,線上老是超時,偶爾還莫名其妙的OOM。通過幾天debug,也沒有任何轉機。安全

就在快要放棄時,通過一名大神提點,忽然發現可能根本是個人方案就有問題。
才發現本身忽略了比賽很重要的一個前提:存儲設備是SSD!而不是通常的磁盤。
因而再次調研,發現一篇論文:WiscKey: Separating Keys from Values in SSD-conscious Storage 是專門針對LSM在SSD上作的優化。核心思想就是將key 、value分開存儲多線程

而且瞭解到,本次比賽的 key 共計 6400w,分佈比較均勻。因而通過和隊友的討論,以爲能夠將key、value分開存,這能夠解決寫放大問題,可是線程競爭呢?64個線程,實際線下測的時候每次併發跑的最多50多個,總會有好幾個線程被阻塞。因而查閱資料,發現可使用「數據分桶」的思想,併發

3.終於上路

因而,咱們趕忙從新實現了一版BucketDB,按照分桶的思想,進行新的一輪測試,終於,此次有了姓名!函數

數據分桶的好處,能夠大大減小順序讀寫的鎖衝突,而 key 的分佈均勻這一特性,啓發咱們在作數據分區時,能夠按照 key 的位進行設計hash函數,將高几位相同的key都分進一個桶。工具

設計要點:
  • 將key、value 分開存,則key和value文件的write pos始終會保持一致,即索引只要存儲key的file 的write pos就好了,也即當前file的長度
  • key 分桶的hash 函數的設計:按照當前線程id,能夠分到不一樣的桶中,根據 key hash,將隨機寫轉換爲對應分區文件的順序寫。
實現要點:
  • 將整個讀寫桶設計成BucketReaderWriter類,每一個都維護了本身的data direct io file 和index mmap
  • 數據key進行直接io,索引進行mmap,維護一個索引的mmap空間,不夠的時候再去從新映射一段空間

3、優化

以後想到了幾個優化思路:post

  1. 初始化加載內存映射時,爲加快能夠採起一大片內存映射mmap的方式
  2. 初始化加載內存映射時,加載索引的消耗很大,考慮進行並行初始化,能夠對64(或128)個Worker分別進行初始化,實測初始化耗時提高了60%
  3. hash函數的優化:更改成 key[0] % CommonConfig.MAP_COUNT

4、遇到的坑

1.線上各類OOM

  • Heap ByteBuffer會有內存泄漏!!: Fixing Java's ByteBuffer native memory "leak"
    Heap bytebuffer會爲每一個線程都保持一個直接內存拷貝!
  • ThreadLocal ====> 可能致使內存泄漏!!!
    ThreadLocal 經過隱式的在不一樣線程內建立獨立實例副本避免了實例線程安全的問題
    對於已經再也不被使用且已被回收的 ThreadLocal 對象,它在每一個線程內對應的實例因爲被線程的 ThreadLocalMap 的 Entry 強引用,沒法被回收,可能會形成內存泄漏。

2.內存調優

線上錯誤日誌:內存映射文件時寫入失敗
後來發現是元空間的鍋!jdk1.8裏的metaspace!
元空間並不在虛擬機中,而是使用直接內存,
因而還須要設置一個Metaspace參數,以防止直接內存爆掉!

-XX:MaxMetaspaceSize=300m

另外,優化了整個程序的內存使用 => 隨着整個程序的運行,內存佔用愈來愈大,顯然是不少對象沒有回收 => 優化程序,經過將沒必要要的引用設爲null,循環內不建立對象

3.多線程競爭開銷大

線上超時問題,一是LSM自己寫放大明顯;二是多線程競爭激烈,線程切換過多,致使寫入性能急劇降低。

參考文獻

https://github.com/abbshr/abbshr.github.io/issues/58
http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html
https://raoxiaoman.github.io/2018/04/08/wisckey%E9%98%85%E8%AF%BB/. LSM-Tree存在的問題
https://colobu.com/2017/10/11/badger-a-performant-k-v-store/
http://www.javashuo.com/article/p-eysmnacc-gk.html
https://zhuanlan.zhihu.com/p/38810568
http://blog.jobbole.com/69969/
https://www.zhihu.com/question/31024021
https://juejin.im/post/5ba9f9d7f265da0afd4b3de7 探究SSD寫放大的成因與解決思路
http://codecapsule.com/2014/10/18/implementing-a-key-value-store-part-7-optimizing-data-structures-for-ssds/

相關文章
相關標籤/搜索