日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

TitanDB 中使用Compaction Filter ,产生了预期之外几十倍的读I/O

發布時間:2023/11/27 生活经验 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TitanDB 中使用Compaction Filter ,产生了预期之外几十倍的读I/O 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Compaction過程中 產生大量讀I/O 的背景

項目中因大value 需求,引入了PingCap 參考Wisckey 思想實現的key-value分離存儲 titan, 使用過程中因為有用到Rocksdb本身的 CompactionFilter功能,所以就直接用TitanDB的option 傳入了compaction filter。

使用過程中,單純的通過db->Put接口寫入 就會發現磁盤上大量的讀I/O。

Ps : 相關的現象產生時的基本配置就不貼上來了,這個現象用過titan的compaction filter的同學應該都會比較清楚。

如果沒有用過,但也發現了一些異常,也可以直接向后看。

我們數據寫入量是 key:10B, value: 8K , 磁盤上的讀本身是由于compaction引起的,compaction過程中需要將選擇的sst文件中的key-value通過迭代器一個一個讀取上來做堆排序。這個過程會產生讀I/O,也就是只有Compaction 本身會有讀I/O。

問題現象是單次compaction的量也就幾十M,但磁盤上卻產生了數百M的讀I/O

更加直觀的體現就是通過命令sudo iotop,可以看到此時大量的compaction 線程產生了讀IO

問題分析

這里顯然不合理,rocksdb的日志打印出來的LOG 中總共的compaction的帶寬也就幾十M,因為在titandb的key-value分離存儲之后LSM-tree中僅僅存儲了key和key-index,所以單次compaction的過程中理論上并不會攜帶著value參與,這樣的大量I/O不太合理。

繼續向下看,從iotop的輸出中取出來一個compaction的線程ID,sudo strace -ttt -T -p 209278 抓取它的系統調用,可以看到大量的pread64系統調用

1617853714.972743 pread64(14224, "\203\250\206p\20/\0\0\0\fuid:11288154\201^\365.\0\0\n\362]\22"..., 8190, 13057621) = 12057 <0.000445>
1617853714.973241 pread64(13772, "\357\212\255y\20/\0\0\0\fuid:11288198\201^\365.\0\0\n\362]\22"..., 8190, 3267429) = 12057 <0.000013>
1617853714.973284 pread64(15591, "\343\3602\373\20/\0\0\0\fuid:11288239\201^\365.\0\0\n\362]\22"..., 8190, 3279482) = 12057 <0.000230>
...

可以看到pread64讀到的數據大小是8190B,顯然是我們寫入的value大小,這貨肯定讀了存放value的blobfile

