跳转至

RocksDB-FAQ

问: 如果我的进程 crash 了,我的数据库数据会受影响吗?

答:不会,但是如果你没有开启 WAL 没有刷入到存储介质的 memtable 数据可能会丢失。

问: 如果我的机器 crash 了,RocksDB 能保证数据的完整吗?

答:数据在你调用一个带 sync 的写请求的时候会被写入磁盘(使用 WriteOptions.sync=true 的写请求),或者你可以等待 memtable 被刷入存储的时候。

问:RocksDB 会抛异常嘛?

答:不,出错的时候 RocksDB 会返回一个 rocksdb::Status,用于指明错误。然后 RocksDB 本身也不捕捉来自 STL 库和其他依赖的异常,所以如果内存不够,你可能会遇到 std::bad_malloc 异常,或者类似的场景下的类似的异常。

问: 如何获取 RocksDB 里面存储的键值的数量?

答:使用 GetIntProperty(cf_handle, “rocksdb.estimate-num-keys") 可以获得存储在一个列族里的键的大概数量。GetAggregatedIntProperty(“rocksdb.estimate-num-keys", &num_keys) 可以获得整个 RocksDB 数据库的键数量。

问: 为什么 GetIntProperty 只能返回 RocksDB 总键数的估算值?

答:在像 RocksDB 这种 LSM 数据库里获得 key 的总数量是一个非常有挑战性的问题,因为他们总是存储有一些重复的 key 和一些被删除的项目,所以如果你需要精确数量,你需要进行一次全压缩。另外,如果 RocksDB 的数据库使用了合并操作符,这个估算的值将更加不准确。

问: 哪些基本操作,Put(),Write(),NewIterator(),是线程安全的吗

答:是的

问: 我可以从多个进程同时写 RocksDB 吗?

答:不可以。然而,你可以在多个进程中用只读模式打开 RocksDB。

问:RocksDB 支持多进程读吗?

答:RocksDB 支持多个进程以只读模式打开 rocksDB,可以通过使用 DB::OpenForReadOnly() 打开数据库。

问: 最大支持的 key 和 value 的大小是多少

答:RocksDB 不是针对大 key 设计的。推荐的最大 key value 大小分别为 8MB 和 3GB

问: 我可以在 HDFS 上跑 RocksDB 吗?

答:可以,使用 NewHdfsEnv() 接口返回的 Env,RocksDB 就可以把数据存储到 HDFS 上了。然而 HDFS 上目前无法支持文件锁。

问: 我可以保存一个 snapshot,然后回滚 rocksDB 到这个状态吗?

答:可以,请使用 BackupEngine 或者 Checkpoints 接口

问: 如何快速将数据载入 rocksdb

答:其中一种方法是直接往 RocksDB 里面插入数据:

  • 使用单一写线程,然后按顺序插入
  • 将数百个键值在一个写请求插入
  • 使用 vector memtable
  • 确保 options.max_background_flushes 至少为 4
  • 开始插入数据前,关闭自动压缩,将 options.level0_file_num_compaction_trigger, options.level0_slowdown_writes_trigger and options.level0_stop_writes_trigger 设置到非常大的值。插入完成后,开始一次手动压缩。

3-5 条可以通过调用 Options::PrepareForBulkLoad() 一次完成。

如果你可以离线处理数据,有一个更加快的方法:你可以对数据进行排序,并行生成没有交集的 SST 文件,然后批量加载这些 SST 文件接口。参考 创建以及导入SST文件

问: RocksJava 已经支持全部功能了嘛?

答:我们还在开发 RocksJava。当然,如果你发现某些功能缺失,你也可以主动提交 PR。

问: 谁在用 RocksDB

答:参考 Users

问: 如何正确删除一个 DB?我可以直接对活动的 DB 调用 DestoryDB 吗?

答:先调用 close 然后调用 destory 才是正确的方法。对一个活动的 DB 调用 DestoryDB 会导致未定义行为。

问: 调用 DestoryDB 和直接删除 DB 所在的文件夹有什么差别?

