leveldb groupcommit思想
leveldb是一個KV引擎,支援上層多用戶端線程并發寫該引擎。所有用戶端的寫入會連結成一個連結清單,隻需要head節點作為主writer負責寫入,如圖示例:

寫由主writer負責,這個batch其他writer同步等待,等待被喚醒即可。主writer行為如下:
- 合并所有writer的writeRequest,生成一個大batch
- 寫WAL
- 寫memtable
- 都操作完,喚醒大batch中的其他writer回用戶端ack,喚醒新進入的writer,作為主writer繼續執行。
優點
- 小的IO都會合并成大的IO落盤(合并batch),提升磁盤吞吐,寫WAL,寫memtable單線程執行,不需要鎖。
缺點
-
多線程變單線程工作,無法發揮多核優勢。
是以Rocksdb在這個方面做了很多優化工作,其中有一個特性就是pipelined write。
rocksdb pipelined write
思想很樸素,拆分寫WAL,寫Memtable等子任務項。用戶端線程接力完成這些子任務項。每個子任務有個狀态位,用戶端線程通過狀态位CAS操作搶任務項執行權力
拆分的task一般也就小幾個,也就能多用幾個線程,大量的用戶端線程仍舊同步等待。現代cpu的多核架構仍不能有效發揮出來。
并行寫+barrier思想,cassandra示例
用戶端并行的寫入,每個線程都寫透,大家角色對等,這樣也不存在rocksdb那樣詭異的拆子任務邏輯,同時也能自适應硬體,充分發揮多核優勢。但也存在如下問題:
- 每個線程僅對自己的kv追加寫WAL,單條kv都是小IO,浪費iops,這一點可以通過MMAP優化。
- 多線程鎖競争激烈,其實僅在寫wal及寫memtable存在臨界區,僅對這兩塊邏輯加鎖即可。或者優化成lock-free程式設計。
用戶端線程要源源不斷寫wal,然後寫memtable。當memtable寫滿,flush線程要刷sst。要通知用戶端切換寫新memtable怎麼實作,用互斥鎖肯定可以實作。更樸素的思想是用barrier,核心思想是設定栅欄,栅欄前面的,栅欄後面的分别處理。參考下圖
- 有一個全局的writeorder,用戶端線程寫storage之前,調用writeorder.start方法,該方法增加目前group計數引用,将目前寫attach到目前group上,上圖就是group1. 然後繼續寫WAL,寫memtable。可以看得出來整個鍊路耗時很長。該寫線程完成後或異常時,目前group的引用計數-1。
- 背景線程周期性嘗試flush memtable,先建立新memtable,加入到連結清單頭,标記為最新memtable。
- 把old-memtable的barrier設定上,該barrier前一個group是group1.通過第一點我們知道用戶端線程寫完還是比較慢的,flush線程會等group1引用計數為0,也就是所有線程寫完才會刷sst。
- flush線程封箱barrier,意思是新的寫會挂載在group2上,group1會減引用,如果為0然後unlink掉。
- 新的寫入attach到group2, 舊的寫入寫完慢慢減group1的引用,變為0之後,寫線程都寫完了,flush線程可以安全的刷sst,并且回收memtable。
總結
簡單的記錄了常見開源引擎裡面一些令人印象深刻的優化,後續再慢慢追加wiretiger&innodb一些特别點。