mirror of
https://github.com/facebook/rocksdb.git
synced 2024-11-26 16:30:56 +00:00
97f6f475bc
Summary: 1. **Error** in TestIterateAgainstExpected API - `Assertion index < pre_read_expected_values.size() && index < post_read_expected_values.size() failed.` **Fix** - `Prev` op is not supported with `auto_readahead_size`. So added support to Reseek in db_iter, if Prev is called. In BlockBasedTableIterator, index_iter_ already moves forward. So there is no way to do Prev from BlockBasedTableIterator. 2. **Error** - `void rocksdb::BlockBasedTableIterator::BlockCacheLookupForReadAheadSize(uint64_t, size_t, size_t&): Assertion index_iter_->value().handle.offset() == offset` **Fix** - Remove prefetch_buffer to be used when uncompressed dict is read. 3. ** Error in TestPrefixScan API - `db_stress: db/db_iter.cc:369: bool rocksdb::DBIter::FindNextUserEntryInternal(bool, const rocksdb::Slice*): Assertion !skipping_saved_key || CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) > 0 failed. Received signal 6 (Aborted) Invoking GDB for stack trace... db_stress: table/merging_iterator.cc:1036: bool rocksdb::MergingIterator::SkipNextDeleted(): Assertion comparator_->Compare(range_tombstone_iters_[i]->start_key(), pik) <= 0 failed` **Fix** - SeekPrev also calls 1) SeekPrev , 2)Seek and then 3)Prev in some cases in db_iter.cc leading to failure of Prev operation. These backward operations also call Seek. Added direction to disable lookup once direction is backwards in BlockBasedTableIterator.cc Pull Request resolved: https://github.com/facebook/rocksdb/pull/11884 Test Plan: Ran various flavors of crash tests locally for the whole duration Reviewed By: anand1976 Differential Revision: D49834201 Pulled By: akankshamahajan15 fbshipit-source-id: 9a007b4d46a48002c43dc4623a400ecf47d997fe
421 lines
15 KiB
C++
421 lines
15 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).
|
|
//
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
#pragma once
|
|
#include <cstdint>
|
|
#include <string>
|
|
|
|
#include "db/db_impl/db_impl.h"
|
|
#include "db/range_del_aggregator.h"
|
|
#include "memory/arena.h"
|
|
#include "options/cf_options.h"
|
|
#include "rocksdb/db.h"
|
|
#include "rocksdb/iterator.h"
|
|
#include "rocksdb/wide_columns.h"
|
|
#include "table/iterator_wrapper.h"
|
|
#include "util/autovector.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
class Version;
|
|
|
|
// This file declares the factory functions of DBIter, in its original form
|
|
// or a wrapped form with class ArenaWrappedDBIter, which is defined here.
|
|
// Class DBIter, which is declared and implemented inside db_iter.cc, is
|
|
// an iterator that converts internal keys (yielded by an InternalIterator)
|
|
// that were live at the specified sequence number into appropriate user
|
|
// keys.
|
|
// Each internal key consists of a user key, a sequence number, and a value
|
|
// type. DBIter deals with multiple key versions, tombstones, merge operands,
|
|
// etc, and exposes an Iterator.
|
|
// For example, DBIter may wrap following InternalIterator:
|
|
// user key: AAA value: v3 seqno: 100 type: Put
|
|
// user key: AAA value: v2 seqno: 97 type: Put
|
|
// user key: AAA value: v1 seqno: 95 type: Put
|
|
// user key: BBB value: v1 seqno: 90 type: Put
|
|
// user key: BBC value: N/A seqno: 98 type: Delete
|
|
// user key: BBC value: v1 seqno: 95 type: Put
|
|
// If the snapshot passed in is 102, then the DBIter is expected to
|
|
// expose the following iterator:
|
|
// key: AAA value: v3
|
|
// key: BBB value: v1
|
|
// If the snapshot passed in is 96, then it should expose:
|
|
// key: AAA value: v1
|
|
// key: BBB value: v1
|
|
// key: BBC value: v1
|
|
//
|
|
|
|
// Memtables and sstables that make the DB representation contain
|
|
// (userkey,seq,type) => uservalue entries. DBIter
|
|
// combines multiple entries for the same userkey found in the DB
|
|
// representation into a single entry while accounting for sequence
|
|
// numbers, deletion markers, overwrites, etc.
|
|
class DBIter final : public Iterator {
|
|
public:
|
|
// The following is grossly complicated. TODO: clean it up
|
|
// Which direction is the iterator currently moving?
|
|
// (1) When moving forward:
|
|
// (1a) if current_entry_is_merged_ = false, the internal iterator is
|
|
// positioned at the exact entry that yields this->key(), this->value()
|
|
// (1b) if current_entry_is_merged_ = true, the internal iterator is
|
|
// positioned immediately after the last entry that contributed to the
|
|
// current this->value(). That entry may or may not have key equal to
|
|
// this->key().
|
|
// (2) When moving backwards, the internal iterator is positioned
|
|
// just before all entries whose user key == this->key().
|
|
enum Direction : uint8_t { kForward, kReverse };
|
|
|
|
// LocalStatistics contain Statistics counters that will be aggregated per
|
|
// each iterator instance and then will be sent to the global statistics when
|
|
// the iterator is destroyed.
|
|
//
|
|
// The purpose of this approach is to avoid perf regression happening
|
|
// when multiple threads bump the atomic counters from a DBIter::Next().
|
|
struct LocalStatistics {
|
|
explicit LocalStatistics() { ResetCounters(); }
|
|
|
|
void ResetCounters() {
|
|
next_count_ = 0;
|
|
next_found_count_ = 0;
|
|
prev_count_ = 0;
|
|
prev_found_count_ = 0;
|
|
bytes_read_ = 0;
|
|
skip_count_ = 0;
|
|
}
|
|
|
|
void BumpGlobalStatistics(Statistics* global_statistics) {
|
|
RecordTick(global_statistics, NUMBER_DB_NEXT, next_count_);
|
|
RecordTick(global_statistics, NUMBER_DB_NEXT_FOUND, next_found_count_);
|
|
RecordTick(global_statistics, NUMBER_DB_PREV, prev_count_);
|
|
RecordTick(global_statistics, NUMBER_DB_PREV_FOUND, prev_found_count_);
|
|
RecordTick(global_statistics, ITER_BYTES_READ, bytes_read_);
|
|
RecordTick(global_statistics, NUMBER_ITER_SKIP, skip_count_);
|
|
PERF_COUNTER_ADD(iter_read_bytes, bytes_read_);
|
|
ResetCounters();
|
|
}
|
|
|
|
// Map to Tickers::NUMBER_DB_NEXT
|
|
uint64_t next_count_;
|
|
// Map to Tickers::NUMBER_DB_NEXT_FOUND
|
|
uint64_t next_found_count_;
|
|
// Map to Tickers::NUMBER_DB_PREV
|
|
uint64_t prev_count_;
|
|
// Map to Tickers::NUMBER_DB_PREV_FOUND
|
|
uint64_t prev_found_count_;
|
|
// Map to Tickers::ITER_BYTES_READ
|
|
uint64_t bytes_read_;
|
|
// Map to Tickers::NUMBER_ITER_SKIP
|
|
uint64_t skip_count_;
|
|
};
|
|
|
|
DBIter(Env* _env, const ReadOptions& read_options,
|
|
const ImmutableOptions& ioptions,
|
|
const MutableCFOptions& mutable_cf_options, const Comparator* cmp,
|
|
InternalIterator* iter, const Version* version, SequenceNumber s,
|
|
bool arena_mode, uint64_t max_sequential_skip_in_iterations,
|
|
ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
|
|
bool expose_blob_index);
|
|
|
|
// No copying allowed
|
|
DBIter(const DBIter&) = delete;
|
|
void operator=(const DBIter&) = delete;
|
|
|
|
~DBIter() override {
|
|
// Release pinned data if any
|
|
if (pinned_iters_mgr_.PinningEnabled()) {
|
|
pinned_iters_mgr_.ReleasePinnedData();
|
|
}
|
|
RecordTick(statistics_, NO_ITERATOR_DELETED);
|
|
ResetInternalKeysSkippedCounter();
|
|
local_stats_.BumpGlobalStatistics(statistics_);
|
|
iter_.DeleteIter(arena_mode_);
|
|
}
|
|
void SetIter(InternalIterator* iter) {
|
|
assert(iter_.iter() == nullptr);
|
|
iter_.Set(iter);
|
|
iter_.iter()->SetPinnedItersMgr(&pinned_iters_mgr_);
|
|
}
|
|
|
|
bool Valid() const override {
|
|
#ifdef ROCKSDB_ASSERT_STATUS_CHECKED
|
|
if (valid_) {
|
|
status_.PermitUncheckedError();
|
|
}
|
|
#endif // ROCKSDB_ASSERT_STATUS_CHECKED
|
|
return valid_;
|
|
}
|
|
Slice key() const override {
|
|
assert(valid_);
|
|
if (timestamp_lb_) {
|
|
return saved_key_.GetInternalKey();
|
|
} else {
|
|
const Slice ukey_and_ts = saved_key_.GetUserKey();
|
|
return Slice(ukey_and_ts.data(), ukey_and_ts.size() - timestamp_size_);
|
|
}
|
|
}
|
|
Slice value() const override {
|
|
assert(valid_);
|
|
|
|
return value_;
|
|
}
|
|
|
|
const WideColumns& columns() const override {
|
|
assert(valid_);
|
|
|
|
return wide_columns_;
|
|
}
|
|
|
|
Status status() const override {
|
|
if (status_.ok()) {
|
|
return iter_.status();
|
|
} else {
|
|
assert(!valid_);
|
|
return status_;
|
|
}
|
|
}
|
|
Slice timestamp() const override {
|
|
assert(valid_);
|
|
assert(timestamp_size_ > 0);
|
|
if (direction_ == kReverse) {
|
|
return saved_timestamp_;
|
|
}
|
|
const Slice ukey_and_ts = saved_key_.GetUserKey();
|
|
assert(timestamp_size_ < ukey_and_ts.size());
|
|
return ExtractTimestampFromUserKey(ukey_and_ts, timestamp_size_);
|
|
}
|
|
bool IsBlob() const {
|
|
assert(valid_);
|
|
return is_blob_;
|
|
}
|
|
|
|
Status GetProperty(std::string prop_name, std::string* prop) override;
|
|
|
|
void Next() final override;
|
|
void Prev() final override;
|
|
// 'target' does not contain timestamp, even if user timestamp feature is
|
|
// enabled.
|
|
void Seek(const Slice& target) final override;
|
|
void SeekForPrev(const Slice& target) final override;
|
|
void SeekToFirst() final override;
|
|
void SeekToLast() final override;
|
|
Env* env() const { return env_; }
|
|
void set_sequence(uint64_t s) {
|
|
sequence_ = s;
|
|
if (read_callback_) {
|
|
read_callback_->Refresh(s);
|
|
}
|
|
iter_.SetRangeDelReadSeqno(s);
|
|
}
|
|
void set_valid(bool v) { valid_ = v; }
|
|
|
|
private:
|
|
// For all methods in this block:
|
|
// PRE: iter_->Valid() && status_.ok()
|
|
// Return false if there was an error, and status() is non-ok, valid_ = false;
|
|
// in this case callers would usually stop what they were doing and return.
|
|
bool ReverseToForward();
|
|
bool ReverseToBackward();
|
|
// Set saved_key_ to the seek key to target, with proper sequence number set.
|
|
// It might get adjusted if the seek key is smaller than iterator lower bound.
|
|
// target does not have timestamp.
|
|
void SetSavedKeyToSeekTarget(const Slice& target);
|
|
// Set saved_key_ to the seek key to target, with proper sequence number set.
|
|
// It might get adjusted if the seek key is larger than iterator upper bound.
|
|
// target does not have timestamp.
|
|
void SetSavedKeyToSeekForPrevTarget(const Slice& target);
|
|
bool FindValueForCurrentKey();
|
|
bool FindValueForCurrentKeyUsingSeek();
|
|
bool FindUserKeyBeforeSavedKey();
|
|
// If `skipping_saved_key` is true, the function will keep iterating until it
|
|
// finds a user key that is larger than `saved_key_`.
|
|
// If `prefix` is not null, the iterator needs to stop when all keys for the
|
|
// prefix are exhausted and the iterator is set to invalid.
|
|
bool FindNextUserEntry(bool skipping_saved_key, const Slice* prefix);
|
|
// Internal implementation of FindNextUserEntry().
|
|
bool FindNextUserEntryInternal(bool skipping_saved_key, const Slice* prefix);
|
|
bool ParseKey(ParsedInternalKey* key);
|
|
bool MergeValuesNewToOld();
|
|
|
|
// If prefix is not null, we need to set the iterator to invalid if no more
|
|
// entry can be found within the prefix.
|
|
void PrevInternal(const Slice* prefix);
|
|
bool TooManyInternalKeysSkipped(bool increment = true);
|
|
bool IsVisible(SequenceNumber sequence, const Slice& ts,
|
|
bool* more_recent = nullptr);
|
|
|
|
// Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
|
|
// is called
|
|
void TempPinData() {
|
|
if (!pin_thru_lifetime_) {
|
|
pinned_iters_mgr_.StartPinning();
|
|
}
|
|
}
|
|
|
|
// Release blocks pinned by TempPinData()
|
|
void ReleaseTempPinnedData() {
|
|
if (!pin_thru_lifetime_ && pinned_iters_mgr_.PinningEnabled()) {
|
|
pinned_iters_mgr_.ReleasePinnedData();
|
|
}
|
|
}
|
|
|
|
inline void ClearSavedValue() {
|
|
if (saved_value_.capacity() > 1048576) {
|
|
std::string empty;
|
|
swap(empty, saved_value_);
|
|
} else {
|
|
saved_value_.clear();
|
|
}
|
|
}
|
|
|
|
inline void ResetInternalKeysSkippedCounter() {
|
|
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
|
if (valid_) {
|
|
local_stats_.skip_count_--;
|
|
}
|
|
num_internal_keys_skipped_ = 0;
|
|
}
|
|
|
|
bool expect_total_order_inner_iter() {
|
|
assert(expect_total_order_inner_iter_ || prefix_extractor_ != nullptr);
|
|
return expect_total_order_inner_iter_;
|
|
}
|
|
|
|
// If lower bound of timestamp is given by ReadOptions.iter_start_ts, we need
|
|
// to return versions of the same key. We cannot just skip if the key value
|
|
// is the same but timestamps are different but fall in timestamp range.
|
|
inline int CompareKeyForSkip(const Slice& a, const Slice& b) {
|
|
return timestamp_lb_ != nullptr
|
|
? user_comparator_.Compare(a, b)
|
|
: user_comparator_.CompareWithoutTimestamp(a, b);
|
|
}
|
|
|
|
// Retrieves the blob value for the specified user key using the given blob
|
|
// index when using the integrated BlobDB implementation.
|
|
bool SetBlobValueIfNeeded(const Slice& user_key, const Slice& blob_index);
|
|
|
|
void ResetBlobValue() {
|
|
is_blob_ = false;
|
|
blob_value_.Reset();
|
|
}
|
|
|
|
void SetValueAndColumnsFromPlain(const Slice& slice) {
|
|
assert(value_.empty());
|
|
assert(wide_columns_.empty());
|
|
|
|
value_ = slice;
|
|
wide_columns_.emplace_back(kDefaultWideColumnName, slice);
|
|
}
|
|
|
|
bool SetValueAndColumnsFromEntity(Slice slice);
|
|
|
|
bool SetValueAndColumnsFromMergeResult(const Status& merge_status,
|
|
ValueType result_type);
|
|
|
|
void ResetValueAndColumns() {
|
|
value_.clear();
|
|
wide_columns_.clear();
|
|
}
|
|
|
|
// The following methods perform the actual merge operation for the
|
|
// no base value/plain base value/wide-column base value cases.
|
|
// If user-defined timestamp is enabled, `user_key` includes timestamp.
|
|
bool MergeWithNoBaseValue(const Slice& user_key);
|
|
bool MergeWithPlainBaseValue(const Slice& value, const Slice& user_key);
|
|
bool MergeWithWideColumnBaseValue(const Slice& entity, const Slice& user_key);
|
|
|
|
const SliceTransform* prefix_extractor_;
|
|
Env* const env_;
|
|
SystemClock* clock_;
|
|
Logger* logger_;
|
|
UserComparatorWrapper user_comparator_;
|
|
const MergeOperator* const merge_operator_;
|
|
IteratorWrapper iter_;
|
|
const Version* version_;
|
|
ReadCallback* read_callback_;
|
|
// Max visible sequence number. It is normally the snapshot seq unless we have
|
|
// uncommitted data in db as in WriteUnCommitted.
|
|
SequenceNumber sequence_;
|
|
|
|
IterKey saved_key_;
|
|
// Reusable internal key data structure. This is only used inside one function
|
|
// and should not be used across functions. Reusing this object can reduce
|
|
// overhead of calling construction of the function if creating it each time.
|
|
ParsedInternalKey ikey_;
|
|
std::string saved_value_;
|
|
Slice pinned_value_;
|
|
// for prefix seek mode to support prev()
|
|
PinnableSlice blob_value_;
|
|
// Value of the default column
|
|
Slice value_;
|
|
// All columns (i.e. name-value pairs)
|
|
WideColumns wide_columns_;
|
|
Statistics* statistics_;
|
|
uint64_t max_skip_;
|
|
uint64_t max_skippable_internal_keys_;
|
|
uint64_t num_internal_keys_skipped_;
|
|
const Slice* iterate_lower_bound_;
|
|
const Slice* iterate_upper_bound_;
|
|
|
|
// The prefix of the seek key. It is only used when prefix_same_as_start_
|
|
// is true and prefix extractor is not null. In Next() or Prev(), current keys
|
|
// will be checked against this prefix, so that the iterator can be
|
|
// invalidated if the keys in this prefix has been exhausted. Set it using
|
|
// SetUserKey() and use it using GetUserKey().
|
|
IterKey prefix_;
|
|
|
|
Status status_;
|
|
Direction direction_;
|
|
bool valid_;
|
|
bool current_entry_is_merged_;
|
|
// True if we know that the current entry's seqnum is 0.
|
|
// This information is used as that the next entry will be for another
|
|
// user key.
|
|
bool is_key_seqnum_zero_;
|
|
const bool prefix_same_as_start_;
|
|
// Means that we will pin all data blocks we read as long the Iterator
|
|
// is not deleted, will be true if ReadOptions::pin_data is true
|
|
const bool pin_thru_lifetime_;
|
|
// Expect the inner iterator to maintain a total order.
|
|
// prefix_extractor_ must be non-NULL if the value is false.
|
|
const bool expect_total_order_inner_iter_;
|
|
ReadTier read_tier_;
|
|
bool fill_cache_;
|
|
bool verify_checksums_;
|
|
// Whether the iterator is allowed to expose blob references. Set to true when
|
|
// the stacked BlobDB implementation is used, false otherwise.
|
|
bool expose_blob_index_;
|
|
bool is_blob_;
|
|
bool arena_mode_;
|
|
const Env::IOActivity io_activity_;
|
|
// List of operands for merge operator.
|
|
MergeContext merge_context_;
|
|
LocalStatistics local_stats_;
|
|
PinnedIteratorsManager pinned_iters_mgr_;
|
|
DBImpl* db_impl_;
|
|
ColumnFamilyData* cfd_;
|
|
const Slice* const timestamp_ub_;
|
|
const Slice* const timestamp_lb_;
|
|
const size_t timestamp_size_;
|
|
std::string saved_timestamp_;
|
|
bool auto_readahead_size_;
|
|
};
|
|
|
|
// Return a new iterator that converts internal keys (yielded by
|
|
// "*internal_iter") that were live at the specified `sequence` number
|
|
// into appropriate user keys.
|
|
extern Iterator* NewDBIterator(
|
|
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
|
const MutableCFOptions& mutable_cf_options,
|
|
const Comparator* user_key_comparator, InternalIterator* internal_iter,
|
|
const Version* version, const SequenceNumber& sequence,
|
|
uint64_t max_sequential_skip_in_iterations, ReadCallback* read_callback,
|
|
DBImpl* db_impl = nullptr, ColumnFamilyData* cfd = nullptr,
|
|
bool expose_blob_index = false);
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|