RocksDB中内存使用

在这里,我们试图解释RocksDB如何使用内存。RocksDB中有几个组件有助于内存使用:

  1. 1.Block cache
  2. 2.Indexes and bloom filters
  3. 3.Memtables
  4. 4.Blocks pinned by iterators

我们将依次描述它们。

Block cache 块缓存

块缓存是RocksDB缓存块的地方。您可以通过设置BlockBasedTableOptions的block_cache属性来配置块缓存的大小:

  1. rocksdb::BlockBasedTableOptions table_options;
  2. table_options.block_cache = rocksdb::NewLRUCache(1 * 1024 * 1024 * 1024LL);
  3. rocksdb::Options options;
  4. options.table_factory.reset(new rocksdb::BlockBasedTableFactory(table_options));

如果在块缓存中没有找到数据块,则RocksDB使用缓冲IO从文件中读取数据块。这意味着它还使用页面缓存—它包含原始压缩块。 在某种程度上,RocksDB的缓存是两层的:块缓存和页面缓存。不直观的说,减少块缓存大小不会增加IO。所保存的内存可能会用于页面缓存,因此会缓存更多的数据。 但是,由于RocksDB需要解压从页面缓存中读取的页面,所以CPU使用量可能会增加。

要了解块缓存使用了多少内存,可以调用块缓存对象上的GetUsage()函数:

  1. table_options.block_cache->GetUsage();

在MongoRocks中,您可以通过调用来获得块缓存的大小:

  1. > db.serverStatus()["rocksdb"]["block-cache-usage"]

Indexes and filter blocks 索引和块过滤器

索引和筛选块可以是大内存用户,默认情况下它们不计入分配给块缓存的内存中。这有时会给用户带来困惑:您为块缓存分配了10GB,但是RocksDB使用了15GB的内存。 这种差异通常由索引和bloom过滤器块来解释。

下面是如何粗略计算和管理索引块和过滤块的大小:

1.对于每个数据块,我们在索引中存储三个信息:键、偏移量和大小。因此,有两种方法可以减少索引的大小。如果增加块大小,块的数量就会减少,因此索引大小也会线性减少。 默认情况下,我们的块大小是4KB,尽管我们通常在生产环境中运行16-32KB。 减少索引大小的第二种方法是减少键大小,尽管这在某些用例中可能不是一个选项。

2.计算过滤块的大小很简单。如果您为每个键配置了10位bloom过滤器(默认值为1%的误报),那么bloom过滤器的大小是number_of_keys * 10 bits。 这里有一个技巧。如果确定Get()将主要找到要查找的键,那么可以设置选项。optimize_filters_for_hits = true。 打开此选项后,我们将不会在包含90%数据库的最后一层构建bloom过滤器。因此,bloom过滤器的内存使用量将减少10倍。 但是,对于没有在数据库中找到数据的Get(),您将为其支付一个IO。

有两个选项可以配置我们在内存中放入了多少索引和过滤块:

如果将cache_index_and_filter_blocks设置为true,则索引和筛选器块将与所有其他数据块一起存储在块缓存中。 这也意味着它们可以被呼出。如果您的访问模式是非常本地的(即您有一些非常冷的键范围),那么这个设置可能是有意义的。 然而,在大多情况下,这将损害您的性能,因为您需要索引和过滤器来访问特定的文件。始终要考虑设置pin_l0_filter_and_index_blocks_in_cache来最小化性能影响。

如果cache_index_and_filter_blocks设置为false(这是默认值),则索引/过滤器块的数量由选项max_open_files控制。 如果您确定您的ulimit总是大于数据库中的文件数量,我们建议将max_open_files设置为-1,这意味着无穷大。该选项将预加载所有过滤器和索引块,不需要维护文件的LRU。将max_open_files设置为-1将获得最好的性能。

要了解索引和筛选块使用了多少内存,可以使用RocksDB的GetProperty() API:

  1. std::string out;
  2. db->GetProperty("rocksdb.estimate-table-readers-mem", &out);

在MongoRocks中,只需从mongo shell调用这个API:

  1. > db.serverStatus()["rocksdb"]["estimate-table-readers-mem"]

在 分区索引/过滤器(https://github.com/facebook/rocksdb/wiki/Partitioned-Index-Filters) 中,分区总是存储在块缓存中。可以将顶级索引配置为通过cache_index_and_filter_blocks存储在堆或块缓存中

Memtables

您可以将memtables看作内存中的写缓冲区。每个新的键值对首先写入memtable。memtable大小由选项write_buffer_size控制。 它通常不会占用太多内存,然而,memtable大小与写放大成反比—您给memtable的内存越多,写放大就越小。如果您增加了memtable大小,请确保也增加了L1大小! L1大小由max_bytes_for_level_base选项控制。

要获得当前的memtable大小,可以使用:

  1. std::string out;
  2. db->GetProperty("rocksdb.cur-size-all-mem-tables", &out);

在MongoRocks中,等价的调用是:

  1. > db.serverStatus()["rocksdb"]["cur-size-all-mem-tables"]

从5.6版本开始,可以将memtables的内存预算作为块缓存的一部分。检查写缓冲区管理器以获取信息。(https://github.com/facebook/rocksdb/wiki/Write-Buffer-Manager)

Blocks pinned by iterators 由迭代器固定的块

迭代器固定的块通常不会对总体内存使用产生多大的影响。然而,在某些情况下,当100,000个读取事务同时发生时,可能会对内存造成压力。 固定块的内存使用量很容易计算。每个迭代器为每个L0文件精确地固定一个数据块,为每个L1+级别固定一个数据块。因此,固定块的总内存使用量大约是num_iterators block_size ((num_levels-1) + num_l0_files)。 这个内存使用情况的统计信息,调用GetPinnedUsage()块缓存对象:

  1. table_options.block_cache->GetPinnedUsage();