跳转至

迭代器

一致性视图

如果 ReadOptions.snapshot 被给出,那么迭代器会从一个快照里面返回数据。如果这是一个 nullptr,迭代器隐式创建一个迭代器创建的时间节点的快照。该隐式快照会通过 固定资源 来提供数据。隐式快照无法转换为显式快照。

错误处理

Iterator::status() 会返回迭代过程中的错误。错误包括 IO 错误,校验和错误,不支持的操作,内部错误,或者其他错误。

如果没有错误,会返回 Status::OK()。如果不是 OK,迭代器会马上失效。换句话说,如果 Iterator::Valid() 为 true,status() 会被保证为 OK(),所以你不检查 status 也是可以的:

for (it->Seek("hello"); it->Valid(); it->Next()) {
  // Do something with it->key() and it->value().
}
if (!it->status().ok()) {
  // Handle error. it->status().ToString() contains error message.
}

换句话说,如果 Iterator::Valid() 为 false,有两种可能:(1) 我们已经读完所有数据了。这个时候,status() 是 OK();(2) 确实出错了。这种情况下,status() 不会是 OK()。在迭代器失效的时候检查一下 status 是非常好的习惯。

Seek() 和 SeekForPrev() 会丢弃之前的状态。

注意,在 5.13.x 和之前的版本(在 2018 年 5 月 17 日合并的 PR 之前),status() 和 Valid() 的行为是有所不同的:

  • Valid() 可能在 status() 不为 ok 的时候返回 true。这可以用来跳过一些损坏的数据。现在我们不支持这个功能了。正确的损坏数据处理方式是使用 RepairDB() (参考 db.h)。
  • Seek() 和 SeekForPrev() 不一定总是会丢弃之前的状态。Next() 和 Prev() 不一定会给你非 ok 的状态。

## 迭代边界

你可以通过在调用 NewIterator 的时候,给传入的 option 设定 ReadOptions.iterate_upper_bound 来为你的迭代范围设置一个上边界。通过这个设定,RocksDB 就不用继续查找这个 key 之后的内容了。在一些情况下,可以节省一些 IO 和计算。在特定的工作载荷下,它带来的改善是显著的。这个选项可以同在正向和反向迭代。

参考该选项的注释以了解更多内容。

## 迭代器使用的资源以及迭代器更新

迭代器自身并不怎么使用内存,但是他会阻止一些资源释放。包括:

  • 迭代器创建时使用的 memtable 和 sst 文件。即使其中一些 memtable 和 sst 文件在落盘或者压缩之后被删除了,他们还会为了保证迭代器工作而被保留。
  • 当前迭代点的数据快。这些块会被保存在内存,要么在块缓存,要么在堆(如果没有快缓存的话)。注意,尽管大多数块都是很小的,但在一些极端情况下,如果值非常大,那么单个块可能非常大。

所以使用迭代器最好是短期使用,这样资源可以被正确释放。

迭代器在构建的时候会有一些花销。在一些应用场景(特别是纯内存的场景),人们可能会希望通过重用迭代器来减少构建的开销。如果你也这么做,请记住,迭代器是可能过期的,他会阻止一些资源的释放。所以请一定要记得在他们一段时间(例如,1 秒钟)没有使用后销毁,或者更新他们。如果你希望处理这个过期的迭代器,在 5.7 版本前,你只能销毁,然后重建这个迭代器。5.7 版本之后,你可以使用接口 Iterator::Refresh() 来更新他。通过这个函数,迭代器会更新到当前的数据状态,过期的资源会被清理。

前缀迭代

前缀迭代允许用户在迭代中使用 bloom filter 或者哈希索引,以此来改善性能。然而,这个功能有一定限制,而且,如果你误用他们,他还会返回错误的结果而不报任何错误。我们希望你使用这个功能的时候小心谨慎。更多关于这个功能的内容,参考 前缀迭代。选项 total_order_seek 和 prefix_same_as_start 只在前缀迭代过程中有用。

预读取

迭代过程中,如果注意到同一个表文件有需要被读 2 次以上 IO 的数据,rocksDB 会自动预读,预取数据。预读的大小从 8KB 开始,然后会在每次额外的顺序 IO 之后指数增长,最大增长到 256KB。这可以帮助减少完成一个区间扫描的 IO 数量。这个自动预读只有在 ReadOptions.readahead_size = 0(默认值)的时候开启(从 5.12 开始,迭代器自动预读功能可以用于带缓冲的文件 IO,5.15 开始可用于无缓冲文件 IO)

如果你的整个应用都是不断迭代,而你又依赖 OS 的页缓存(例如,使用带缓冲的文件 IO),你可以选择通过设置 DBOptions.advise_random_on_open = false 手动打开预读取。你如果使用硬盘或者远程存储的时候会更加有用,但是如果是挂载在本地的 SSD 设备,效果就不明显了。

ReadOptions.readahead_size 为 RocksDB 的一些非常特定的使用场景提供预读功能。他的限制就是,如果这个功能打开了,构建迭代器的开销会大很多。所以,只在你需要迭代相当大量的数据、而且又没有其他更好的办法的时候,你才应该打开它。一个典型的场景就是,存储介质是远程存储,并且延迟比较大,OS 的页缓存不可用,然后又有大量数据需要扫描的时候。通过打开这个功能,每次 SST 文件的读都会根据这个设定进行预读取。注意,一个迭代器可能会在每一层都打开一个文件,同时也会打开 L0 的所有文件。你需要为你的预读准备足够的内存。而且预读的内存无法被自动追踪。

我们还在尝试改善 RocksDB 的预读取。