recycle_log_file_num
複用wal檔案資訊, 優化wal檔案的空間配置設定,減少pagecache中檔案元資訊的更新開銷。
為同僚提供了一組rocksdb寫優化參數之後有一個疑惑的現象被問到,發現之前的一些代碼細節有遺忘情況,同時也發現了這個參數的一些小優化,這裡做個總結。
在參數:
opts.recycle_log_file_num = 10;
opts.max_write_buffer_number = 16;
opts.write_buffer_size = 128 << 20;
大壓力寫下 出現了多個.log檔案同時存在的現象,想要看看為什麼會有這個現象。
在描述這個現象産生的原因之前我們先看看Rocksdb的wal建立以及清理過程,其中
recycle_log_file_num
是如何reuse log的
Rocksdb的WAL建立
如果不設定
disable_memtable=true
,不設定
enable_pipelined_write=1
,不
disableWAL=1
的話,基本的寫入調用棧如下:
JoinBatchGroup的邏輯這裡暫不提及,從大體的寫入過程中各個檔案的建立過程如下:
DBImpl::PreprocessWrite
DBImpl::SwitchWAL
DBImpl::SwitchMemtable
DBImpl::CreateWAL // 建立wal檔案
在CreateWal檔案的時候,Rocksdb會為這個wal建立一個
PosixEnv
下的檔案句柄,以及檔案名,并建立一個檔案writer,用來後續的資料寫入。
如下代碼:
// 其中recycle_log_number 為目前想要複用的wal log檔案名
// new_log 為需要在該函數中建立的writer,最後傳出來
// log_file_num 是建立的新的檔案名,如果可回收的檔案為0,則直接用新的就可以了。
IOStatus DBImpl::CreateWAL(uint64_t log_file_num, uint64_t recycle_log_number,
size_t preallocate_block_size,
log::Writer** new_log) {
......
// 根據檔案num,建立新的檔案名
std::string log_fname =
LogFileName(immutable_db_options_.wal_dir, log_file_num);
// 如果有可回收的numer,則reuse
if (recycle_log_number) {
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"reusing log %" PRIu64 " from recycle list\n",
recycle_log_number);
std::string old_log_fname =
LogFileName(immutable_db_options_.wal_dir, recycle_log_number);
TEST_SYNC_POINT("DBImpl::CreateWAL:BeforeReuseWritableFile1");
TEST_SYNC_POINT("DBImpl::CreateWAL:BeforeReuseWritableFile2");
io_s = fs_->ReuseWritableFile(log_fname, old_log_fname, opt_file_options,
&lfile, /*dbg=*/nullptr);
} else {
io_s = NewWritableFile(fs_.get(), log_fname, &lfile, opt_file_options);
}
if (io_s.ok()) {
......
// 建立一個file writer
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
std::move(lfile), log_fname, opt_file_options,
immutable_db_options_.clock, io_tracer_, nullptr /* stats */, listeners,
nullptr, tmp_set.Contains(FileType::kWalFile)));
*new_log = new log::Writer(std::move(file_writer), log_file_num,
immutable_db_options_.recycle_log_file_num > 0,
immutable_db_options_.manual_wal_flush);
}
return io_s;
}
其中
ReuseWritableFile
函數和
NewWritableFile
函數内部分别打開的是一個存在和不存在的檔案,如果我們能夠reuse log檔案名,則在
ReuseWritableFile
函數中通過open系統調用打開已存在檔案的時候不需要建立新的dentry和inode,且不需要将這一些中繼資料更新到各自dcache/inode-cache中的相應hash表中,是以重用檔案名這裡的優化就展現在核心對檔案的一些操作邏輯上。
關于open系統調用的核心邏輯,可以參考從unlink系統調用來看作業系統檔案系統原理。
CreateWAL
這個函數僅僅是用到了recycle_log_number,什麼時候給recycle_log_number 指派呢,可以由下向上遞推。
recycle_log_number 如何複用log
向上遞推 ,可以看到
CreateWAL
函數是在
SwitchMemtable
中被調用,
recycle_log_number
數值是從一個
log_recycle_files_
的deque中取出來的。
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
......
// 從log_recycle_files_ 的頭端取出一個元素作為目前可回收的log number
uint64_t recycle_log_number = 0;
if (creating_new_log && immutable_db_options_.recycle_log_file_num &&
!log_recycle_files_.empty()) {
recycle_log_number = log_recycle_files_.front();
}
......
if (creating_new_log) {
// TODO: Write buffer size passed in should be max of all CF's instead
// of mutable_cf_options.write_buffer_size.
io_s = CreateWAL(new_log_number, recycle_log_number, preallocate_block_size,
&new_log);
if (s.ok()) {
s = io_s;
}
}
而
log_recycle_files_
這個deque則是在從活躍log deque
alive_log_files_
中取的。
在
FindOnsoleteFiles
函數中需要清理一些過期檔案(log, sst, blob等),針對一些過期的log進行回收,并添加到log_recycle_files_ 雙端隊列中。
其中recycle_log_file_num 表示能夠回收的log個數
if (!alive_log_files_.empty() && !logs_.empty()) {
uint64_t min_log_number = job_context->log_number;
size_t num_alive_log_files = alive_log_files_.size();
// find newly obsoleted log files
// 從活躍log中取出沒有接受寫入資料的log,将這一部分log進行重用
// min_log_number表示目前這個log 還在被持續更新。
while (alive_log_files_.begin()->number < min_log_number) {
auto& earliest = *alive_log_files_.begin();
if (immutable_db_options_.recycle_log_file_num >
log_recycle_files_.size()) {
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"adding log %" PRIu64 " to recycle list\n",
earliest.number);
log_recycle_files_.push_back(earliest.number);
} else {
job_context->log_delete_files.push_back(earliest.number);
關于
alive_log_files_
這個變量的元素更新是在 打開db 和 SwitchMemtable 過程中進行更新的,這兩個部分會建立wal
在FindOnsoleteFiles函數中,構造好的
job_context
傳出即可。
清理WAL的調用棧如下(如果目前log被reuse,那就不會被清理了)
DBImpl::MaybeScheduleFlushOrCompaction // 排程compaction/flush
DBImpl::BGWorkFlush // 從線程池排程flush
DBImpl::BackgroundCallFlush
DBImpl::FindObsoleteFiles // 構造好我們的job_context,其中包括需要清理的sst/blob/log
DBImpl::PurgeObsoleteFiles // 執行清理
在清理函數中
PurgeObsoleteFiles
會決定是否需要keep log
bool keep = true;
switch (type) {
case kWalFile:
keep = ((number >= state.log_number) ||
(number == state.prev_log_number) ||
(log_recycle_files_set.find(number) !=
log_recycle_files_set.end()));
break;
如果發現log在log_recycle_files_set 我們之前回收的log清單中,則需要keep,也就不會執行後續的log檔案删除了。
總結
到此,我們就知道參數
opts.recycle_log_file_num
的完整作用了,回到開頭提到的現象,在開頭的配置下大并發寫rocksdb 會發現部分log檔案可能存在的時間較長,且同時存在多個log 數目。
對于第一個問題 log存在的時間較長,即是由
recycle_log_file_num
參數控制,它會不斷得複用一些過期(不接受寫入)的log,并且這一些log不會被回收。這個參數能夠提升log檔案的複用,減少對檔案中繼資料的操作,加速
SwitchMemtable
的過程。