Faster: A Concurrent Key-Value Store with In-Place Updates
TL;DR
- 使用Epoch Protection Framework來避免lock operation
- 使用
in-place
update來避免I/O - 承上,設計hybrid Log Allocator混合
memory
和disk
access
Major components
Epoch Protection Framework
這套framework定義了三個變數:
- global shared atomic counter
- thread local counter
- which is strictly less all
每當thread做了某個operation,就會increase 並且把設定成,而且也可以在設定action,當的時候,就會執行這些action。
使用epoch
的好處是保證了每個operation都是sequential
,不會有並行處理的問題,而且只要某個timestamp
is visble to all thread,就代表這個timestamp
之前的操作都被所有thread acknowledge,保證了consistency。
Operations
- : 保留的entry給此thread,並且把更新到
- : 更新到, 到最大的"secure"值,並且把trigger 的所有action。
- : 把的值從變成,並且註冊到 。
- : 把thread 從中釋放。
Hash Index
Architecture
Index format
Hash index
的數目為,每個hash index
都是一個hash bucket
,每個hash bucket
的大小都跟cache line的大小一樣。要知道一個hash value
分配到哪個hash bucket
,只需要取vaule的後面個bits,就可以知道map到哪個hash bucket
。
一個hash bucket
會有n
個8-byte
的hash entry,hash entry設定為8-byte
是因為在64-bit
的系統可以做atomic
的compare-and-swap。
Hash bucket
和hash entry的format如下:
Hash entry
每個hash entry
由三個部分組成:
- Tentative bit:用來表示此entry是否被使用
- Tag (up to 15 bits): Optional。但是可以跟offset來搭配組成,且此組合唯一。
- Address (48 bits): 在
allocator
裡面的位置。
Handle concurrent inserting same (offset, tag) pair
Index有三種operation:find
, insert
和delete
,其中find
和delete
沒有concurrent
的問題,所以只需要處理insert相同entry(相同的)的情況。
因為inserting
的時候,是在hash bucket
找第一個有empty的slot來insert,所以當兩個thread同時的insert了相同的,而在這時候又有另外一個thread刪除了bucket裡面的某個slot,那有可能導致兩個thread看到的第一個empty entry並不一樣,並且同時insert。
這邊採用的是2-phase
的insertion。在做insertion的時候,thread會寫入entry並且set tentative bit,然後thread檢查是否有其他slot有相同的,如果有的話就reset tentative bit並且retry。
Hybrid Log Allocator
Head & Tail Offset
Head offset
紀錄logical address space
裡面,memory部分最低的可用位置。Tail offset
指向memory部分下一個可用的位置。兩者的差代 表可用的記憶體大小,而差距的誤差時間約保持在一個固定時間(constant)。
Circular Buffer
Allocator
額外用兩個array flush status
和closed status
來記錄哪些page被寫到second storage
,哪些可以page可以被evict。
每當有新page被append,epoch就會bump,然後之前的page 就會被asynchonous的flush到second stroage
。
而要evict page的時候,就增加head offset
的值,然後bump epoch,並且在trigger action裡面set closed status
。
In-place Update
整個logical address space
被分成三個部分:stable
、read-only
、mutable
。
Stable: 在disk
。無法in-place
修改。
Read-only: 在memory
。無法in-place
修改,如果要修改會append
一筆到mutable
。概念上是準備被flush的page,但如果在flush之前被更新,就重新放回活躍區(mutable
)。
Mutable: 在memory
。如果修改會in-place
的修改。
Fuzzy Region
以上的方法有個問題,如果mutable
的區域因為某些thread的更新而變成read-only
,但是這個變動還沒反應到所有thread,因此某些thread的data是stale。在這個情況,這些含有stale data的thread可能做in-place
的update,導致inconsistent。
為了避免這個問題,需要額外一個新的marker
: safe read-only
offset。在read-only
跟safe read-only
之間的區域稱作fuzzy region
,在不同的thread可能被視為read-only
或mutable
。
在fuzzy region
的update有3種operation適用:
- Blind update: 這種操作適用在thread不考慮舊值的狀況,如果data stale,其他thread也會append一筆到
mutable
,所以沒關係。 - Read-modify-write: 這種操作有可能會產生inconsistent,解決方法是把context記錄下來,defer到之後做。
- CRDT update: 如果data type是
CRDT
,那可以各自做,之後在merge。
Recovery
HybridLog
被當成WAL
來使用,並且在flush的時候會紀錄tail offset
的值,start
和end
的時候各有一個值。但因為有in-place
update的關係,這兩個時間點之間的操作是fuzzy的,in-place
update的行為可能發生在append
前或後,所以無法判斷in-place
的操作之中的order。
解決問題的方式被記錄在另外一篇paper。