迭代器

一致性视图

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

错误处理

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

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

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

换句话说,如果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的预读取。