# rocksdb engine 寫邏輯
## 執(zhí)行路徑
DB::Put(key, value)是一個寫操作簡單封裝, 最終都會打包一個WriteBatch對象锻离,調(diào)用rocksdb::DBImpl::WriteImpl來完成寫网严。
也可以手工構(gòu)造一個WriteBatch晚岭,作為一個批量事務(wù)操作州袒,放入多個key/value操作看尼,一次提交递鹉。
```cpp
#0? rocksdb::MemTable::Add (this=0x1619800, s=1, type=rocksdb::kTypeValue, key=..., value=..., allow_concurrent=false, post_process_info=0x0) at db/memtable.cc:425
#1? 0x00000000005c25c2 in rocksdb::MemTableInserter::PutCF (this=0x7fffffffa8b0, column_family_id=0, key=..., value=...) at db/write_batch.cc:869
#2? 0x00000000005bdfe7 in rocksdb::WriteBatch::Iterate (this=0x7fffffffb0c0, handler=0x7fffffffa8b0) at db/write_batch.cc:381
#3? 0x00000000005bfa41 in rocksdb::WriteBatchInternal::InsertInto (writers=..., sequence=1, memtables=0x1616100, flush_scheduler=0x1608808, ignore_missing_column_families=false, log_number=0, db=0x1608000,
concurrent_memtable_writes=false) at db/write_batch.cc:1206
#4? 0x00000000004c85ae in rocksdb::DBImpl::WriteImpl (this=0x1608000, write_options=..., my_batch=0x7fffffffb0c0, callback=0x0, log_used=0x0, log_ref=0, disable_memtable=false) at db/db_impl.cc:4953
#5? 0x00000000004c6b72 in rocksdb::DBImpl::Write (this=0x1608000, write_options=..., my_batch=0x7fffffffb0c0) at db/db_impl.cc:4585
#6? 0x00000000004ccd99 in rocksdb::DB::Put (this=0x1608000, opt=..., column_family=0x15d6500, key=..., value=...) at db/db_impl.cc:5803
#7? 0x00000000004c69f0 in rocksdb::DBImpl::Put (this=0x1608000, o=..., column_family=0x15d6500, key=..., val=...) at db/db_impl.cc:4560
```
## WriteBatch
一個WriteBatch就是一個事務(wù),里面會有很多條操作記錄藏斩,可以調(diào)用WriteBatch.Put/Delete...等操作加入操作(Key/Value)
WriteBatch.rep_ 是一個binary buffer, 用于存儲batch中所有操作的記錄躏结,格式如下:
WriteBatch.content\_flags_ 標(biāo)記batch中含有的操作類型集合。
field | length? | description |
---------:| :----- |:-----
kHeader | FixInt64 | 序列號狰域,單調(diào)遞增媳拴, Batch sequence的起始值
Count | FixInt32 | 操作記錄個數(shù)
Type | FixInt8 + Var32Int | 操作類型 + column_family(if != 0)
Key | Var32String | key binary buffer
Value | Var32String | value binary buffer
Type/Key/Value每個記錄重復(fù)一條, kHeader/Count batch對象共用
## rocksdb::DBImpl::WriteImpl
- 新建一個WriteThread::Writer對象北专,關(guān)聯(lián)到傳入的batch object.
- 調(diào)用write\_thread_.JoinBatchGroup(&w);
### Group Commit
為了提高commit性能禀挫,存儲引擎會將很多線程的并發(fā)write合并到一個group,批量寫日志拓颓,write memory table,然后一次性commit.
JoinBatchGroup調(diào)用LinkOne(w, &linked_as_leader);將當(dāng)前write\_thread_中的writer連接成一個鏈表语婴,其中write\_thread_.newest\_writer_是鏈表的頭,是最新加入的follower驶睦,而第一個加入鏈表的也就是當(dāng)前group的leader(link_older=nullptr).
follower(newest_writer) --*link_older*--> follower --*link_older*--> follower --*link_older*--> **leader** ----> nullptr
如果當(dāng)前的Writer成為了Leader砰左,那么返回做剩下的提交邏輯,如果當(dāng)前已經(jīng)有了Leader场航,需要等待Write.state成為STATE_GROUP_LEADER | STATE_PARALLEL_FOLLOWER | STATE_COMPLETED
- As Leader
- 檢查是否需要Flush, 如果需要缠导,找出所有column_family中最大的MemTable的CF,調(diào)用SwitchMemtable,凍結(jié)當(dāng)前active memtable, 調(diào)用SchedulePendingFlush調(diào)度刷盤溉痢。
- 取當(dāng)前的versions_的LastSequence僻造。 開始持有DBImpl::mutex
- 調(diào)用write\_thread_.EnterAsBatchGroupLeader,這個函數(shù)確定當(dāng)前提交的批次應(yīng)該包含哪些數(shù)據(jù)
- 計算當(dāng)前可以批量提交的最大長度max_size; 如果leader.size<128KB max_size=leader.size+128KB孩饼,如果>128KB髓削,max_size=1MB.
- 調(diào)用CreateMissingNewerLinks(newest_writer),將整個鏈表的反向鏈接建立起來(link_newer)镀娶,成了一個雙向鏈表立膛。
- 從leader開始反向遍歷,一直到newest_writer,? 累加每個batch的size梯码,一直到max_size宝泵,超過之后截斷,同時檢查每個writer的sync/no_slowdown/disableWAL是否一致轩娶,不一致的地方也開始截斷儿奶,用last_writer標(biāo)記鏈表的結(jié)束位置,作為函數(shù)輸出參數(shù)返回鳄抒。w->callback->AllowWriteBatching()闯捎,也可以設(shè)置不想被batch的writer. 并且把符合條件的writer batch都push到write_group的vector中搅窿。
- 檢查是否可以parallel提交,條件有幾個:1. memtable支持隙券,2.allow_concurrent_memtable_write設(shè)置男应,3.write_group 有多個batch, 4. batch中沒有merge操作。
- 確定當(dāng)前提交的group的current_sequence=last_sequence+1(作為起始sequence), 并且將sequence先進行占位娱仔,一次性**為write_group中每個batch的每個操作記錄都分配一個sequence**. (注意writer.ShouldWriteToMemtable標(biāo)記為false的不計入sequence)
- 將write_group中的每個batch的數(shù)據(jù)都append到一個新的WriteBatch對象merged_batch中(tmp\_batch_)沐飘,如果group中只有一個batch, 那么就用這個batch,沒必要拷貝數(shù)據(jù)了。
- 設(shè)置新的merged_batch對象的sequence為current_sequence(起始sequence)
- 寫WAL牲迫,用的數(shù)據(jù)就是merged\_batch中的rep_ (如果是多個batch耐朴,那么tmp\_batch_可以清理了)
- 如果不允許并發(fā):串行執(zhí)行write memtable,調(diào)用WriteBatchInternal::InsertInto將write_group中所有數(shù)據(jù)串行寫入到memtable.
- 并行執(zhí)行(concurrent_memtable_writes)
- WriteThread::ParallelGroup 建立一個并行寫memtable group,pg.leader/last_writer分別指向鏈表的頭和尾盹憎。
- write\_thread_.LaunchParallelFollowers: 設(shè)置鏈表中每個writer.sequence為之前分配好的sequence筛峭,(注意每個batch分配自己的一段,調(diào)用InsertInto的時候再每個Key設(shè)置自己的sequence)陪每,設(shè)置writer.state為STATE_PARALLEL_FOLLOWER
- 作為Leader的writer batch開始寫memtable
- 調(diào)用write\_thread_.CompleteParallelWorker(&w)判斷是不是最后一個完成write memtable的線程
- 正常情況下(如果early_exit_allowed為false)影晓,只有l(wèi)eader會最后做收尾工作,因此即便leader不是最后一個寫完memtable的線程檩禾,也會等待writer.state == STATE\_COMPLETED 才會退出, 并返回true,表示需要做最后的提交工作(update versions_.LastSequence)挂签,leader的STATE_COMPLETEDe是由最后一個退出的follower線程設(shè)置的。
- follower線程如果不是最后一個完成工作的線程盼产,那么會一直等到writer.state == STATE_COMPLETED退出饵婆。
- follower線程如果是最后一個完成工作的線程,那么會先把group.leader.state設(shè)置為STATE_COMPLETED戏售,然后等待自己變成STATE_COMPLETED侨核,退出。
- 如果 CompleteParallelWorker返回了true(leader等到了STATE\_COMPLETED或者自己就是最后一個)灌灾,做提交動作更新全局的sequence: versions_->SetLastSequence(last_sequence);
- 調(diào)用write\_thread_.ExitAsBatchGroupLeader
- 注意當(dāng)前的newest\_writer_可能已經(jīng)加了很多新的write batch進來了搓译,在上一次commit的過程中,新進來的write batch還會一直往write\_thread_的鏈表上掛紧卒,但是本次提交的截止的點在EnterAsBatchGroupLeader時候確定的侥衬,因此退出的時候會將本次提交鏈還剩余的writer鏈诗祸,重新建立好反向鏈接跑芳,設(shè)置緊接著的writer為新的Leader,之前調(diào)用JoinBatchGroup等待的線程直颅,又可以繼續(xù)執(zhí)行下一批事務(wù)博个。
- 遍歷write_group中所有的writer, 設(shè)置所有writer的state為STATE_COMPLETED,這樣還在調(diào)用CompleteParallelWorker的follower線程就會退出功偿。
- As follower
- JoinBatchGroup之后如果沒有成為Leader盆佣,那就等著Leader線程在LaunchParallelFollowers的時候把自己設(shè)置為follower狀態(tài)往堡,一旦設(shè)置完,就進入follower寫memtable邏輯共耍,最后判斷CompleteParallelWorker是否可以退出虑灰,一般是等待所有的batch都干完活以后退出。
- 如果allow_concurrent_memtable_write沒有打開, follower線程會一直等待Leader干完所有的事情痹兜,最后調(diào)用ExitAsBatchGroupLeader設(shè)置狀態(tài)為STATE_COMPLETED后直接退出穆咐。
### writer鏈表在group commit過程中的變化
![link_list](rocksdb_writer_link_list.png)
### writer對象狀態(tài)變遷圖
![state_flow](rocksdb_writer_state_flow.png)