RocksDb的基本操作:Basic Operation
读流程
写流程
Opening A Database
Writes
Atomic Updates
- 多个更新操作通过
WriteBatch
可以保证原子性。WriteBatch
中操作会按照顺序应用到db中。 把多个单独的修改放入WriteBatch
也可以提高bulk updates速度。
#include "rocksdb/write_batch.h"
...
std::string value;
rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key1, &value);
if (s.ok()) {
rocksdb::WriteBatch batch;
batch.Delete(key1);
batch.Put(key2, value);
s = db->Write(rocksdb::WriteOptions(), &batch);
}
Synchronous Writes
- 默认rocksdb的每个write都是异步:把进程中write push到操作系统内存后就返回。而从操作系统内存到底层存储的传输过程是异步的。如果打开
sync
选项,则write操作直到写数据都已经被push到存储层后才返回。(在posix 系统中,这种方式是通过在write操作之前调用fsync(...)
orfdatasync(...)
ormsync(..., MS_SYNC)
方法实现的)
rocksdb::WriteOptions write_options;
write_options.sync = true;
db->Put(write_options, ...);
Non-sync Writes
- 如果是非同步写(异步),rocksdb只是在操作系统 buffer或内部buffer(如果设置了options.manual_wal_flush = true)缓存WAL write。比同步写快。
- 非同步写坏处: 机器crash后,会导致最新的一些updates操作会丢失。注意,如果只是rocksdb进程的crash不会导致数据丢失,即使
sync
为false,因为一个udpate被认为执行完成时,其进程中的内存数据已经被push到操作系统内存中了。
Comparators
- rocksdb的key默认按照字典序排序bytes。注意:cpu的字节序时采用小端存储:低地址存储低字节,高地址存储高字节,所以int64的变量内容存入key,如果不进行大端转换,存储到roksdb的key中也是小端存储,这样排序并不满足数值大小规则。
- 可以通过实现
rocksdb::Comparator
接口来自定义排序规则。如果此时也提供了Filter,则需要保证Filter规则和比较规则兼容
class TwoPartComparator : public rocksdb::Comparator {
public:
// Three-way comparison function:
// if a < b: negative result
// if a > b: positive result
// else: zero result
int Compare(const rocksdb::Slice& a, const rocksdb::Slice& b) const {
// key中包含两个部分,只按照第一部分进行排序
int a1, a2, b1, b2;
ParseKey(a, &a1, &a2);
ParseKey(b, &b1, &b2);
if (a1 < b1) return -1;
if (a1 > b1) return +1;
return 0;
}
// Ignore the following methods for now:
const char* Name() const { return "TwoPartComparator"; }
void FindShortestSeparator(std::string*, const rocksdb::Slice&) const { }
void FindShortSuccessor(std::string*) const { }
};
// use
TwoPartComparator cmp;
rocksdb::DB* db;
rocksdb::Options options;
options.create_if_missing = true;
options.comparator = &cmp;
rocksdb::Status status = rocksdb::DB::Open(options, "/tmp/testdb", &db);
Backwards compatibility
- 如果
comparator
的name确定了,那么以后再次打开db时必须使用相同的name的comparator,否则打开失败。 - 如果key的format后期可能会变化,那么为了兼容,在设计之初,可以给每个key预留一个字节作为version,这样如果有新的key fromat,那么新key format的version加一,那么可以不用改变comparator的name,同时comparator中根据version采用不同的比较规则。
Block size(读写传输单元)
- rocksdb在和持久化存储(应该是sstfile)间传输数据时(读写操作),rocksdb会将key排序相近的数据组成一个block为单位进行传输,默认block size是4096个未压缩字节。
- db的内容存储在sstfiles中,每个sstfile包含多个有序的压缩的blocks.
- 如果应用的bulk scan操作多的话,调大该值,这样可以使一个block中都是scan需要的值,如果大多数都是单次的value读操作,就调小该值。但是不要低于1k, 也不要大于几M,否则效果不明显。
- 有更大的block size时,使用compression(压缩)效率更高。
-
Options::block_size
选项来设置该参数。
Write buffer
-
Options::write_buffer_size
设置内存中write buffer大小,达到大小后,会将buffer写到磁盘上sstfile中。默认64M. 该值越大,内存利用越高,而且再次打开db时,将要需要更久的恢复时间。 -
Options::max_write_buffer_number
: 设置内存中最多存在的write buffer数量,默认2两个buffer,当一个write buffer正在flush到L0中时,新的写操作写入另一个write buffer中。flush操作在Thread Pool中被执行。 -
Options::min_write_buffer_number_to_merge
:buffers最小合并数量,在把write buffer写入文件前,会对多个buffers合并(merge),以减少写数据量,因为多个buffer中可能会存在重复的key,这样会删除旧的,只保留新的写入。默认为1, 如果是1,那么意味着,不合并buffer,每个buffer都会被在磁盘上写成一个单独的文件。(L0层)
Compression(压缩block的算法)
- 每个block在被写入持久化存储中前都会被压缩。压缩默认开启,而且对于uncompressible data自动关闭。(文件中的Filter block不会被压缩, data blocks和index blocks会被压缩)。
-
Options.compression
控制前n-1个level中使用的压缩算法,默认,Snappy,但是LZ4更推荐,都是lightweight压缩算法,在space和cpu使用方面有一个很好的平衡。 -
Options.compression_per_level[index]
: 为每个level配置不同压缩算法,覆盖compression的配置,也可以取消压缩 -
options.bottommost_compression
: 设置最后一层level压缩算法,因为包含更多数据,所以推荐采用heavy-weight算法ZSTD,或Zlib。压缩空间更小cpu使用更高。
rocksdb::Options options;
// 关闭压缩
options.compression = rocksdb::kNoCompression;
... rocksdb::DB::Open(options, name, ...) ....
压缩算法,得确保机器上拥有该压缩算法包
rocksdb::kNoCompression , rocksdb::kSnappyCompression ,
rocksdb::kLZ4Compression , rocksdb::kLZ4HCCompression ,
rocksdb::kZSTD , rocksdb::kZlibCompression ,
rocksdb::kBZip2Compression
- 更多参数配置:
Cache (缓存频繁访问的uncopressed block)
- db的内容存储在sstfiles中,每个sstfile包含多个有序的压缩的blocks.
-
options.block_cache
不为空,则会被用来cache uncompressed block的数据(比如filter block?或者从压缩block读出来解压完的block?)。 - 使用OS的file cache(即 Page cache)来缓存compressed data。
#include "rocksdb/cache.h"
rocksdb::BlockBasedTableOptions table_options;
table_options.block_cache = rocksdb::NewLRUCache(100 * 1048576); // 100MB uncompressed cache
rocksdb::Options options;
options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options));
rocksdb::DB* db;
rocksdb::DB::Open(options, name, &db);
... use the db ...
delete db
- 当执行bulk read时,可以关掉cache,不然bulk read中内容会替换掉cached中大部分内容, 在每个iterator option中可以配置该项。
rocksdb::ReadOptions options;
options.fill_cache = false;
rocksdb::Iterator* it = db->NewIterator(options);
for (it->SeekToFirst(); it->Valid(); it->Next()) {
...
}
- See Block Cache for more details.
Key Layout
- 因为磁盘传输和cache的最小单位都是block,所以排序相近的key通常都待在相同的block中,为此,为了提高性能,可以把经常一起访问的key放在一起,不相关的放在不同block中,可以通过给key设置前缀来实现。
- 比如, 基于rocksdb实现的文件系统,key/value的类型有如下两种,为了提升方便读取,可以为filename和file_block_id两个key加上不同的一个字符前缀"/"和“0”来实现。
filename -> permission-bits, length, list of file_block_ids
file_block_id -> data
Filters (提升读取效率)
- Filters,See rocksdb/filter_policy.h for detail.
- 由于rocksdb采用LSMT的数据组织方式,一次
Get
操作可能会涉及到多次的io操作才能读到需要的数据,为了减少io次数,提供了FilterPolicy
机制。FilterPolicy对Range scan(Iterator)操作没有用。 -
BlockBasedTableOptions.filter_policy
选项如果不为空,则使用该filter,可以使用内置的Bloom Filter,也可以继承FilterPolicy自定义策略。
rocksdb::Options options;
rocksdb::BlockBasedTableOptions bbto;
bbto.filter_policy.reset(rocksdb::NewBloomFilterPolicy(
10 /* bits_per_key 为每个key消耗的内存:10个bit,值越大,越占用内存,精度越高*/,
false /* use_block_based_builder*/));
options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(bbto));
rocksdb::DB* db;
rocksdb::DB::Open(options, "/tmp/testdb", &db);
... use the database ...
delete db;
delete options.filter_policy;
- 如果使用了自定义的key comparator,那么你的filter policy需要考虑和你的comparator兼容,此时可能需要自定义FilterPolicy。比如,你的key排序规则只考虑key的前两个数值,对于key后面的内容忽略掉,不参与比较,那么,此时你的FilterPolicy需要定制也忽略key中剩余部分的值。如下:
class CustomFilterPolicy : public rocksdb::FilterPolicy {
private:
FilterPolicy* builtin_policy_;
public:
CustomFilterPolicy() : builtin_policy_(NewBloomFilter(10, false)) { }
~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 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);
}
};
Checksums(在多副本中通过验证可以做到提前备份)
-
rocksdb中的数据(metadata和blocks)都存有checksum(校验和),提供了两个方法来验证checksum是否正确:如果数据不完成,得到的checksum和已有的checksum就不一致。
-
ReadOptions::verify_checksums
, 默认打开,每次读操作都会对当前读的数据所在metat或blocks进行checksums验证。 -
Options::paranoid_checks
, 默认打开, 如果发现database的某个部分已经corrupted,那么在open db时或者后续的其它database 操作时就会产生一个error。比上面选项覆盖数据量要大,因为即使你读取的数据blocks是完整的,但是发现其它blocks事不完整的,那么此时也会报错。
-
DB::VerifyChecksum()
: 手动触发校验和验证,对所有的数据进行(metadata, data blocks),耗时长,目前只支持BlockBasedTable format格式组织的数据。如果事分布式多副本数据库,可以通过该方法来验证某个副本是否完整,如果不完整,则提前创建要给副本。
Compaction
- 目的:删除过期数据,提高read效率
- Compaction.
Approximate Sizes(得到key占用文件存储空间)
-
GetApproximateSizes
方法可以得到一个key或多个key在文件系统中占用得存储空间(单位bytes)。
rocksdb::Range ranges[2];
ranges[0] = rocksdb::Range("a", "c");
ranges[1] = rocksdb::Range("x", "z");
uint64_t sizes[2];
db->GetApproximateSizes(ranges, 2, sizes);
- sizes[0]代表key range[a...c)占用文件系统得存储空间,sizes[1] 代表key range[x...z)。
Environment(高级,自定义该接口可改变rocksdb对文件操作或者系统调用方式)
- rocksdb的实现中对所有文件操作(file operations)或者其它系统调用(system calls)都是通过
rocksdb::Env
对象来完成的。 - 复杂的客户端如果想提供自己的
Env
实现来获得更灵活的控制,那么可以通过继承Env来实现。比如:应用想在file IO paths上引入人工干预的延迟,一次来限制rocksdb对系统上其它应用的影响。
class SlowEnv : public rocksdb::Env {
.. implementation of the Env interface ...
};
SlowEnv env;
rocksdb::Options options;
options.env = &env;
Status s = rocksdb::DB::Open(options, ...);
Porting
- rocksdb可以移动到新的平台用户通过实现不同接口,更多看文档
Managebility(通过收集统计信息来更好的tune应用)
Options::table_properties_collectors
orOptions::statistics
用来收集使用信息。建议把统计信息输出到其它监控系统中来降低自身应用的负担。也可以对单次请求做统计:Perf Context and IO Stats Context.
refer to rocksdb/table_properties.h and rocksdb/statistics.h
Statistics
Purging WAL files(WAL日志删除规则)
- 默认,当wal日志不在需要时就会被删除。但是用户可以通过TTL和大小限制配置项来archive(归档)日志,然后延迟删除他们。
-
Options::WAL_ttl_seconds
andOptions::WAL_size_limit_MB.
- 如果都为0,那么不在需要时立即删除。
- ttl = 0, size != 0,那么每隔10分钟检查wal files大小,如果超过 size limit,则从最开始位置删除直到size limit大小。
- ttl!=0, size = 0, 那么每隔 ttl/ 2秒检查wal files,大于ttl的files将被删除。
- 如果都不为0,每隔10分钟检查,两者都检查,ttl优先,满足任何一个都删除。