起因
前一段時(shí)間雌芽,在給用戶升級(jí) TiKV 并重啟的時(shí)候肤舞,突然爆出了大量 “Sst file size mismatch” 的錯(cuò)誤,也就是硬盤上面的 SST 文件跟實(shí)際 MANIFEST 里面的文件大小不一致。通常遇到這個(gè)問(wèn)題,表明 RocksDB 的文件已經(jīng)有問(wèn)題了猾警。
因?yàn)槭谴笈康?SST 都報(bào)了這樣的錯(cuò)誤,所以我們首先懷疑跟 disk 有關(guān)隆敢,看是否是硬件損壞发皿。但通過(guò)觀察系統(tǒng)日志,發(fā)現(xiàn)那段時(shí)間并沒有系統(tǒng)異常拂蝎,而且 disk 做了 raid0穴墅,照理也不應(yīng)該出現(xiàn)如此大規(guī)模的文件損壞,所以我們決定排除温自。剩下的就是懷疑 RocksDB 自己的 bug 了玄货。
于是將這個(gè)問(wèn)題放在了 RocksDB 的群里面,得知在某些新版本內(nèi)核的 XFS 文件系統(tǒng)上面捣作,fallocate
函數(shù)是有 bug 的誉结,這個(gè)就會(huì)導(dǎo)致使用 fallocate
分配的 SST 文件 size 跟實(shí)際 manifest 里面的不一致鹅士。雖然是一個(gè) kernel 的 bug券躁,但 RocksDB 也需要繞過(guò)去,修復(fù)在這個(gè) https://github.com/facebook/rocksdb/pull/2038。
但上面出現(xiàn)不一致錯(cuò)誤的 SST 文件并沒有損壞也拜,只是文件末尾多了一些 hole以舒,只要我們從 MANIFEST 文件里面得到這個(gè) SST 實(shí)際的 size,手動(dòng) truncate慢哈,就可以正常使用了蔓钟。為了修復(fù)這些文件,我決定研究一下 manifest 文件卵贱。
MANIFEST
MANIFEST 記錄著 RocksDB 一些狀態(tài)變化的信息滥沫,用來(lái)在重啟的時(shí)候能讓 RocksDB 還原到最近的一致狀態(tài)上面去。
為什么需要 MANIFEST 呢键俱?RocksDB 是一個(gè) key-value storage兰绣,但它實(shí)際的數(shù)據(jù)文件還是會(huì)存放到操作系統(tǒng)的文件系統(tǒng)上面。有些時(shí)候编振,文件系統(tǒng)的操作并不是原子的缀辩,可能因?yàn)橐恍┫到y(tǒng)的問(wèn)題導(dǎo)致出現(xiàn)數(shù)據(jù)不一致的狀態(tài),即使文件系統(tǒng)有 journal log踪央,也不是絕對(duì)安全的臀玄。所以 RocksDB 并不會(huì)將自己的一些 meta 信息存放到自己的 key-value 系統(tǒng)里面,而是使用了單獨(dú)的一個(gè) MANIFEST 文件畅蹂。
MANIFEST 包括一系列的 manifest 文件健无,以及標(biāo)識(shí)最后最新的一個(gè) manifest 文件的 CURRENT 文件。Manifest 文件名的格式類似 MANIFEST-<seq no>
魁莉,sequence number 會(huì)一直遞增睬涧,最新的 manifest 文件一定有最大的 sequence number。
我們可以認(rèn)為 MANIFEST 是一個(gè) transaction log旗唁,只要 RocksDB 的狀態(tài)變化畦浓,就會(huì)記錄一下。當(dāng)一個(gè) manifest 文件超過(guò)了配置的最大值的時(shí)候检疫,一個(gè)包含當(dāng)前 RocksDB 狀態(tài)信息的新的 manifest 文件就會(huì)創(chuàng)建讶请,CURRENT 文件會(huì)記錄最新的 manifest 文件信息。當(dāng)所有的更改都 sync 到文件系統(tǒng)之后屎媳,之前老的 manifest 文件就會(huì)被清除夺溢。
MANIFEST = { CURRENT, MANIFEST-<seq-no>* }
CURRENT = 標(biāo)識(shí)最新的一個(gè) manifest 文件
MANIFEST-<seq no> = 某個(gè) snapshot 的 RocksDB 狀態(tài)以及后續(xù)的更新操作
這里需要注意,一定要設(shè)置 max_manifest_file_size
烛谊,不然 RocksDB recover 的時(shí)間會(huì)非常的長(zhǎng)风响。
Version Edit
RocksDB 使用 version 來(lái)表示任意時(shí)間的一個(gè)特定狀態(tài)(其實(shí)就是 snapshot),任何對(duì) version 的改動(dòng)會(huì)被認(rèn)為是一次 version edit丹禀。一個(gè) version 通過(guò)合并一系列的 version edits 來(lái)構(gòu)造状勤。也就是一個(gè) manifest 文件其實(shí)就是包含著一系列 version edits record鞋怀。每一個(gè) record 都會(huì)有一個(gè)唯一的 edit number 來(lái)標(biāo)識(shí)。
Record Format
Version Edit 里面的類型都采用了特定的編碼方式持搜,對(duì)于整形密似,通常是 Var 和 Fixed 兩種,譬如 Var32 就是對(duì) int32 整數(shù)的可變長(zhǎng)度編碼葫盼。
對(duì)于 string 類型残腌,使用 size(n) + content
的方式,size 就是整個(gè) string 的實(shí)際長(zhǎng)度贫导,用 Var32 方式編碼抛猫。
對(duì)于一個(gè) Version edit 記錄來(lái)說(shuō),由 record ID 加上可變長(zhǎng)度的 bytes 組成孩灯,record ID 使用 Var32 編碼邑滨,而后面實(shí)際的 record 數(shù)據(jù)則是需要根據(jù)不同的類型來(lái)實(shí)際進(jìn)行解析。
Record Type
Record 有多重類型钱反,包括 Comparator掖看,Log Number,Previous Manifest File Number 等面哥,譬如對(duì)于 Comparator 來(lái)說(shuō)哎壳,格式就是
+-------------+----------------+
| kComparator | data |
+-------------+----------------+
<-- Var32 --->|<-- String -->|
具體不同 Type 的解析,可以參考 RocksDB 源碼 VersionEdit::DecodeFrom
函數(shù)尚卫,因?yàn)楸容^簡(jiǎn)單归榕,所以這里不再做說(shuō)明。
這里我們重點(diǎn)關(guān)注 record 為 New File 的類型吱涉,因?yàn)樗鼤?huì)記錄實(shí)際的 SST 的信息刹泄,譬如 New File Format 4 的格式就是:
+--------------+-------------+--------------+------------+----------------+--------------+----------------+----------------+
| kNewFile4 | level | file number | file size | smallest_key | largest_key | smallest_seqno | largest_seq_no |
+--------------+-------------+--------------+------------+----------------+--------------+----------------+----------------+
|<-- var32 -->|<-- var32 -->|<-- var64 -->|<- var64 ->|<-- String -->|<-- String -->|<-- var64 -->|<-- var64 -->|
+-----------+---------------+-------+------------------+-------+--------------+
|kPathID ---| Path size(n) | path | kNeedCompaction | 1 | value (0/1) |
+-----------+---------------+-------+------------------+-------+--------------+
<- var32 ->|<-- var32 -->|<- n ->|<-- var32 -->|<- 1 ->|<-- 1 -->|
具體到我們之前出現(xiàn)的問(wèn)題,如果要修復(fù) SST怎爵,就需要在 manifest 文件里面讀取到 New File record特石,然后解析出它實(shí)際的 path 以及對(duì)應(yīng)的 file size,然后將其做 truncate鳖链。
后記
因?yàn)橐粋€(gè) kernel 的 bug姆蘸,我們得以研究了一下 MANIFEST。當(dāng)然因?yàn)?RocksDB 已經(jīng)提交了 PR 去 fix 這個(gè)問(wèn)題芙委,加上現(xiàn)階段用戶那邊除了一臺(tái)機(jī)器有這個(gè) kernel 的 bug逞敷,其他機(jī)器都是沒問(wèn)題的,所以相關(guān) truncate 工具并沒有寫灌侣。