性能 - Performance

可以通过修改定义在include/leveldb/options.h中的默认值值来对性能进行调整和优化。

Block size - 块大小

leveldb将相邻的key组合在一块儿放进同一个block中,这样的一个block是与持久化存储设备进行传输的基本单元。默认的block大小约为4096个未压缩字节。那些经常需要扫描整个数据库内容的应用可能希望增加这个值的大小。对于小的value值进行大量的单点读取的应用,想要改进性能的话可以尝试将这个值减小。在这个值小于1千字节或大于几兆字节并没有太多的好处。还要注意,压缩对于那些比较大的block更有效一些。

Compression - 压缩

每个block在被写入持久化存储器之前都会被单独压缩。由于默认的压缩方法速度非常快,并且对不可压缩的数据禁用压缩,因此压缩默认的状态是打开的。只有在极少数的情况下,应用才会完全关闭压缩,但只有在通过benchmarks能看到性能提升时才应该这样做:

leveldb::Options options;
options.compression = leveldb::kNoCompression;
... leveldb::DB::Open(options, name, ...) ....

Cache - 缓存

数据库的内容存储在文件系统的一组文件中,并且每个文件存储的都是一系列压缩过的blocks。如果options.cache的值时non-NULL的,那么那些用过的未被压缩的block的内容将被存储在缓存中。

#include "leveldb/cache.h"
leveldb::Options options;
options.cache = leveldb::NewLRUCache(100 * 1048576);  // 100MB cache
leveldb::DB* db;
leveldb::DB::Open(options, name, &db);
... use the db ...
delete db
delete options.cache;

注意,缓存里存放的时未压缩的数据,因此它的大小是应用层面的数据大小,而不是压缩后的。(压缩过的block是交给操作系统缓冲区去缓存的,或是由客户端提供的自定义的Env实现来完成的)。

当执行大批量读取操作时,应用程序可能会希望禁用高速缓存,从而不会由于大批量的数据读取操作而消耗大量的缓存。一个针对迭代器的option参数可以实现这个目的:

leveldb::ReadOptions options;
options.fill_cache = false;
leveldb::Iterator* it = db->NewIterator(options);
for (it->SeekToFirst(); it->Valid(); it->Next()) {
  ...
}

Key Layout - 键的布局方式

需要注意硬盘的传输和缓存的单位时一个block。相邻的key(跟据数据库的排序顺序)通常放置在同一个块中。因此,应用可以通过将相邻的key放置在一起进行访问、将不经常使用的key放置在单独的key值空间内的方法来提高性能。

例如,假设我们在leveldb上实现一个简单的文件系统。我们通常需要存储的数据应该是:

filename -> permission-bits, length, list of file_block_ids
file_block_id -> data

我们可以为filename添加个前缀(比如’/’),为file_block_id添加个前缀(比如’0’),这样在扫描文件元数据时就不需要去获取和缓存大量的文件内容。

Filters - 过滤器

由于leveldb的数据是按组存放在硬盘上的,所以一个Get()调用可能需要在硬盘上执行多次读取操作。可选的FilterPolicy机制可以显著的减少磁盘的读取操作量。

leveldb::Options options;
options.filter_policy = NewBloomFilterPolicy(10);
leveldb::DB* db;
leveldb::DB::Open(options, "/tmp/testdb", &db);
... use the database ...
delete db;
delete options.filter_policy;

上面的代码将基于Bloom Filter的过滤策略与数据库相关联。基于Bloom Filter的过滤依赖于在内存中每个key中保存一定量数据位(在上面的例子中NewBloomFilterPolicy的参数是10,说明每个key中增加的数据位是10位)。此过滤器将调用Get()时所需的不必要的磁盘读取操作数量减少了大约100倍。增加每个key的数据位能过更多的减少硬盘的读取操作数,但是要以更多的内存开销为代价。因此我们建议那些数据不适合在内存中存放的应用和需要做很多随机读取操作的应用设置过滤器策略。

如果你使用的是自定义的比较器,那么应该确保你使用的过滤器策略和自定义比较器相兼容。例如,在一个比较器中比较key时忽略其尾部的空格,那么NewBloomFilterPolicy就不能和这样的比较器兼容。相应的,应用也可以提供一个忽略key尾部空格的自定义过滤器策略。例如:

class CustomFilterPolicy : public leveldb::FilterPolicy {
 private:
  FilterPolicy* builtin_policy_;
 public:
  CustomFilterPolicy() : builtin_policy_(NewBloomFilterPolicy(10)) { }
  ~CustomFilterPolicy() { delete builtin_policy_; }
  const char* Name() const { return "IgnoreTrailingSpacesFilter"; }
  void CreateFilter(const Slice* keys, int n, std::string* dst) const {
    // Use builtin bloom filter code after removing trailing spaces
    std::vector<Slice> trimmed(n);
    for (int i = 0; i < n; i++) {
      trimmed[i] = RemoveTrailingSpaces(keys[i]);
    }
    return builtin_policy_->CreateFilter(&trimmed[i], n, dst);
  }
  bool KeyMayMatch(const Slice& key, const Slice& filter) const {
    // Use builtin bloom filter code after removing trailing spaces
    return builtin_policy_->KeyMayMatch(RemoveTrailingSpaces(key), filter);
  }
};

更高级的应用可以使用一系列其他的用于概括一组key的机制的过滤策略而不使用bloom filter过滤策略。更多的细节参见leveldb/filter_policy.h