09-leveldb性能优化

Posted anda0109

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了09-leveldb性能优化相关的知识,希望对你有一定的参考价值。

1、WriteOptions如何设置写性能才高?

在写入数据时,即调用Put和Delete接口时,我们需要传入一个WriteOptions类型的参数,例如Put接口如下:
Status Put(const WriteOptions& options, const Slice& key, const Slice& value);
WriteOptions参数该如何设置呢?我们看它的定义:

// Options that control write operations
struct LEVELDB_EXPORT WriteOptions 
  WriteOptions() = default;

  // If true, the write will be flushed from the operating system
  // buffer cache (by calling WritableFile::Sync()) before the write
  // is considered complete.  If this flag is true, writes will be
  // slower.
  //
  // If this flag is false, and the machine crashes, some recent
  // writes may be lost.  Note that if it is just the process that
  // crashes (i.e., the machine does not reboot), no writes will be
  // lost even if sync==false.
  //
  // In other words, a DB write with sync==false has similar
  // crash semantics as the "write()" system call.  A DB write
  // with sync==true has similar crash semantics to a "write()"
  // system call followed by "fsync()".
  bool sync = false;
;

WriteOptions是一个结构体,它有一个成员变量sync,这个sync的作用是什么呢?
看注释,当这个值设置为false的时候,当机器或操作系统挂掉时,最近写入的数据可能会丢失。注意是机器或操作系统挂掉,而不是仅进程挂掉。仅进程挂掉不会有任何数据丢失。当这个值设置为true的时候,无论是系统还是进程挂掉,数据都不会丢失。
这个值的作用就是每次数据写入后,是否同步刷盘,即调用fsync()。也就是数据写入wal时,调用write()后,是否紧接着调用fsync(),如果设置为true就会调用。
当sync设置为true时,写入的数据会同步刷盘,保证了数据即使在机器宕机也不会丢失。但它的代价就是性能的急剧下降。因为每次IO都进行刷盘是非常消耗性能的。
因此,在实际的业务应用中,如果追求高性能、且少量数据丢失是可以容忍的,那么果断将sync设置为false。相反,如果不那么追求性能、但对数据的安全要求非常高,那么你需要将sync设置为true。
当然也有权衡二者的做法,例如每写入100次后,将sync设置成一次true,即每写入100条数据,强制刷一次盘。这样最多丢失最近的100次写入。

2、ReadOptions如何设置读性能才高?

当我们读取数据时,需要传入一个ReadOptions类型的参数,接口如下:
Get(const ReadOptions& options, const Slice& key, std::string* value)
ReadOptions的定义如下:

// Options that control read operations
struct LEVELDB_EXPORT ReadOptions 
  ReadOptions() = default;

  // If true, all data read from underlying storage will be
  // verified against corresponding checksums.
  bool verify_checksums = false;

  // Should the data read for this iteration be cached in memory?
  // Callers may wish to set this field to false for bulk scans.
  bool fill_cache = true;

  // If "snapshot" is non-null, read as of the supplied snapshot
  // (which must belong to the DB that is being read and which must
  // not have been released).  If "snapshot" is null, use an implicit
  // snapshot of the state at the beginning of this read operation.
  const Snapshot* snapshot = nullptr;
;

成员变量verify_checksums代表读取数据时是否进行数据校验,在leveldb中是每一个block对应一个4字节的crc校验。如果设置为true,则每次从磁盘读取出一个块都会进行校验。校验数据当然会消耗CPU资源,查询性能会有一定的降低。但开启它的好处是及时识别损坏的块。
第二个变量fill_cache代表本次读取到的数据所在的块是否进行缓存。理论上,如果将块缓存,则后续的查询动作只需要在内存中进行,可以提高查询的性能。如果你的可用内存够大,block_cache设置得足够大的情况下,开启这个值会有利于提升查询性能。
第三个参数snapshot是指从指定的快照查询,这个与业务有关。与性能关系不大。

3、Compact如何设置来平衡读和写?

在leveldb源码的dbformat.h中有如下常量定义:

namespace config 
static const int kNumLevels = 7;

// Level-0 compaction is started when we hit this many files.
static const int kL0_CompactionTrigger = 4;

// Soft limit on number of level-0 files.  We slow down writes at this point.
static const int kL0_SlowdownWritesTrigger = 8;

// Maximum number of level-0 files.  We stop writes at this point.
static const int kL0_StopWritesTrigger = 12;

// Maximum level to which a new compacted memtable is pushed if it
// does not create overlap.  We try to push to level 2 to avoid the
// relatively expensive level 0=>1 compactions and to avoid some
// expensive manifest file operations.  We do not push all the way to
// the largest level since that can generate a lot of wasted disk
// space if the same key space is being repeatedly overwritten.
static const int kMaxMemCompactLevel = 2;

// Approximate gap in bytes between samples of data read during iteration.
static const int kReadBytesPeriod = 1048576;

  // namespace config

我们主要关注下面三个与compact关系较大的参数:
// 当0层文件数达到该值时触发0层的compcat
static const int kL0_CompactionTrigger = 4;

// 当0层文件数达到该值时降低写入速度.
static const int kL0_SlowdownWritesTrigger = 8;

// 当0层文件数达到该值时停止写入.
static const int kL0_StopWritesTrigger = 12;

上述三个参数实际上是为了平衡写入和compact速度而设置的。当0层文件达到4个时触发compact,当0层文件数达到8个时,说明compact速度慢于写入速度,这个时候降低写入速度;当0层文件数达到12个时,说明写入速度远大于compact速度,这个时候停止数据写入,等待将0层积压的文件compact下去后再进行写入。
这几个值的设置,最重要的是寻求平衡。
kL0_CompactionTrigger设置得越小,compact触发的频率就越高,读写放大就越严重。读写放大反过来影响业务的读写性能。
kL0_CompactionTrigger设置得越大,compact触发的频率就越低,读写放大就越不明显。但是如果业务中如果读取操作比较多,则读取数据时会有严重的读放大,因为0层文件数是Range重叠的,查询一个值时会将0层的每个文件查一遍,读性能很低。
因此,基于业务场景,如果是写多读少的场景,或者对读取性能不那么敏感,我们可以将kL0_CompactionTrigger的值设置得大一些,以提升写性能。如果是写少读多的场景,可以将kL0_CompactionTrigger的值设置得小一些,以提升读性能。

以上是关于09-leveldb性能优化的主要内容,如果未能解决你的问题,请参考以下文章

09-leveldb性能优化

UGUI性能优化

mysql性能优化分析 --- 下篇

Web 前端性能优化

js性能优化之--数据访问选取

PHP性能优化