騰訊工程師教你玩轉 RocksDB


歡迎大家前往雲+社區,獲取更多騰訊海量技術實踐干貨哦~

作者:騰訊雲數據庫內核團隊

原文標題:【騰訊雲CDB】教你玩轉MyRocks/RocksDB—STATISTICS與后台線程篇

0. Intro

在facebook的MySQL版本(以下稱為MyRocks)中,RocksDB是可選的存儲引擎。相比於InnoDB引擎,RocksDB的一個重要的優勢是它使用更少的磁盤空間。在生產系統中,特別是用戶數在億級以上的互聯網應用,磁盤空間是其中比較大的成本之一,而能夠使用更少的磁盤空間的RocksDB無疑是具有吸引力的。然而在生產系統中使用新的存儲引擎自然有它的潛在風險,除了通過外部的各種benchmark工具測試得到各種性能數據,全方位的內部指標可以幫助我們真正了解數據庫內部正在發生的事情,對於性能調優和開發都具有指導意義。而MyRocks通過SHOW ENGINE ROCKSDB STATUS和多個INFORMATION_SCHEMA表等方式提供了較為全面的內部指標。

本文將介紹SHOW ENGINE ROCKSDB STATUS中關於STATISTICS統計值與后台線程的實現原理。在了解實現原理的基礎上,便可以較容易地通過擴展功能使它更好地為我們服務。

調用SHOW ENGINE ROCKSDB STATUS指令會返回多行數據,其中包括:

  • STATISTICS:RocksDB引擎所有線程的所有操作的各類count/time的累加,比如rocksdb.block.cache.hit和rocksdb.db.write.micros。
  • BG_THREADS: 后台線程的狀態。
  • DBSTATS: 數據庫操作的統計。
  • CF_COMPACTION: 各個Column family進行compaction的相關指標統計。
  • MEMORY_STATS: 內存使用情況。

調用SHOW ENGINE ROCKSDB STATUS會返回若干行數據,然而這些數據並非事先存儲於某個表格中,而是通過調用位於rocksdb/ha_rocksdb.cc文件中的rocksdb_show_status函數將內存中對應的數值進行規整返回給用戶。

1. STATISTICS

根據RocksDB官方相關文檔介紹STATISTICS,開啟STATISTICS會增加增加5%-10%額外開銷。

STATISTICS統計值記錄着RocksDB引擎所有線程的所有操作的各類count/time的累加。RocksDB引擎在它的各類操作如Put/Get/Delete中的代碼都設立了很多埋點。

以函數GetEntryFromCache為例,它的作用是返回可用的block cache。特別地,可以看到statistics是GetEntryFromCache和block_cache->Lookup的一個參數。沒錯,就是靠着statistics這個參數它到處收集數據。 當有可用的block cache時,調用了三次RecordTick為其中三個統計值增加計數;沒有可用的block cache,同樣也為BLOCK_CACHE_MISS和block_cache_miss_ticker增加計數。

Cache::Handle* GetEntryFromCache(Cache* block_cache, const Slice& key,
                                 Tickers block_cache_miss_ticker,
                                 Tickers block_cache_hit_ticker,
                                 Statistics* statistics) {
  auto cache_handle = block_cache->Lookup(key, statistics);
  if (cache_handle != nullptr) {
    PERF_COUNTER_ADD(block_cache_hit_count, 1);
    // overall cache hit
    RecordTick(statistics, BLOCK_CACHE_HIT);
    // total bytes read from cache
    RecordTick(statistics, BLOCK_CACHE_BYTES_READ,
               block_cache->GetUsage(cache_handle));
    // block-type specific cache hit
    RecordTick(statistics, block_cache_hit_ticker);
  } else {
    // overall cache miss
    RecordTick(statistics, BLOCK_CACHE_MISS);
    // block-type specific cache miss
    RecordTick(statistics, block_cache_miss_ticker);
  }

  return cache_handle;
}

1.1 RocksDB的STATISTICS接口

使用STATISTICS的方法也很簡單。

它的頭文件位於:

include/rocksdb/statistics.h
monitoring/statistics.h

使用方法:

Options options;
options.statistics = rocksdb::CreateDBStatistics();