答:如果你的数据存放在多个文件夹,DestoryDB 会处理好这些文件夹。一个 DB 可以通过配置 DBOptions::db_paths, DBOptions::db_log_dir, 和 DBOptions::wal_dir 以将数据存储在不同的目录。

问:BackupableDB 会创建一个指定时间的 snapshot 吗?

答:调用 CreateNewBackup 的时候,如果 BackupOptions::backup_log_files = true 或者 flush_before_backup 为 true,就会创建。

问: 备份进程会影响到其他数据库访问吗?

答:不会,你可以在备份的同时读写数据库。

问: 有什么更好的办法把 map-reduce 生成的数据导入到 rocksDB 吗?

答:使用 SstFileWriter,它可以让你直接创建 RocksDB 的 SST 文件,然后直接把他们加入到数据库。然而,如果你把 SST 文件加入到已有的 RocksDB 数据库,那么这些键不能跟已有的键值有交集。参看 创建以及导入SST文件

问: 对不同的列族配置不同的前缀提取器安全吗?

答:安全。

问:RocksDB 编译需要的最小 gcc 版本是多少?

答:可以使用 4.7。但是推荐 4.8 及以上的。

问: 如何配置 RocksDB 以使用多个磁盘?

答:你可以在多个磁盘上创建一个文件系统(ext3,xfs,等)。然后你就可以在这个单一文件系统上跑 RocksDB 了。使用多个磁盘的一些 tips: - 如果使用 RAID,请使用大的 stripe size(64kb 太小,推荐使用 1MB) - 考虑把 ColumnFamilyOptions::compaction_readahead_size 设置到大于 2MB 以打开压缩预读 - 如果主要的工作压力是写,可以增加压缩线程以保持磁盘满负载工作。 - 考虑为压缩打开一步写

问: 直接拷贝一个打开的 RocksDB 实例安全吗?

答:不安全,除非这个实例是以只读模式打开。

问: 如果我用一种新的压缩方式打开 RocksDB,我还能读到旧的(用其他压缩方式保存的)数据吗?

答:可以,RocksDB 会在 SST 文件里面保存压缩方式,所以就算换了压缩方式,仍旧能读到现有的文件。你甚至可以通过 ColumnFamilyOptions::bottommost_compression 给最底层换一个压缩算法。

问: 我想在 HDFS 上备份 RocksDB,怎么配置呢?

答:使用 BackupableDB 然后把 backup_env 设置为 NewHdfsEnv() 的返回值即可。

问: 在压缩过滤器的回调里面读,写 rocksDB 是不是安全的?

答:读是安全的,但是写不一定总是安全的,因为在要锁过滤器的回调里写数据可能会在触发停写条件的时候造成死锁。

问: 生成 snapshot 的时候,RocksDB 会保留 SST 文件以及 memtable 吗?

答:不会,参考 snapshot工作原理

问: 如果设置了 DBWithTTL,过期的 key 被删除的时间有保证吗?

答:没有,DBWithTTL 不保证一个时间上限。过期的 key 只在他们被压缩的时候被删除。然而,没人保证这个压缩一定会发生。例如,你有一部分 key 永远都不会更新,那么压缩基本不会影响这部分 key 所以他们也不会因过期而被删除。

问: 如果我删除了一个列族,但是不删除他的 handle,我还能访问这个数据库吗?

答:可以的,DropColumnFamily() 只是把特定的列族标记为丢弃,在他的引用减少到 0 之前都不会被删除。

问: 为什么 RocksDB 在我只做写请求的时候还会发起读请求?

答:这些读请求来自压缩操作。RocksDB 的压缩操作会读取不定数量的 SST 文件,然后进行合并排序之类的操作,生成新的 SST 文件,然后删除旧的读取的 SST 文件。

问:RocksDB 支持拷贝吗?

答:不支持,RocksDB 不直接支持拷贝。然而,他提供一些 API 可以用来帮忙支持拷贝。例如,GetUpdateSince()允许开发者遍历从某个时间点之后的所有更新。参考

问:RocksDB 的最新版本是哪个?