隨機抽樣幾個fd ,也就是pread64(14224, "\20...)的第一參數,從進程的fd列表中看看它鏈接得是哪個文件ls -l /proc/xxx/fd | grep 209278

lr-x------ 1 kiwi2 kiwi2 64 Apr  8 11:49 /proc/209235/fd/10029 -> /mnt/db/14/titandb/000681.blob

果然是從blobfile中讀取的數據,到這里我們就知道為什么compaction線程會有這么多的讀,因為compaction過程中竟然讀了blob file中的value。。。陷入沉思,梳理一下titan的寫入邏輯。

  1. Key-value 都和以前rocksdb一樣,先寫入memtable
  2. 在Flush過程中形成sst文件的時候,通過titan自己的table-builder add的過程中來做區分,大于一個閾值時 分離value寫入到blobfile中,key+key-index 存放到LSM-tree 的sst文件中
  3. 后續LSM-tree繼續自己的compaction, blobfiles 則在達到觸發gc條件的時候由一個線程池的一個線程調度blobfile的過期清理

也就是titan compaction過程中理論上僅僅是sst文件中的key + key-index參與,并不會涉及blobfiles 中的value,要不然key-value分離的意義何在?帶寬還是沒有降下來。

接下來的分析就更加明了了,看看這個時候大量讀的compaction線程調用棧,直接上命令sudo pstack 209278(pstack底層也是調用gdb 執行的,不過是quiet指令執行,并不會阻塞線程),最后能看到如下調用棧

#0  0x00007faa05d93f73 in pread64 () from /lib64/libpthread.so.0
#1  0x000000000095f85e in pread (__offset=16132142, __nbytes=12057, __buf=0x3c074a000, __fd=<optimized out>)
#2  rocksdb::PosixRandomAccessFile::Read(unsigned long, unsigned long, rocksdb::Slice*, char*) const ()
#3  0x0000000000a0c0b1 in rocksdb::RandomAccessFileReader::Read(unsigned long, unsigned long, rocksdb::Slice*, char*, bool) const ()
#4  0x000000000081b197 in rocksdb::titandb::BlobFileReader::ReadRecord(rocksdb::titandb::BlobHandle const&, rocksdb::titandb::BlobRecord*, rocksdb::titandb::OwnedSlice*) ()
#5  0x000000000081ba21 in rocksdb::titandb::BlobFileReader::Get(rocksdb::ReadOptions const&, rocksdb::titandb::BlobHandle const&, rocksdb::titandb::BlobRecord*, rocksdb::PinnableSlice*) ()
#6  0x00000000008428e3 in rocksdb::titandb::BlobFileCache::Get(rocksdb::ReadOptions const&, unsigned long, unsigned long, rocksdb::titandb::BlobHandle const&, rocksdb::titandb::BlobRecord*, rocksdb::PinnableSlice*) ()
#7  0x00000000008396b8 in rocksdb::titandb::BlobStorage::Get(rocksdb::ReadOptions const&, rocksdb::titandb::BlobIndex const&, rocksdb::titandb::BlobRecord*, rocksdb::PinnableSlice*) ()
#8  0x00000000007f4a3b in rocksdb::titandb::TitanCompactionFilter::FilterV2 (this=0x3ceb05b00, level=0, key=..., value_type=<optimized out>, value=..., new_value=0x1be783cf8, skip_until=0x1be783d18)
#9  0x0000000000a2fa1a in InvokeFilterIfNeeded (skip_until=0x7fa9f786e730, need_skip=0x7fa9f786e72f, this=0x1be783b00)
#10 rocksdb::CompactionIterator::InvokeFilterIfNeeded (this=0x1be783b00, need_skip=0x7fa9f786e72f, skip_until=0x7fa9f786e730)
#11 0x0000000000a3039a in rocksdb::CompactionIterator::NextFromInput() ()
#12 0x0000000000a31c5a in rocksdb::CompactionIterator::Next (this=0x1be783b00)
#13 0x0000000000a39658 in rocksdb::CompactionJob::ProcessKeyValueCompaction(rocksdb::CompactionJob::SubcompactionState*) ()
#14 0x0000000000a3aa1c in rocksdb::CompactionJob::Run() ()
#15 0x0000000000887a5b in rocksdb::DBImpl::BackgroundCompaction(bool*, rocksdb::JobContext*, rocksdb::LogBuffer*, rocksdb::DBImpl::PrepickedCompaction*, rocksdb::Env::Priority) ()
#16 0x000000000088ab44 in rocksdb::DBImpl::BackgroundCallCompaction(rocksdb::DBImpl::PrepickedCompaction*, rocksdb::Env::Priority) ()
#17 0x000000000088b028 in rocksdb::DBImpl::BGWorkCompaction (arg=<optimized out>)
#18 0x0000000000a1437c in operator() (this=0x7fa9f7870370)
#19 rocksdb::ThreadPoolImpl::Impl::BGThread(unsigned long) ()
#20 0x0000000000a144d3 in rocksdb::ThreadPoolImpl::Impl::BGThreadWrapper (arg=<optimized out>)

這個調用棧中可以看到

#11 0x0000000000a3039a in rocksdb::CompactionIterator::NextFromInput() ()
#12 0x0000000000a31c5a in rocksdb::CompactionIterator::Next (this=0x1be783b00)
#13 0x0000000000a39658 in rocksdb::CompactionJob::ProcessKeyValueCompaction(rocksdb::CompactionJob::SubcompactionState*) ()
#14 0x0000000000a3aa1c in rocksdb::CompactionJob::Run() ()

這一些都是正常的compaction邏輯,但是再往上走就進入了compaction filter之中,使用了Titandb的filter函數,并且調用了rocksdb::titandb::BlobStorage::Get,確實,我們用戶態用了compaction filter,但不應該調用到blob的Get,好吧。。。
直接看Titan的源代碼。

Titan的Compaction Filter實現

在打開TitanDB的時候會將用戶傳入的compaction_filter作為一個子filter傳進來,并且交給titan自己的TitanCompactionFilterFactory來處理

Status TitanDBImpl::OpenImpl(const std::vector<TitanCFDescriptor>& descs,std::vector<ColumnFamilyHandle*>* handles) {......std::vector<ColumnFamilyDescriptor> base_descs;std::vector<std::shared_ptr<TitanTableFactory>> titan_table_factories;for (auto& desc : descs) {......if (cf_opts.compaction_filter != nullptr ||cf_opts.compaction_filter_factory != nullptr) {std::shared_ptr<TitanCompactionFilterFactory> titan_cf_factory =std::make_shared<TitanCompactionFilterFactory>(cf_opts.compaction_filter, cf_opts.compaction_filter_factory,this, desc.options.skip_value_in_compaction_filter, desc.name);cf_opts.compaction_filter = nullptr;cf_opts.compaction_filter_factory = titan_cf_factory;}}// Open base DB.s = DB::Open(db_options_, dbname_, base_descs, handles, &db_);\......
}

進入TitanCompactionFilterFactoryCreateCompactionFilter函數
之前介紹Rocksdb的ComapctionFilter實現的時候知道,引擎對外暴漏了這一些接口,能夠由用戶來指定自己想要過濾什么樣的key。

Rocskdb CompactionFilter實現

std::unique_ptr<CompactionFilter> CreateCompactionFilter(const CompactionFilter::Context &context) override {assert(original_filter_ != nullptr || original_filter_factory_ != nullptr);std::shared_ptr<BlobStorage> blob_storage;{MutexLock l(&titan_db_impl_->mutex_);blob_storage = titan_db_impl_->blob_file_set_->GetBlobStorage(context.column_family_id).lock();}if (blob_storage == nullptr) {assert(false);// Shouldn't be here, but ignore compaction filter when we hit error.return nullptr;}const CompactionFilter *original_filter = original_filter_;std::unique_ptr<CompactionFilter> original_filter_from_factory;if (original_filter == nullptr) {original_filter_from_factory =original_filter_factory_->CreateCompactionFilter(context);original_filter = original_filter_from_factory.get();}return std::unique_ptr<CompactionFilter>(new TitanCompactionFilter(titan_db_impl_, cf_name_, original_filter,std::move(original_filter_from_factory), blob_storage, skip_value_));
}

Factory會將TitanCompactionFilter返回,且這個filter也攜帶著用戶自定義的Filteroriginal_filter。也就是comapction 過程中會先執行TitanCompactionFilterFilterV2函數,接著看一下titandb 的FilterV2函數:

Decision FilterV2(int level, const Slice &key, ValueType value_type,const Slice &value, std::string *new_value,std::string *skip_until) const override {......BlobRecord record;PinnableSlice buffer;ReadOptions read_options;// 問題源頭s = blob_storage_->Get(read_options, blob_index, &record, &buffer);if (s.IsCorruption()) {// Could be cause by blob file beinged GC-ed, or real corruption.// TODO(yiwu): Tell the two cases apart.return Decision::kKeep;} else if (s.ok()) {// 用戶自定義的Filter邏輯auto decision = original_filter_->FilterV2(level, key, kValue, record.value, new_value, skip_until);...}
}

可以看到這里會有一個blob_storage_->Get,到此我們就知道為什么會有一個blobfile 的Get了。

因為用戶在回掉使用original_filter_->FilterV2邏輯的時候需要知道具體的value,所以Titan這里需要將blobfile中的value傳回去。

OK。。。這樣啊,那確實沒有辦法,畢竟想要擁有靈活性,代價是必不可少的。

解決

如果業務中針對compaction filter的需求是不需要value的數據,這里可以避免掉blobfile的Get,設置titan options skip_value_in_compaction_filter = true 即可。

總結

以上是生活随笔為你收集整理的TitanDB 中使用Compaction Filter ,产生了预期之外几十倍的读I/O的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。