可選統計級別:

  • kExceptDetailedTimers: 除去mutex等待和壓縮的計時
  • kExceptTimeForMutex: 除去mutex等待的計時
  • kAll: 所有

數據統計類型分成兩種:

  • ticker:計數,類型是64位無符號整型。用於度量counters (e.g. “rocksdb.block.cache.hit”), cumulative bytes (e.g. “rocksdb.bytes.written”) 或者 time (e.g. “rocksdb.l0.slowdown.micros”)。
  • histogram:統計數據的統計分布,包括最大值、最小值、平均值、中位數、標准差。

統計函數的接口:

  • MeasureTime:函數名有歧義。實際上是把value記錄到histogram中。
  • RecordTick:累加ticker。

獲取結果的接口:

  • Statistics::getTickerCount:指定ticker type獲得count。
  • Statistics::histogramData:指定Histograms type,返回一個HistogramData結構體,成員是統計值,包括最大值、最小值、平均值、中位數、標准差。
  • Statistics::getHistogramString:指定Histograms type,返回直方圖可讀的字符串。
  • Statistics::ToString():返回可讀的字符串,包括所有的ticker和histogram。

1.2 RocksDB的STATISTICS實現

RocksDB實現了StatisticsImpl類,繼承了Statistics的接口。

主要接口:

  • getTickerCount
  • histogramData
  • getHistogramString
  • getAndResetTickerCount
  • recordTick
  • measureTime
  • ToString

成員變量:

  • TickerInfo tickers_[INTERNAL_TICKER_ENUM_MAX];
  • HistogramInfo histograms_[INTERNAL_HISTOGRAM_ENUM_MAX];

這里的TickerInfo和HistogramInfo類型的數據結構是相似的:一個線程局部的counter或者time;加上一個非線程局部的統計值用來累加counter或者time。

TickerInfo類型包含兩個參數:

ThreadLocalPtr類型(真實類型ThreadTickerInfo)的thread_value,包含:

  • 整型類型的value
  • 指向merged_sum的指針
  • 整型類型的merged_sum
  • HistogreamInfo類型包含兩個參數:

ThreadLocalPtr類型(真實類型ThreadHistogramInfo)的thread_value,包含:

  • HistogramImpl類型的value
  • 指向merged_hist的指針
  • 指向merge_lock的指針
  • HistogramImpl類型的merged_hist
  • Mutex類型的merge_lock

事實上,STATISTICS相關實現是比較巧妙的,也是使用STATISTICS僅增加5%-10%的關鍵。為了避免線程間共享數據導致CPU的cache頻繁失效,merged_sum和merged_hist初始化時都是空的,而且當且僅當線程退出時,才調用mergeThreadValue函數將TickerInfo和HistogreamInfo中的線程局部變量累加到merged_sum和merged_hist。

1.3 MyRocks的使用

MyRocks使用了RocksDB提供的接口進行數據統計。通過聲明了變量rocksdb_stats,並且隨着RocksDB引擎啟動時通過rocksdb_init_func函數進行初始化。

rocksdb_stats = rocksdb::CreateDBStatistics();
rocksdb_db_options->statistics = rocksdb_stats;

除了使用所有RocksDB引擎層的統計,MyRocks還通過定義了

commit_latency_stats = new rocksdb::HistogramImpl();

在rocksdb_commit_by_xid和rocksdb_commit兩個函數中通過計時的方式,統計了每一次commit所花費的時間。

rocksdb::StopWatchNano timer(rocksdb::Env::Default(), true);
...
commit_latency_stats->Add(timer.ElapsedNanos() / 1000);

在rocksdb_show_status函數中,輸出Statistics統計的過程如下:

  1. 如果定義rocksdb_stats,則調用rocksdb_stats->ToString()將統計值轉化為可讀的字符串;
  2. commit_latency_stats是直方圖的類型,輸出對應的50%, 95%, 99%, 100%四個位點的對應的值。
  3. 假如定義了is-write-stopped或者actual-delayed-write-rate等Property變量,同樣會將它們輸出。

2 后台線程