答:所有在 releases 的都是稳定版本。对于 RocksJava,稳定版本发布在 (https://oss.sonatype.org/#nexus-search;quick~rocksdb

问:block_size 是压缩前的大小,还是压缩后的?

答:压缩前的。

问: 使用了 options.prefix_extrator 之后,我有时会看到错误的结果。哪里出错了呢?

答:options.extrator 有一些限制。如果使用前缀迭代器,那么他将不支持 Prev() 或者 SeekToLast(),很多操作还不支持 SeekToFirst()。一个常见的错误是通过 Seek 和 Prev 来找一个前缀的最后一个键。这是不支持的。目前没法通过前缀迭代器来拿到某个前缀的最后一个键。同事,如果所有 prefix 都查完了,你不能继续迭代这些键。如果你确实需要这些操作,你可以试着将 ReadOptions.total_order_seek 设置为 true 来关闭前缀迭代。

问: 一个迭代器需要持有多少资源,这些资源会在什么时候被释放?

答:迭代器需要持有数据块以及内存的 memtable。每个迭代器需要持有以下资源: - 当前的迭代器所指向的所有数据块。参考 迭代器固定的数据块 - 迭代器创建的时候存在的 memtable,即使这些 memtable 被刷到磁盘,也需要持有 - 所有在迭代器创建的时候,硬盘上的 SST 文件。即使这些 SST 文件被压缩了,也需要持有 这些资源会在迭代器删除的时候被释放

问: 我可以把日志文件和 sst 文件放在不同目录吗?info 日志呢?

答:可以,WAL 文件可以通过 DBOptions::wal_dir 指定存放目录,info 日志可以通过 DBOptions::db_log_dir 指定存放目录

问:bloom filter 的 SST 文件总是被加载到内存吗?还是他们会从硬盘加载

答:这是可配置的。 BlockBaseTableOptions::cache_index_and_filter_blocks 为 true 的时候,bloom filter 以及索引块会在 Get 调用的时候被载入一个 LRU 缓存。否则,RocksDB 会尝试加载 DBOptions::max_open_files 个 SST 文件的 bloom filter 文件及索引进入内存。

问: 发起一个 Put 和 Write 请求的时候,如果设置 WriteOptions.sync=true,是不是之前写的数据也会落盘呢?

答:如果之前写的所有请求都是 WriteOptions.disableWAL=false 的话,是的。

问: 我关闭了 WAL,然后依赖 DB::Flush 来落盘数据。在单一列族的时候这很完美。如果我有多个列族,我也能这么做吗?

答:不可以,目前 DB::Flush 不是跨列族的原子操作。我们有计划支持这个功能。

问: 如果使用非默认的压缩器和合并操作符,我还能用 ldb 工具吗?

答:这种情况下,常规的 ldb 工具是不能使用的。然而,你可以把这些东西编译进你的定制 ldb 工具,然后通过 rocksdb::LDBTool::Run(argc, argv, options) 把参数传过去。

问:RocksDB 如何处理读写 IO 错误?

答:如果 IO 错误发生在前端请求,例如 Get 和 Write 的时候,RocksDB 会返回一个 rocksdb::IOError 状态。如果错误发生在后台线程并且 options.paranoid_checks 为 true,我们会切换到只读模式。所有写操作都会被拒绝,并且返回具体的后台的错误。

问: 我可以取消某次特定的压缩吗?

答:不可以。你没法指定。

问: 如果压缩正在进行,我可以手动关闭这个 db 吗?

答:不可以,这样不安全。你可以在另一个线程调用 CancelAllBackgroundWork 来放弃压缩工作,这样你可以更快地关闭 DB。

问: 如果我用一个不同的压缩方式打开 RocksDB,会发生什么?

答:如果用不同的压缩方式打开 rocksdb 的数据库,一下场景可能会发生: - 如果当前 LSM 布局的压缩方式与配置不兼容,数据库会拒绝打开。 - 如果新的配置跟当前的 LSM 布局兼容,rocksdb 会继续大开数据库。然而,为了保证新的配置完全生效,rocksdb 会做一次全量压缩

问: 如何正确删除一个范围的 key?

答:参考 这里

问: 列族是用来干嘛的?

答:用列族的原因包括: - 对不同的数据使用不同的压缩方式,压缩算法, 压缩风格,合并操作符,压缩过滤器。 - 通过删除一个列族来删除其携带的所有数据。 - 一个列族用于携带另一个列族的元数据。

问: 把数据存在不同的列族和存在不同的 rocksdb 数据库有什么不同?

答:主要差异在于备份,原子写以及写性能。 使用多个数据库的优点:数据库是备份 (backup) 和检查点 (checkpoint) 的单位。拷贝到另一个主机的时候,数据库比列族更方便。是用列族的优点:在一个数据库里,批量写是跨列族原子的。用多个数据库的时候没法做到这个。如果你对 WAL 调用 sync,大量的数据库会损耗性能。

问:RocsDB 有列吗?如果没有,那为什么有列族呢?

答:没有,RocksDB 没有列。参考 Column Families 了解什么是列族

问:RocksDB 真的在读的时候无锁吗?

答:下列情况读请求会需要锁: - 读区共享的缓存块。 - 读取 options.max_open_files != -1 的表缓存 - 如果一个读请求发生在一次落盘或者压缩之后,他可能会短暂地持有一俄国全局锁,用于加载最新的 LSM 树的元数据。 - RocksDB 使用的内存分配器(如 jmelloc),有时候会加锁。这些锁通常都是很少出现的,并且都有性能保证。

问: 如果我需要更新多个 key,我应该用多个 Put 操作,还是把他们放到一个批量写操作,然后使用 Write 调用?

答:通常使用一次 Write 会比多次调用 Put 获得更好的性能。

问: 迭代所有的 key 的最好方式是什么?

答:如果是一个小的或者只读的数据库,创建一个迭代器然后遍历所有 key 就行了。否则,考虑隔一段时间就重新创建一个迭代器,毕竟迭代器会持有所有资源,不让他们释放。如果你需要一个一致的视图,创建一个 snapshot,然后遍历这个 snapshot 即可。

问: 如果我有不同的键值空间,我应该用不同的前缀区分它们,还是放在不同的列族?

答:如果每个键值空间都很大,放在不同的列族是好的。如果它们可能很小,你应该考虑把几个不同的键值空间打包到一个列族,这样就不用维护好几个不同的列族了。

问: 如何估算一次强制全压缩会归还的空间?

答:目前没有一个简单的方法估算到精确值,特别是如果你用了压缩过滤器的时候。如果数据库的大小比较稳定,读取 DB 属性 rocksdb.estimate-live-data-size 应该是最好的估算。

问:snapshot,checkpoint 和 backup 有什么区别

答:snapshot 是一个逻辑概念,用户可以通过 API 查询数据,但是底层的压缩还是会覆盖存在的文件。 checkpoint 会用同一个 Env 为所有数据库文件创建一个物理镜像。如果操作系统允许使用硬链接创建镜像文件,那么这个操作就比较轻量。 backup 可以把物理的数据库文件移动到其他环境(如 HDFS)。backup 引擎还支持不同备份之间的增量复制。

问: 我应该用哪种压缩风格呢?

答:从把所有层设置为 LZ4(或者 Snappy)开始,以得到一个好性能。如果你希望减小数据大小,尝试在最后一层使用 Zlib。

问: 如果没有覆盖或者删除,压缩还有必要吗?

答:即使没有过时数据,压缩仍旧是有必要的,这可以提高读性能。

问: 如果我用 option.disableWAL=true 发起了一个写请求,然后用 options.sync=true 发起另一个写请求,前面那个请求会被落盘吗?

答:不会。程序崩溃的话,如果 option.disableWAL=true 的数据没有被刷入 sst 文件,这些数据就丢了。

问:options.target_file_size_multiplier 是用来干嘛的?

答:这是一个非常少用的功能。你可以用这个来减小 SST 文件的数量。

问: 如何区分 RocksJava 抛出的异常?

答:RocksJava 会用 RocksDBException 封装所有的异常。

问: 我观察到写 IO 有尖峰。我应该如何消除它们?

答:尝试使用限速器:https://github.com/facebook/rocksdb/blob/v4.9/include/rocksdb/options.h#L875-L879

问: 不重新打开 rocksdb,我能修改压缩过滤器吗?

答:不支持这种操作。然而,你可以通过编写自己的,返回不同压缩过滤器的 CompactionFilterFactory 来实现这个操作。

问: 对于迭代器,Next 和 Prev 的性能是一样的吗?

答:反向迭代器的性能通常比正向迭代差。有几个原因:1,数据块的编码方式对 Next 更加友好。memtable 的跳表是单向的,所以每次 Prev 调用都是一个新的二分搜索。3,内部的 key 排序是为 Next 优化过的。

问: 一个数据库最多可以有多少个列族?

答:用户在至少上千个列族的时候,都是不应该看到错误的。然而,列族太多会影响性能。我们不推荐用户使用超过数百个列族。

问:RocksDB 可以提供数据库里的 key 总数吗?或者某个范围内的 key 总数?

答:rocksDB 可以通过 DB 属性 "rocksdb.estimate-num-keys" 估算总 key 数量。注意,如果有合并操作符,写覆盖,删除不存在的键值,这个估算会偏差很大。

估算一个区间的 key 数量的最好办法是,先调用 DB::GetApproximateSizes,然后通过该返回估算一个值。

问: 我什么时候应该使用 RocksDB repair 工具?有什么最佳实践吗?

答:参考 RocksDB Repairer

问: 如果我想取 10 个 key 出来,是调用 MultiGet 好还是调用 10 次 Get 好?

答:性能很接近。MultiGet 从同一个一致视图读取,但是不会更快。

问: 我应该怎么处理数据分片?

答:你可以从对每个数据分片用一个 rocksdb 数据库开始。

问: 块缓存的默认大小是多大?

答:8MB。

问: 如果我有好几个列族然后在调用 DB 函数的时候不传列族指针,会发生什么?

答:只会操作默认列族。

问: 由于磁盘空间不足,DB 操作失败了,我要如何把自己解锁呢?

答:先清理出一些空间。然后你需要重启 DB,使之恢复正常。目前没有不重启 DB 就恢复的方法。

问:ReadOptions,WriteOptions 可以被跨线程使用吗?

答:只要他们是不变的,你就可以复用它们。

问: 我可以复用 DBOptions 或者 ColumnFamilyOptions 来打开多个 DB 或者列族吗?

答:可以。内部实现上,RocksDB 总是会拷贝一次这些选项,所以你可以修改,复用它们。

问:RocksDB 支持群提交吗?

答:是的。多个线程提交的多个写请求会被聚集起来。你可以配置其中一个线程为这些请求通过一个写调用写 WAL 日志并且落盘。

问: 有办法只对 key 做遍历吗?如果可以,这样会比加载 key 和 value 更高效吗?

答:不,这通常不会更高效。RocksDB 的 key 和 value 一般存在一起。当用户遍历 key 的时候,value 已经被加载到内存了,所以不管 value 不会有太大的提升。在 BlobDB,key 和 value 被分开存储,所以他可以在只遍历 key 的时候有更好的性能,不过这个还没有支持。我们以后可能会支持这个特性。

问: 事务对象是线程安全的吗?

答:不是的。你不可以并发地对同一个事务对象进行操作。(当然,你可以平行地对多个事务对象进行操作,毕竟这个是该功能的意义)

问: 当迭代器从一个 key/value 上移开之后,该 key/value 占用的内存还会被保留吗?

答:不,它们会被释放,除非你设置了 ReadOptions.pin_data = true,并且你的设置支持这个功能。

问: 如何估算 DB 索引和过滤块的大小

答:对一个离线的 DB,"sst_dump --show_properties --command=none" 会给出一个 sst 文件的索引和过滤器的大小。你可以把它们加起来,得到数据库的总大小。对于运行中的 DB,你可以读取 DB 属性 kAggregatedTableProperties。或者对每个独立的文件调用 DB::GetPropertiesOfAllTables(),然后把它们加起来。

问: 我可以从程序里读取 SST 文件的数据吗?

答:我们目前不支持这么做。你可以通过 sst_dump 来导出数据。