mirror of
https://github.com/facebook/rocksdb.git
synced 2024-11-27 02:44:18 +00:00
68ce5d84f6
Summary: This PR resolves https://github.com/facebook/rocksdb/issues/10487 & https://github.com/facebook/rocksdb/issues/10536, user code needs to call Refresh() periodically. The main code change is to support range deletions. A range tombstone iterator uses a sequence number as upper bound to decide which range tombstones are effective. During Iterator refresh, this sequence number upper bound needs to be updated for all range tombstone iterators under DBIter and LevelIterator. LevelIterator may create new table iterators and range tombstone iterator during scanning, so it needs to be aware of iterator refresh. The code path that propagates this change is `db_iter_->set_sequence(read_seq) -> MergingIterator::SetRangeDelReadSeqno() -> TruncatedRangeDelIterator::SetRangeDelReadSeqno() and LevelIterator::SetRangeDelReadSeqno()`. This change also fixes an issue where range tombstone iterators created by LevelIterator may access ReadOptions::snapshot, even though we do not explicitly require users to keep a snapshot alive after creating an Iterator. Pull Request resolved: https://github.com/facebook/rocksdb/pull/10594 Test Plan: * New unit tests. * Add Iterator::Refresh(snapshot) to stress test. Note that this change only adds tests for refreshing to the same snapshot since this is the main target use case. TODO in a following PR: * Stress test Iterator::Refresh() to different snapshots or no snapshot. Reviewed By: ajkr Differential Revision: D48456896 Pulled By: cbi42 fbshipit-source-id: 2e642c04e91235cc9542ef4cd37b3c20823bd779
236 lines
9.3 KiB
C++
236 lines
9.3 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
|
|
#include "db/dbformat.h"
|
|
#include "file/readahead_file_info.h"
|
|
#include "rocksdb/comparator.h"
|
|
#include "rocksdb/iterator.h"
|
|
#include "rocksdb/status.h"
|
|
#include "table/format.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
class PinnedIteratorsManager;
|
|
|
|
enum class IterBoundCheck : char {
|
|
kUnknown = 0,
|
|
kOutOfBound,
|
|
kInbound,
|
|
};
|
|
|
|
struct IterateResult {
|
|
Slice key;
|
|
IterBoundCheck bound_check_result = IterBoundCheck::kUnknown;
|
|
// If false, PrepareValue() needs to be called before value().
|
|
bool value_prepared = true;
|
|
};
|
|
|
|
template <class TValue>
|
|
class InternalIteratorBase : public Cleanable {
|
|
public:
|
|
InternalIteratorBase() {}
|
|
|
|
// No copying allowed
|
|
InternalIteratorBase(const InternalIteratorBase&) = delete;
|
|
InternalIteratorBase& operator=(const InternalIteratorBase&) = delete;
|
|
|
|
virtual ~InternalIteratorBase() {}
|
|
|
|
// This iterator will only process range tombstones with sequence
|
|
// number <= `read_seqno`.
|
|
// Noop for most child classes.
|
|
// For range tombstone iterators (TruncatedRangeDelIterator,
|
|
// FragmentedRangeTombstoneIterator), will only return range tombstones with
|
|
// sequence number <= `read_seqno`. For LevelIterator, it may open new table
|
|
// files and create new range tombstone iterators during scanning. It will use
|
|
// `read_seqno` as the sequence number for creating new range tombstone
|
|
// iterators.
|
|
virtual void SetRangeDelReadSeqno(SequenceNumber /* read_seqno */) {}
|
|
|
|
// An iterator is either positioned at a key/value pair, or
|
|
// not valid. This method returns true iff the iterator is valid.
|
|
// Always returns false if !status().ok().
|
|
virtual bool Valid() const = 0;
|
|
|
|
// Position at the first key in the source. The iterator is Valid()
|
|
// after this call iff the source is not empty.
|
|
virtual void SeekToFirst() = 0;
|
|
|
|
// Position at the last key in the source. The iterator is
|
|
// Valid() after this call iff the source is not empty.
|
|
virtual void SeekToLast() = 0;
|
|
|
|
// Position at the first key in the source that at or past target
|
|
// The iterator is Valid() after this call iff the source contains
|
|
// an entry that comes at or past target.
|
|
// All Seek*() methods clear any error status() that the iterator had prior to
|
|
// the call; after the seek, status() indicates only the error (if any) that
|
|
// happened during the seek, not any past errors.
|
|
// 'target' contains user timestamp if timestamp is enabled.
|
|
virtual void Seek(const Slice& target) = 0;
|
|
|
|
// Position at the first key in the source that at or before target
|
|
// The iterator is Valid() after this call iff the source contains
|
|
// an entry that comes at or before target.
|
|
virtual void SeekForPrev(const Slice& target) = 0;
|
|
|
|
// Moves to the next entry in the source. After this call, Valid() is
|
|
// true iff the iterator was not positioned at the last entry in the source.
|
|
// REQUIRES: Valid()
|
|
virtual void Next() = 0;
|
|
|
|
// Moves to the next entry in the source, and return result. Iterator
|
|
// implementation should override this method to help methods inline better,
|
|
// or when UpperBoundCheckResult() is non-trivial.
|
|
// REQUIRES: Valid()
|
|
virtual bool NextAndGetResult(IterateResult* result) {
|
|
Next();
|
|
bool is_valid = Valid();
|
|
if (is_valid) {
|
|
result->key = key();
|
|
// Default may_be_out_of_upper_bound to true to avoid unnecessary virtual
|
|
// call. If an implementation has non-trivial UpperBoundCheckResult(),
|
|
// it should also override NextAndGetResult().
|
|
result->bound_check_result = IterBoundCheck::kUnknown;
|
|
result->value_prepared = false;
|
|
assert(UpperBoundCheckResult() != IterBoundCheck::kOutOfBound);
|
|
}
|
|
return is_valid;
|
|
}
|
|
|
|
// Moves to the previous entry in the source. After this call, Valid() is
|
|
// true iff the iterator was not positioned at the first entry in source.
|
|
// REQUIRES: Valid()
|
|
virtual void Prev() = 0;
|
|
|
|
// Return the key for the current entry. The underlying storage for
|
|
// the returned slice is valid only until the next modification of
|
|
// the iterator.
|
|
// REQUIRES: Valid()
|
|
virtual Slice key() const = 0;
|
|
|
|
// Return user key for the current entry.
|
|
// REQUIRES: Valid()
|
|
virtual Slice user_key() const { return ExtractUserKey(key()); }
|
|
|
|
// Return the value for the current entry. The underlying storage for
|
|
// the returned slice is valid only until the next modification of
|
|
// the iterator.
|
|
// REQUIRES: Valid()
|
|
// REQUIRES: PrepareValue() has been called if needed (see PrepareValue()).
|
|
virtual TValue value() const = 0;
|
|
|
|
// If an error has occurred, return it. Else return an ok status.
|
|
// If non-blocking IO is requested and this operation cannot be
|
|
// satisfied without doing some IO, then this returns Status::Incomplete().
|
|
virtual Status status() const = 0;
|
|
|
|
// For some types of iterators, sometimes Seek()/Next()/SeekForPrev()/etc may
|
|
// load key but not value (to avoid the IO cost of reading the value from disk
|
|
// if it won't be not needed). This method loads the value in such situation.
|
|
//
|
|
// Needs to be called before value() at least once after each iterator
|
|
// movement (except if IterateResult::value_prepared = true), for iterators
|
|
// created with allow_unprepared_value = true.
|
|
//
|
|
// Returns false if an error occurred; in this case Valid() is also changed
|
|
// to false, and status() is changed to non-ok.
|
|
// REQUIRES: Valid()
|
|
virtual bool PrepareValue() { return true; }
|
|
|
|
// Keys return from this iterator can be smaller than iterate_lower_bound.
|
|
virtual bool MayBeOutOfLowerBound() { return true; }
|
|
|
|
// If the iterator has checked the key against iterate_upper_bound, returns
|
|
// the result here. The function can be used by user of the iterator to skip
|
|
// their own checks. If Valid() = true, IterBoundCheck::kUnknown is always
|
|
// a valid value. If Valid() = false, IterBoundCheck::kOutOfBound indicates
|
|
// that the iterator is filtered out by upper bound checks.
|
|
virtual IterBoundCheck UpperBoundCheckResult() {
|
|
return IterBoundCheck::kUnknown;
|
|
}
|
|
|
|
// Pass the PinnedIteratorsManager to the Iterator, most Iterators don't
|
|
// communicate with PinnedIteratorsManager so default implementation is no-op
|
|
// but for Iterators that need to communicate with PinnedIteratorsManager
|
|
// they will implement this function and use the passed pointer to communicate
|
|
// with PinnedIteratorsManager.
|
|
virtual void SetPinnedItersMgr(PinnedIteratorsManager* /*pinned_iters_mgr*/) {
|
|
}
|
|
|
|
// If true, this means that the Slice returned by key() is valid as long as
|
|
// PinnedIteratorsManager::ReleasePinnedData is not called and the
|
|
// Iterator is not deleted.
|
|
//
|
|
// IsKeyPinned() is guaranteed to always return true if
|
|
// - Iterator is created with ReadOptions::pin_data = true
|
|
// - DB tables were created with BlockBasedTableOptions::use_delta_encoding
|
|
// set to false.
|
|
virtual bool IsKeyPinned() const { return false; }
|
|
|
|
// If true, this means that the Slice returned by value() is valid as long as
|
|
// PinnedIteratorsManager::ReleasePinnedData is not called and the
|
|
// Iterator is not deleted.
|
|
// REQUIRES: Same as for value().
|
|
virtual bool IsValuePinned() const { return false; }
|
|
|
|
virtual Status GetProperty(std::string /*prop_name*/, std::string* /*prop*/) {
|
|
return Status::NotSupported("");
|
|
}
|
|
|
|
// When iterator moves from one file to another file at same level, new file's
|
|
// readahead state (details of last block read) is updated with previous
|
|
// file's readahead state. This way internal readahead_size of Prefetch Buffer
|
|
// doesn't start from scratch and can fall back to 8KB with no prefetch if
|
|
// reads are not sequential.
|
|
//
|
|
// Default implementation is no-op and its implemented by iterators.
|
|
virtual void GetReadaheadState(ReadaheadFileInfo* /*readahead_file_info*/) {}
|
|
|
|
// Default implementation is no-op and its implemented by iterators.
|
|
virtual void SetReadaheadState(ReadaheadFileInfo* /*readahead_file_info*/) {}
|
|
|
|
// When used under merging iterator, LevelIterator treats file boundaries
|
|
// as sentinel keys to prevent it from moving to next SST file before range
|
|
// tombstones in the current SST file are no longer needed. This method makes
|
|
// it cheap to check if the current key is a sentinel key. This should only be
|
|
// used by MergingIterator and LevelIterator for now.
|
|
virtual bool IsDeleteRangeSentinelKey() const { return false; }
|
|
|
|
protected:
|
|
void SeekForPrevImpl(const Slice& target, const CompareInterface* cmp) {
|
|
Seek(target);
|
|
if (!Valid()) {
|
|
SeekToLast();
|
|
}
|
|
while (Valid() && cmp->Compare(target, key()) < 0) {
|
|
Prev();
|
|
}
|
|
}
|
|
};
|
|
|
|
using InternalIterator = InternalIteratorBase<Slice>;
|
|
|
|
// Return an empty iterator (yields nothing).
|
|
template <class TValue = Slice>
|
|
extern InternalIteratorBase<TValue>* NewEmptyInternalIterator();
|
|
|
|
// Return an empty iterator with the specified status.
|
|
template <class TValue = Slice>
|
|
extern InternalIteratorBase<TValue>* NewErrorInternalIterator(
|
|
const Status& status);
|
|
|
|
// Return an empty iterator with the specified status, allocated arena.
|
|
template <class TValue = Slice>
|
|
extern InternalIteratorBase<TValue>* NewErrorInternalIterator(
|
|
const Status& status, Arena* arena);
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|