通過調用SHOW ENGINE ROCKSDB STATUS可以得到與BG_THREADS相關結果,它的輸出結果類似於:

Type: BG_THREADS
Name: 140173379593984
Status:
thread_type: Low Pri##
cf_name: default
operation_type: Compaction
operation_stage: CompactionJob::ProcessKeyValueCompaction
elapsed_time_ms: 6172.244 ms
BaseInputLevel: 0
BytesRead: 992806363
BytesWritten: 992071408
IsDeletion: 0
IsManual: 0
IsTrivialMove: 0
JobID: 1936
OutputLevel: 5
TotalInputBytes: 1586832446
state_type:

可以看到較多的信息量:這個線程正在進行Compaction,處於CompactionJob::ProcessKeyValueCompaction階段,已經耗時6172.244 ms,讀取的字節數為992806363,寫出的字節數為992071408。然而並不包括可能感興趣的正在進行Compaction的源文件和目標文件等信息。正如文章開頭提到的,了解實現原理能夠使我們更好地進行擴展。

2.1 thread status的接口與實現

MyRocks中的SHOW ENGINE ROCKSDB STATUS指令展示BG_THREAD的機制使用了RocksDB中關於thread status的接口。

它的頭文件位於:

include/rocksdb/env.h
include/rocksdb/thread_status.h
util/thread_operation.h
monitoring/thread_status_updater.h
monitoring/thread_status_util.h

關鍵類:

ThreadStatusUpdater:存儲了各自后台線程的狀態和所有后台線程狀態的指針。 ThreadStatusUtil:該類只有靜態變量和靜態方法,推薦通過該類的方法去更新ThreadStatusUpdater中的狀態。

使用方法:

  • 將該線程的統計加入ThreadStatusUpdater:調用ThreadStatusUtil::RegisterThread
  • 將該線程的統計從ThreadStatusUpdater刪除:調用ThreadStatusUtil::UnregisterThread
  • 其他修改thread status的函數:見monitoring/thread_status_util.h

通過調用env的GetThreadList()函數可以獲得當前后台線程的狀態,狀態的狀態值存放於一個vector中。將其中的內容展現出來,類似於下圖:

 

從代碼中可以看到,實現thread status的目的展示flush和compaction的運行狀態。當然,我們也可以將用戶線程的狀態存儲到thread status,通過調用SHOW ENGINE ROCKSDB STATUS指令展示。

特別地,可以看到compaction特有的狀態值有:

enum CompactionPropertyType : int {
    COMPACTION_JOB_ID = 0,
    COMPACTION_INPUT_OUTPUT_LEVEL,
    COMPACTION_PROP_FLAGS,
    COMPACTION_TOTAL_INPUT_BYTES,
    COMPACTION_BYTES_READ,
    COMPACTION_BYTES_WRITTEN,
    NUM_COMPACTION_PROPERTIES
  };

flush特有的狀態值有:

  enum FlushPropertyType : int {
    FLUSH_JOB_ID = 0,
    FLUSH_BYTES_MEMTABLES,
    FLUSH_BYTES_WRITTEN,
    NUM_FLUSH_PROPERTIES
  };

2.2 MyRocks/RocksDB的使用

在RocksDB的線程池實現中,每一個啟動的后台線程都會通過調用ThreadStatusUtil::RegisterThread加入被觀測的后台線程的集合中。

ThreadPoolImpl::Impl::StartBGThreads-->BGThreadWrapper-->ThreadStatusUtil::RegisterThread

在rocksdb_show_status函數中,輸出BG_THREAD的過程如下:

  1. 通過調用GetThreadList(&thread_list)獲得所有后台線程的ThreadStatus的集合。
  2. 通過遍歷ThreadStatus的集合將每一個后台線程的狀態依次輸出。

3. 小結

本文章介紹了SHOW ENGINE ROCKSDB STATUS指令中關於STATISTICS與BG_THREAD的相關內容。

相關閱讀

MySQL 內核深度優化

【騰訊雲CDB】深入解析MySQL binlog

【騰訊雲CDB】源碼分析 · MySQL binlog組提交和Multi-Threaded-Slave


此文已由作者授權騰訊雲+技術社區發布,轉載請注明文章出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM