閱讀606 返回首頁    go 阿裏雲 go 技術社區[雲棲]


MyRocks之memtable切換與刷盤


title: MySQL · myrocks · MyRocks之memtable切換與刷盤

author: 張遠

概述

MyRocks的memtable默認是skiplist,其大小和個數分別由參數write_buffer_size和max_write_buffer_number控製。數據寫入時先寫入active memtable, 當active memtable寫滿時,active memtable會轉化為immutable memtable. immutable memtable數據是不會變化的,最終會刷入level0的sst文件中。
屏幕快照 2017-06-16 上午6.16.58.png

memtable 內存分配

RocksDB有自己的內存分配機製,稱為Arena. Arena由固定的inline_block_和動態的blocks_組成。
inline_block_固定為2048bytes, blocks_由一係列的block組成,這些block大小一般為KBlockSize, 但從arena申請較大內存時(> KBlockSize/4)單獨分配一個所申請大小的block. KBlockSize由參數arena_block_size指定,arena_block_size 不指定時默認為write_buffer_size的1/8.

屏幕快照 2017-06-16 上午6.56.39.png

這裏有兩個重要的概念

  • blocks_memory_
    Arena當前已分配的內存
  • alloc_bytes_remaining_
    Arena當前block已分配但未使用的內存,注意不是整個Arena已分配而未使用的內存

RocksDB在實際使用內存中用的是ConcurrentArena, 它是在Arena的基礎上封裝,是線程安全的。
同時ConcurrentArena為了提高並發對內存進行了分片,分片數由cpu個數決定,例如cpu核數為24, 則分片數為32,以下是分片的算法

// find a power of two >= num_cpus and >= 8
  auto num_cpus = std::thread::hardware_concurrency();
  index_mask_ = 7;
  while (index_mask_ + 1 < num_cpus) {
    index_mask_ = index_mask_ * 2 + 1;
  }

  shards_.reset(new Shard[index_mask_ + 1]);

每個分片都有已分配但未使用的內存, 分片越多浪費的內存越多。

一個有趣的例子

測試環境:CPU核數64,write_buffer_size=1G, arena_block_size=0
根據前麵的算法,CPU核數64, 內存分片數為64, arena_block_size 默認為write_buffer_size的1/8,對齊後是131072000

我們用1200個連接進行並發插入,這樣能夠充分使用內存分片數
這是測試某個瞬間取得的內存數據

allocated_memory:1179650048
AllocatedAndUnused:1172297392
write_buffer_size:1048576000
BlockSize:131072000

注意AllocatedAndUnused和allocated_memory是如此的接近,也就是說存在**巨大的內存浪費**。然而這不是最嚴重的,更嚴重的是這種情況導致memtable的切換,後麵會進行分析。

memtable 切換

memtable 發生切換的條件有
1) memtable內存超過write_buffer_size會切換
2) WAL日誌滿,WAL日誌超過rocksdb_max_total_wal_size,會從所有的colomn family中找出含有最老日誌(the earliest log containing a prepared section)的memtable進行切換,詳見HandleWALFull
3) Buffer滿,全局的write buffer超過rocksdb_db_write_buffer_size時,會從所有的colomn family中找出最先創建的memtable進行切換,詳見HandleWriteBufferFull
4) flush memtable前會切換memtable, 下節會介紹

下麵詳細介紹memtable滿切換

  • memtable 滿切換

memtable內存超過write_buffer_size會切換,由於arena的內存使用,memtable控製內存使用的算法更加精細,切換條件從源碼中很容易理解

bool MemTable::ShouldFlushNow() const {
  // This constant variable can be interpreted as: if we still have more than
  // "kAllowOverAllocationRatio * kArenaBlockSize" space left, we'd try to over
  // allocate one more block.
  const double kAllowOverAllocationRatio = 0.6;

  // If arena still have room for new block allocation, we can safely say it
  // shouldn't flush.
  auto allocated_memory = table_->ApproximateMemoryUsage() +
                          range_del_table_->ApproximateMemoryUsage() +
                          arena_.MemoryAllocatedBytes();

  // if we can still allocate one more block without exceeding the
  // over-allocation ratio, then we should not flush.
  if (allocated_memory + kArenaBlockSize <
      moptions_.write_buffer_size +
      kArenaBlockSize * kAllowOverAllocationRatio) {
    return false;
  }

  // if user keeps adding entries that exceeds moptions.write_buffer_size,
  // we need to flush earlier even though we still have much available
  // memory left.
  if (allocated_memory > moptions_.write_buffer_size +
      kArenaBlockSize * kAllowOverAllocationRatio) {
    return true;
  }

 return arena_.AllocatedAndUnused() < kArenaBlockSize / 4;
}

而上一節舉出的例子正好符合切換的條件,正如前麵所說的,內存都分配好了,還沒來得及使用就發生切換了,白忙活了一場。

這裏的現象是雖然write_buffer_size是1G,但最後刷到level0的sst都遠遠小於1G。

那麼如何避免這種情況呢

  • 減少內存分片數,不建議
  • 調小arena_block_size, 親測可用

這裏有一個原則是arena_block_size*內存分片數應該小於write_buffer_size

  • memtable 切換實現

    ** NewWritableFile //創建日誌文件
    ** ConstructNewMemtable //創建memtable
    ** cfd->imm()->Add(cfd->mem(), &context->memtables_to_free_); //設置immutable
    ** cfd->SetMemtable(new_mem); //設置新的memtable

flush memtable

immutable memtable會不斷flush到level0的SST文件中

觸發flush的條件有

  • WAL日誌滿,WAL日誌超過rocksdb_max_total_wal_size,會從所有的colomn family中找出含有最老日誌(the earliest log containing a prepared section)的column family進行flush,詳見HandleWALFull
  • Buffer滿,全局的write buffer超過rocksdb_db_write_buffer_size時,會從所有的colomn family中找出最先創建的memtable的column family進行flush,詳見HandleWriteBufferFull
  • 手動設置參數force_flush_memtable_now/rocksdb_force_flush_memtable_and_lzero_now時
  • CompactRange時
  • 創建checkpoint時
  • shutdown時avoid_flush_during_shutdown=0會flush所有memtable

other

rocksdb中設置max_background_flushes=-1可以禁止flush,而MyRocks中rocksdb_max_background_flushes最小值限製為0. 因此,MyRocks若要禁止flush需放開此限製。

最後更新:2017-06-20 10:01:52

  上一篇:go  技術漫談:為何KPI毀了索尼,而OKR卻成就了穀歌?
  下一篇:go  linux kernel 為什麼需要initrd