Introduce RangeDelAggregatorV2 (#4649)
Summary: The old RangeDelAggregator did expensive pre-processing work to create a collapsed, binary-searchable representation of range tombstones. With FragmentedRangeTombstoneIterator, much of this work is now unnecessary. RangeDelAggregatorV2 takes advantage of this by seeking in each iterator to find a covering tombstone in ShouldDelete, while doing minimal work in AddTombstones. The old RangeDelAggregator is still used during flush/compaction for now, though RangeDelAggregatorV2 will support those uses in a future PR. Pull Request resolved: https://github.com/facebook/rocksdb/pull/4649 Differential Revision: D13146964 Pulled By: abhimadan fbshipit-source-id: be29a4c020fc440500c137216fcc1cf529571eb3main
parent
ed5aec5ba3
commit
457f77b9ff
@ -0,0 +1,195 @@ |
||||
// Copyright (c) 2018-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).
|
||||
|
||||
#include "db/range_del_aggregator_v2.h" |
||||
|
||||
#include "db/compaction_iteration_stats.h" |
||||
#include "db/dbformat.h" |
||||
#include "db/pinned_iterators_manager.h" |
||||
#include "db/range_del_aggregator.h" |
||||
#include "db/range_tombstone_fragmenter.h" |
||||
#include "db/version_edit.h" |
||||
#include "include/rocksdb/comparator.h" |
||||
#include "include/rocksdb/types.h" |
||||
#include "table/internal_iterator.h" |
||||
#include "table/scoped_arena_iterator.h" |
||||
#include "table/table_builder.h" |
||||
#include "util/heap.h" |
||||
#include "util/kv_map.h" |
||||
#include "util/vector_iterator.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
TruncatedRangeDelIterator::TruncatedRangeDelIterator( |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> iter, |
||||
const InternalKeyComparator* icmp, const InternalKey* smallest, |
||||
const InternalKey* largest) |
||||
: iter_(std::move(iter)), icmp_(icmp) { |
||||
if (smallest != nullptr) { |
||||
pinned_bounds_.emplace_back(); |
||||
auto& parsed_smallest = pinned_bounds_.back(); |
||||
if (!ParseInternalKey(smallest->Encode(), &parsed_smallest)) { |
||||
assert(false); |
||||
} |
||||
smallest_ = &parsed_smallest; |
||||
} |
||||
if (largest != nullptr) { |
||||
pinned_bounds_.emplace_back(); |
||||
auto& parsed_largest = pinned_bounds_.back(); |
||||
if (!ParseInternalKey(largest->Encode(), &parsed_largest)) { |
||||
assert(false); |
||||
} |
||||
if (parsed_largest.type == kTypeRangeDeletion && |
||||
parsed_largest.sequence == kMaxSequenceNumber) { |
||||
// The file boundary has been artificially extended by a range tombstone.
|
||||
// We do not need to adjust largest to properly truncate range
|
||||
// tombstones that extend past the boundary.
|
||||
} else if (parsed_largest.sequence == 0) { |
||||
// The largest key in the sstable has a sequence number of 0. Since we
|
||||
// guarantee that no internal keys with the same user key and sequence
|
||||
// number can exist in a DB, we know that the largest key in this sstable
|
||||
// cannot exist as the smallest key in the next sstable. This further
|
||||
// implies that no range tombstone in this sstable covers largest;
|
||||
// otherwise, the file boundary would have been artificially extended.
|
||||
//
|
||||
// Therefore, we will never truncate a range tombstone at largest, so we
|
||||
// can leave it unchanged.
|
||||
} else { |
||||
// The same user key may straddle two sstable boundaries. To ensure that
|
||||
// the truncated end key can cover the largest key in this sstable, reduce
|
||||
// its sequence number by 1.
|
||||
parsed_largest.sequence -= 1; |
||||
} |
||||
largest_ = &parsed_largest; |
||||
} |
||||
} |
||||
|
||||
bool TruncatedRangeDelIterator::Valid() const { |
||||
return iter_->Valid() && |
||||
(smallest_ == nullptr || |
||||
icmp_->Compare(*smallest_, iter_->parsed_end_key()) < 0) && |
||||
(largest_ == nullptr || |
||||
icmp_->Compare(iter_->parsed_start_key(), *largest_) < 0); |
||||
} |
||||
|
||||
void TruncatedRangeDelIterator::Next() { iter_->TopNext(); } |
||||
|
||||
void TruncatedRangeDelIterator::Prev() { iter_->TopPrev(); } |
||||
|
||||
// NOTE: target is a user key
|
||||
void TruncatedRangeDelIterator::Seek(const Slice& target) { |
||||
if (largest_ != nullptr && |
||||
icmp_->Compare(*largest_, ParsedInternalKey(target, kMaxSequenceNumber, |
||||
kTypeRangeDeletion)) <= 0) { |
||||
iter_->Invalidate(); |
||||
return; |
||||
} |
||||
iter_->Seek(target); |
||||
} |
||||
|
||||
// NOTE: target is a user key
|
||||
void TruncatedRangeDelIterator::SeekForPrev(const Slice& target) { |
||||
if (smallest_ != nullptr && |
||||
icmp_->Compare(ParsedInternalKey(target, 0, kTypeRangeDeletion), |
||||
*smallest_) < 0) { |
||||
iter_->Invalidate(); |
||||
return; |
||||
} |
||||
iter_->SeekForPrev(target); |
||||
} |
||||
|
||||
void TruncatedRangeDelIterator::SeekToFirst() { iter_->SeekToTopFirst(); } |
||||
|
||||
void TruncatedRangeDelIterator::SeekToLast() { iter_->SeekToTopLast(); } |
||||
|
||||
RangeDelAggregatorV2::RangeDelAggregatorV2(const InternalKeyComparator* icmp, |
||||
SequenceNumber upper_bound) |
||||
: icmp_(icmp), upper_bound_(upper_bound) {} |
||||
|
||||
void RangeDelAggregatorV2::AddTombstones( |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter, |
||||
const InternalKey* smallest, const InternalKey* largest) { |
||||
if (input_iter == nullptr || input_iter->empty()) { |
||||
return; |
||||
} |
||||
if (wrapped_range_del_agg != nullptr) { |
||||
wrapped_range_del_agg->AddTombstones(std::move(input_iter), smallest, |
||||
largest); |
||||
// TODO: this eats the status of the wrapped call; may want to propagate it
|
||||
return; |
||||
} |
||||
iters_.emplace_back(new TruncatedRangeDelIterator(std::move(input_iter), |
||||
icmp_, smallest, largest)); |
||||
} |
||||
|
||||
void RangeDelAggregatorV2::AddUnfragmentedTombstones( |
||||
std::unique_ptr<InternalIterator> input_iter) { |
||||
assert(wrapped_range_del_agg == nullptr); |
||||
if (input_iter == nullptr) { |
||||
return; |
||||
} |
||||
pinned_fragments_.emplace_back(new FragmentedRangeTombstoneList( |
||||
std::move(input_iter), *icmp_, false /* one_time_use */)); |
||||
auto fragmented_iter = new FragmentedRangeTombstoneIterator( |
||||
pinned_fragments_.back().get(), upper_bound_, *icmp_); |
||||
AddTombstones( |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(fragmented_iter)); |
||||
} |
||||
|
||||
bool RangeDelAggregatorV2::ShouldDelete(const ParsedInternalKey& parsed, |
||||
RangeDelPositioningMode mode) { |
||||
if (wrapped_range_del_agg != nullptr) { |
||||
return wrapped_range_del_agg->ShouldDelete(parsed, mode); |
||||
} |
||||
// TODO: avoid re-seeking every call
|
||||
for (auto& iter : iters_) { |
||||
iter->Seek(parsed.user_key); |
||||
if (iter->Valid() && icmp_->Compare(iter->start_key(), parsed) <= 0 && |
||||
iter->seq() > parsed.sequence) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
bool RangeDelAggregatorV2::IsRangeOverlapped(const Slice& start, |
||||
const Slice& end) { |
||||
assert(wrapped_range_del_agg == nullptr); |
||||
|
||||
// Set the internal start/end keys so that:
|
||||
// - if start_ikey has the same user key and sequence number as the current
|
||||
// end key, start_ikey will be considered greater; and
|
||||
// - if end_ikey has the same user key and sequence number as the current
|
||||
// start key, end_ikey will be considered greater.
|
||||
ParsedInternalKey start_ikey(start, kMaxSequenceNumber, |
||||
static_cast<ValueType>(0)); |
||||
ParsedInternalKey end_ikey(end, 0, static_cast<ValueType>(0)); |
||||
for (auto& iter : iters_) { |
||||
bool checked_candidate_tombstones = false; |
||||
for (iter->SeekForPrev(start); |
||||
iter->Valid() && icmp_->Compare(iter->start_key(), end_ikey) <= 0; |
||||
iter->Next()) { |
||||
checked_candidate_tombstones = true; |
||||
if (icmp_->Compare(start_ikey, iter->end_key()) < 0 && |
||||
icmp_->Compare(iter->start_key(), end_ikey) <= 0) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
if (!checked_candidate_tombstones) { |
||||
// Do an additional check for when the end of the range is the begin key
|
||||
// of a tombstone, which we missed earlier since SeekForPrev'ing to the
|
||||
// start was invalid.
|
||||
iter->SeekForPrev(end); |
||||
if (iter->Valid() && icmp_->Compare(start_ikey, iter->end_key()) < 0 && |
||||
icmp_->Compare(iter->start_key(), end_ikey) <= 0) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
} // namespace rocksdb
|
@ -0,0 +1,134 @@ |
||||
// Copyright (c) 2018-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 <list> |
||||
#include <map> |
||||
#include <set> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "db/compaction_iteration_stats.h" |
||||
#include "db/dbformat.h" |
||||
#include "db/pinned_iterators_manager.h" |
||||
#include "db/range_del_aggregator.h" |
||||
#include "db/range_tombstone_fragmenter.h" |
||||
#include "db/version_edit.h" |
||||
#include "include/rocksdb/comparator.h" |
||||
#include "include/rocksdb/types.h" |
||||
#include "table/internal_iterator.h" |
||||
#include "table/scoped_arena_iterator.h" |
||||
#include "table/table_builder.h" |
||||
#include "util/heap.h" |
||||
#include "util/kv_map.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
class RangeDelAggregatorV2; |
||||
|
||||
class TruncatedRangeDelIterator { |
||||
public: |
||||
TruncatedRangeDelIterator( |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> iter, |
||||
const InternalKeyComparator* icmp, const InternalKey* smallest, |
||||
const InternalKey* largest); |
||||
|
||||
bool Valid() const; |
||||
|
||||
void Next(); |
||||
void Prev(); |
||||
|
||||
// Seeks to the tombstone with the highest viisble sequence number that covers
|
||||
// target (a user key). If no such tombstone exists, the position will be at
|
||||
// the earliest tombstone that ends after target.
|
||||
void Seek(const Slice& target); |
||||
|
||||
// Seeks to the tombstone with the highest viisble sequence number that covers
|
||||
// target (a user key). If no such tombstone exists, the position will be at
|
||||
// the latest tombstone that starts before target.
|
||||
void SeekForPrev(const Slice& target); |
||||
|
||||
void SeekToFirst(); |
||||
void SeekToLast(); |
||||
|
||||
ParsedInternalKey start_key() const { |
||||
return (smallest_ == nullptr || |
||||
icmp_->Compare(*smallest_, iter_->parsed_start_key()) <= 0) |
||||
? iter_->parsed_start_key() |
||||
: *smallest_; |
||||
} |
||||
|
||||
ParsedInternalKey end_key() const { |
||||
return (largest_ == nullptr || |
||||
icmp_->Compare(iter_->parsed_end_key(), *largest_) <= 0) |
||||
? iter_->parsed_end_key() |
||||
: *largest_; |
||||
} |
||||
|
||||
SequenceNumber seq() const { return iter_->seq(); } |
||||
|
||||
private: |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> iter_; |
||||
const InternalKeyComparator* icmp_; |
||||
const ParsedInternalKey* smallest_ = nullptr; |
||||
const ParsedInternalKey* largest_ = nullptr; |
||||
std::list<ParsedInternalKey> pinned_bounds_; |
||||
}; |
||||
|
||||
class RangeDelAggregatorV2 { |
||||
public: |
||||
RangeDelAggregatorV2(const InternalKeyComparator* icmp, |
||||
SequenceNumber upper_bound); |
||||
|
||||
void AddTombstones( |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter, |
||||
const InternalKey* smallest = nullptr, |
||||
const InternalKey* largest = nullptr); |
||||
|
||||
void AddUnfragmentedTombstones(std::unique_ptr<InternalIterator> input_iter); |
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed, |
||||
RangeDelPositioningMode mode); |
||||
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end); |
||||
|
||||
// TODO: no-op for now, but won't be once ShouldDelete leverages positioning
|
||||
// mode and doesn't re-seek every ShouldDelete
|
||||
void InvalidateRangeDelMapPositions() {} |
||||
|
||||
bool IsEmpty() const { return iters_.empty(); } |
||||
bool AddFile(uint64_t file_number) { |
||||
return files_seen_.insert(file_number).second; |
||||
} |
||||
|
||||
// Adaptor method to pass calls through to an old-style RangeDelAggregator.
|
||||
// Will be removed once this new version supports an iterator that can be used
|
||||
// during flush/compaction.
|
||||
RangeDelAggregator* DelegateToRangeDelAggregator( |
||||
const std::vector<SequenceNumber>& snapshots) { |
||||
wrapped_range_del_agg.reset(new RangeDelAggregator( |
||||
*icmp_, snapshots, true /* collapse_deletions */)); |
||||
return wrapped_range_del_agg.get(); |
||||
} |
||||
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() { |
||||
assert(wrapped_range_del_agg != nullptr); |
||||
return wrapped_range_del_agg->NewIterator(); |
||||
} |
||||
|
||||
private: |
||||
const InternalKeyComparator* icmp_; |
||||
SequenceNumber upper_bound_; |
||||
|
||||
std::vector<std::unique_ptr<TruncatedRangeDelIterator>> iters_; |
||||
std::list<std::unique_ptr<FragmentedRangeTombstoneList>> pinned_fragments_; |
||||
std::set<uint64_t> files_seen_; |
||||
|
||||
// TODO: remove once V2 supports exposing tombstone iterators
|
||||
std::unique_ptr<RangeDelAggregator> wrapped_range_del_agg; |
||||
}; |
||||
|
||||
} // namespace rocksdb
|
@ -0,0 +1,469 @@ |
||||
// Copyright (c) 2018-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).
|
||||
|
||||
#include "db/range_del_aggregator_v2.h" |
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "db/db_test_util.h" |
||||
#include "db/dbformat.h" |
||||
#include "db/range_tombstone_fragmenter.h" |
||||
#include "util/testutil.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
class RangeDelAggregatorV2Test : public testing::Test {}; |
||||
|
||||
namespace { |
||||
|
||||
static auto bytewise_icmp = InternalKeyComparator(BytewiseComparator()); |
||||
|
||||
std::unique_ptr<InternalIterator> MakeRangeDelIter( |
||||
const std::vector<RangeTombstone>& range_dels) { |
||||
std::vector<std::string> keys, values; |
||||
for (const auto& range_del : range_dels) { |
||||
auto key_and_value = range_del.Serialize(); |
||||
keys.push_back(key_and_value.first.Encode().ToString()); |
||||
values.push_back(key_and_value.second.ToString()); |
||||
} |
||||
return std::unique_ptr<test::VectorIterator>( |
||||
new test::VectorIterator(keys, values)); |
||||
} |
||||
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneList>> |
||||
MakeFragmentedTombstoneLists( |
||||
const std::vector<std::vector<RangeTombstone>>& range_dels_list) { |
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneList>> fragment_lists; |
||||
for (const auto& range_dels : range_dels_list) { |
||||
auto range_del_iter = MakeRangeDelIter(range_dels); |
||||
fragment_lists.emplace_back(new FragmentedRangeTombstoneList( |
||||
std::move(range_del_iter), bytewise_icmp, false /* one_time_use */)); |
||||
} |
||||
return fragment_lists; |
||||
} |
||||
|
||||
struct TruncatedIterScanTestCase { |
||||
ParsedInternalKey start; |
||||
ParsedInternalKey end; |
||||
SequenceNumber seq; |
||||
}; |
||||
|
||||
struct TruncatedIterSeekTestCase { |
||||
Slice target; |
||||
ParsedInternalKey start; |
||||
ParsedInternalKey end; |
||||
SequenceNumber seq; |
||||
bool invalid; |
||||
}; |
||||
|
||||
struct ShouldDeleteTestCase { |
||||
ParsedInternalKey lookup_key; |
||||
bool result; |
||||
}; |
||||
|
||||
struct IsRangeOverlappedTestCase { |
||||
Slice start; |
||||
Slice end; |
||||
bool result; |
||||
}; |
||||
|
||||
ParsedInternalKey UncutEndpoint(const Slice& s) { |
||||
return ParsedInternalKey(s, kMaxSequenceNumber, kTypeRangeDeletion); |
||||
} |
||||
|
||||
ParsedInternalKey InternalValue(const Slice& key, SequenceNumber seq) { |
||||
return ParsedInternalKey(key, seq, kTypeValue); |
||||
} |
||||
|
||||
void VerifyIterator( |
||||
TruncatedRangeDelIterator* iter, const InternalKeyComparator& icmp, |
||||
const std::vector<TruncatedIterScanTestCase>& expected_range_dels) { |
||||
// Test forward iteration.
|
||||
iter->SeekToFirst(); |
||||
for (size_t i = 0; i < expected_range_dels.size(); i++, iter->Next()) { |
||||
ASSERT_TRUE(iter->Valid()); |
||||
EXPECT_EQ(0, icmp.Compare(iter->start_key(), expected_range_dels[i].start)); |
||||
EXPECT_EQ(0, icmp.Compare(iter->end_key(), expected_range_dels[i].end)); |
||||
EXPECT_EQ(expected_range_dels[i].seq, iter->seq()); |
||||
} |
||||
EXPECT_FALSE(iter->Valid()); |
||||
|
||||
// Test reverse iteration.
|
||||
iter->SeekToLast(); |
||||
std::vector<TruncatedIterScanTestCase> reverse_expected_range_dels( |
||||
expected_range_dels.rbegin(), expected_range_dels.rend()); |
||||
for (size_t i = 0; i < reverse_expected_range_dels.size(); |
||||
i++, iter->Prev()) { |
||||
ASSERT_TRUE(iter->Valid()); |
||||
EXPECT_EQ(0, icmp.Compare(iter->start_key(), |
||||
reverse_expected_range_dels[i].start)); |
||||
EXPECT_EQ( |
||||
0, icmp.Compare(iter->end_key(), reverse_expected_range_dels[i].end)); |
||||
EXPECT_EQ(reverse_expected_range_dels[i].seq, iter->seq()); |
||||
} |
||||
EXPECT_FALSE(iter->Valid()); |
||||
} |
||||
|
||||
void VerifySeek(TruncatedRangeDelIterator* iter, |
||||
const InternalKeyComparator& icmp, |
||||
const std::vector<TruncatedIterSeekTestCase>& test_cases) { |
||||
for (const auto& test_case : test_cases) { |
||||
iter->Seek(test_case.target); |
||||
if (test_case.invalid) { |
||||
ASSERT_FALSE(iter->Valid()); |
||||
} else { |
||||
ASSERT_TRUE(iter->Valid()); |
||||
EXPECT_EQ(0, icmp.Compare(iter->start_key(), test_case.start)); |
||||
EXPECT_EQ(0, icmp.Compare(iter->end_key(), test_case.end)); |
||||
EXPECT_EQ(test_case.seq, iter->seq()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void VerifySeekForPrev( |
||||
TruncatedRangeDelIterator* iter, const InternalKeyComparator& icmp, |
||||
const std::vector<TruncatedIterSeekTestCase>& test_cases) { |
||||
for (const auto& test_case : test_cases) { |
||||
iter->SeekForPrev(test_case.target); |
||||
if (test_case.invalid) { |
||||
ASSERT_FALSE(iter->Valid()); |
||||
} else { |
||||
ASSERT_TRUE(iter->Valid()); |
||||
EXPECT_EQ(0, icmp.Compare(iter->start_key(), test_case.start)); |
||||
EXPECT_EQ(0, icmp.Compare(iter->end_key(), test_case.end)); |
||||
EXPECT_EQ(test_case.seq, iter->seq()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void VerifyShouldDelete(RangeDelAggregatorV2* range_del_agg, |
||||
const std::vector<ShouldDeleteTestCase>& test_cases) { |
||||
for (const auto& test_case : test_cases) { |
||||
EXPECT_EQ( |
||||
test_case.result, |
||||
range_del_agg->ShouldDelete( |
||||
test_case.lookup_key, RangeDelPositioningMode::kForwardTraversal)); |
||||
} |
||||
for (auto it = test_cases.rbegin(); it != test_cases.rend(); ++it) { |
||||
const auto& test_case = *it; |
||||
EXPECT_EQ( |
||||
test_case.result, |
||||
range_del_agg->ShouldDelete( |
||||
test_case.lookup_key, RangeDelPositioningMode::kBackwardTraversal)); |
||||
} |
||||
} |
||||
|
||||
void VerifyIsRangeOverlapped( |
||||
RangeDelAggregatorV2* range_del_agg, |
||||
const std::vector<IsRangeOverlappedTestCase>& test_cases) { |
||||
for (const auto& test_case : test_cases) { |
||||
EXPECT_EQ(test_case.result, |
||||
range_del_agg->IsRangeOverlapped(test_case.start, test_case.end)); |
||||
} |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, EmptyTruncatedIter) { |
||||
auto range_del_iter = MakeRangeDelIter({}); |
||||
FragmentedRangeTombstoneList fragment_list( |
||||
std::move(range_del_iter), bytewise_icmp, true /* one_time_use */); |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter( |
||||
new FragmentedRangeTombstoneIterator(&fragment_list, kMaxSequenceNumber, |
||||
bytewise_icmp)); |
||||
|
||||
TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr, |
||||
nullptr); |
||||
|
||||
iter.SeekToFirst(); |
||||
ASSERT_FALSE(iter.Valid()); |
||||
|
||||
iter.SeekToLast(); |
||||
ASSERT_FALSE(iter.Valid()); |
||||
} |
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, UntruncatedIter) { |
||||
auto range_del_iter = |
||||
MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); |
||||
FragmentedRangeTombstoneList fragment_list( |
||||
std::move(range_del_iter), bytewise_icmp, false /* one_time_use */); |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter( |
||||
new FragmentedRangeTombstoneIterator(&fragment_list, kMaxSequenceNumber, |
||||
bytewise_icmp)); |
||||
|
||||
TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr, |
||||
nullptr); |
||||
|
||||
VerifyIterator(&iter, bytewise_icmp, |
||||
{{UncutEndpoint("a"), UncutEndpoint("e"), 10}, |
||||
{UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{UncutEndpoint("j"), UncutEndpoint("n"), 4}}); |
||||
|
||||
VerifySeek( |
||||
&iter, bytewise_icmp, |
||||
{{"d", UncutEndpoint("a"), UncutEndpoint("e"), 10}, |
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"ia", UncutEndpoint("j"), UncutEndpoint("n"), 4}, |
||||
{"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}, |
||||
{"", UncutEndpoint("a"), UncutEndpoint("e"), 10}}); |
||||
|
||||
VerifySeekForPrev( |
||||
&iter, bytewise_icmp, |
||||
{{"d", UncutEndpoint("a"), UncutEndpoint("e"), 10}, |
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"n", UncutEndpoint("j"), UncutEndpoint("n"), 4}, |
||||
{"", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}}); |
||||
} |
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, UntruncatedIterWithSnapshot) { |
||||
auto range_del_iter = |
||||
MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); |
||||
FragmentedRangeTombstoneList fragment_list( |
||||
std::move(range_del_iter), bytewise_icmp, false /* one_time_use */); |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter( |
||||
new FragmentedRangeTombstoneIterator(&fragment_list, 9 /* snapshot */, |
||||
bytewise_icmp)); |
||||
|
||||
TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, nullptr, |
||||
nullptr); |
||||
|
||||
VerifyIterator(&iter, bytewise_icmp, |
||||
{{UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{UncutEndpoint("j"), UncutEndpoint("n"), 4}}); |
||||
|
||||
VerifySeek( |
||||
&iter, bytewise_icmp, |
||||
{{"d", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"ia", UncutEndpoint("j"), UncutEndpoint("n"), 4}, |
||||
{"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}, |
||||
{"", UncutEndpoint("e"), UncutEndpoint("g"), 8}}); |
||||
|
||||
VerifySeekForPrev( |
||||
&iter, bytewise_icmp, |
||||
{{"d", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}, |
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"n", UncutEndpoint("j"), UncutEndpoint("n"), 4}, |
||||
{"", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}}); |
||||
} |
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, TruncatedIter) { |
||||
auto range_del_iter = |
||||
MakeRangeDelIter({{"a", "e", 10}, {"e", "g", 8}, {"j", "n", 4}}); |
||||
FragmentedRangeTombstoneList fragment_list( |
||||
std::move(range_del_iter), bytewise_icmp, false /* one_time_use */); |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter( |
||||
new FragmentedRangeTombstoneIterator(&fragment_list, kMaxSequenceNumber, |
||||
bytewise_icmp)); |
||||
|
||||
InternalKey smallest("d", 7, kTypeValue); |
||||
InternalKey largest("m", 9, kTypeValue); |
||||
TruncatedRangeDelIterator iter(std::move(input_iter), &bytewise_icmp, |
||||
&smallest, &largest); |
||||
|
||||
VerifyIterator(&iter, bytewise_icmp, |
||||
{{InternalValue("d", 7), UncutEndpoint("e"), 10}, |
||||
{UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{UncutEndpoint("j"), InternalValue("m", 8), 4}}); |
||||
|
||||
VerifySeek( |
||||
&iter, bytewise_icmp, |
||||
{{"d", InternalValue("d", 7), UncutEndpoint("e"), 10}, |
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"ia", UncutEndpoint("j"), InternalValue("m", 8), 4}, |
||||
{"n", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}, |
||||
{"", InternalValue("d", 7), UncutEndpoint("e"), 10}}); |
||||
|
||||
VerifySeekForPrev( |
||||
&iter, bytewise_icmp, |
||||
{{"d", InternalValue("d", 7), UncutEndpoint("e"), 10}, |
||||
{"e", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"ia", UncutEndpoint("e"), UncutEndpoint("g"), 8}, |
||||
{"n", UncutEndpoint("j"), InternalValue("m", 8), 4}, |
||||
{"", UncutEndpoint(""), UncutEndpoint(""), 0, true /* invalid */}}); |
||||
} |
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, SingleIterInAggregator) { |
||||
auto range_del_iter = MakeRangeDelIter({{"a", "e", 10}, {"c", "g", 8}}); |
||||
FragmentedRangeTombstoneList fragment_list( |
||||
std::move(range_del_iter), bytewise_icmp, false /* one_time_use */); |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter( |
||||
new FragmentedRangeTombstoneIterator(&fragment_list, kMaxSequenceNumber, |
||||
bytewise_icmp)); |
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, kMaxSequenceNumber); |
||||
range_del_agg.AddTombstones(std::move(input_iter)); |
||||
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), false}, |
||||
{InternalValue("b", 9), true}, |
||||
{InternalValue("d", 9), true}, |
||||
{InternalValue("e", 7), true}, |
||||
{InternalValue("g", 7), false}}); |
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, |
||||
{"_", "a", true}, |
||||
{"a", "c", true}, |
||||
{"d", "f", true}, |
||||
{"g", "l", false}}); |
||||
} |
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, MultipleItersInAggregator) { |
||||
auto fragment_lists = MakeFragmentedTombstoneLists( |
||||
{{{"a", "e", 10}, {"c", "g", 8}}, |
||||
{{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); |
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, kMaxSequenceNumber); |
||||
for (const auto& fragment_list : fragment_lists) { |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter( |
||||
new FragmentedRangeTombstoneIterator( |
||||
fragment_list.get(), kMaxSequenceNumber, bytewise_icmp)); |
||||
range_del_agg.AddTombstones(std::move(input_iter)); |
||||
} |
||||
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), true}, |
||||
{InternalValue("b", 19), false}, |
||||
{InternalValue("b", 9), true}, |
||||
{InternalValue("d", 9), true}, |
||||
{InternalValue("e", 7), true}, |
||||
{InternalValue("g", 7), false}, |
||||
{InternalValue("h", 24), true}, |
||||
{InternalValue("i", 24), false}, |
||||
{InternalValue("ii", 14), true}, |
||||
{InternalValue("j", 14), false}}); |
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, |
||||
{"_", "a", true}, |
||||
{"a", "c", true}, |
||||
{"d", "f", true}, |
||||
{"g", "l", true}, |
||||
{"x", "y", false}}); |
||||
} |
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, MultipleItersInAggregatorWithUpperBound) { |
||||
auto fragment_lists = MakeFragmentedTombstoneLists( |
||||
{{{"a", "e", 10}, {"c", "g", 8}}, |
||||
{{"a", "b", 20}, {"h", "i", 25}, {"ii", "j", 15}}}); |
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, 19); |
||||
for (const auto& fragment_list : fragment_lists) { |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter( |
||||
new FragmentedRangeTombstoneIterator(fragment_list.get(), |
||||
19 /* snapshot */, bytewise_icmp)); |
||||
range_del_agg.AddTombstones(std::move(input_iter)); |
||||
} |
||||
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 19), false}, |
||||
{InternalValue("a", 9), true}, |
||||
{InternalValue("b", 9), true}, |
||||
{InternalValue("d", 9), true}, |
||||
{InternalValue("e", 7), true}, |
||||
{InternalValue("g", 7), false}, |
||||
{InternalValue("h", 24), false}, |
||||
{InternalValue("i", 24), false}, |
||||
{InternalValue("ii", 14), true}, |
||||
{InternalValue("j", 14), false}}); |
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, |
||||
{"_", "a", true}, |
||||
{"a", "c", true}, |
||||
{"d", "f", true}, |
||||
{"g", "l", true}, |
||||
{"x", "y", false}}); |
||||
} |
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, MultipleTruncatedItersInAggregator) { |
||||
auto fragment_lists = MakeFragmentedTombstoneLists( |
||||
{{{"a", "z", 10}}, {{"a", "z", 10}}, {{"a", "z", 10}}}); |
||||
std::vector<std::pair<InternalKey, InternalKey>> iter_bounds = { |
||||
{InternalKey("a", 4, kTypeValue), |
||||
InternalKey("m", kMaxSequenceNumber, kTypeRangeDeletion)}, |
||||
{InternalKey("m", 20, kTypeValue), |
||||
InternalKey("x", kMaxSequenceNumber, kTypeRangeDeletion)}, |
||||
{InternalKey("x", 5, kTypeValue), InternalKey("zz", 30, kTypeValue)}}; |
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, 19); |
||||
for (size_t i = 0; i < fragment_lists.size(); i++) { |
||||
const auto& fragment_list = fragment_lists[i]; |
||||
const auto& bounds = iter_bounds[i]; |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter( |
||||
new FragmentedRangeTombstoneIterator(fragment_list.get(), |
||||
19 /* snapshot */, bytewise_icmp)); |
||||
range_del_agg.AddTombstones(std::move(input_iter), &bounds.first, |
||||
&bounds.second); |
||||
} |
||||
|
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 10), false}, |
||||
{InternalValue("a", 9), false}, |
||||
{InternalValue("a", 4), true}, |
||||
{InternalValue("m", 10), false}, |
||||
{InternalValue("m", 9), true}, |
||||
{InternalValue("x", 10), false}, |
||||
{InternalValue("x", 9), false}, |
||||
{InternalValue("x", 5), true}, |
||||
{InternalValue("z", 9), false}}); |
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, |
||||
{"_", "a", true}, |
||||
{"a", "n", true}, |
||||
{"l", "x", true}, |
||||
{"w", "z", true}, |
||||
{"zzz", "zz", false}, |
||||
{"zz", "zzz", false}}); |
||||
} |
||||
|
||||
TEST_F(RangeDelAggregatorV2Test, MultipleTruncatedItersInAggregatorSameLevel) { |
||||
auto fragment_lists = MakeFragmentedTombstoneLists( |
||||
{{{"a", "z", 10}}, {{"a", "z", 10}}, {{"a", "z", 10}}}); |
||||
std::vector<std::pair<InternalKey, InternalKey>> iter_bounds = { |
||||
{InternalKey("a", 4, kTypeValue), |
||||
InternalKey("m", kMaxSequenceNumber, kTypeRangeDeletion)}, |
||||
{InternalKey("m", 20, kTypeValue), |
||||
InternalKey("x", kMaxSequenceNumber, kTypeRangeDeletion)}, |
||||
{InternalKey("x", 5, kTypeValue), InternalKey("zz", 30, kTypeValue)}}; |
||||
|
||||
RangeDelAggregatorV2 range_del_agg(&bytewise_icmp, 19); |
||||
|
||||
auto add_iter_to_agg = [&](size_t i) { |
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> input_iter( |
||||
new FragmentedRangeTombstoneIterator(fragment_lists[i].get(), |
||||
19 /* snapshot */, bytewise_icmp)); |
||||
range_del_agg.AddTombstones(std::move(input_iter), &iter_bounds[i].first, |
||||
&iter_bounds[i].second); |
||||
}; |
||||
|
||||
add_iter_to_agg(0); |
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("a", 10), false}, |
||||
{InternalValue("a", 9), false}, |
||||
{InternalValue("a", 4), true}}); |
||||
|
||||
add_iter_to_agg(1); |
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("m", 10), false}, |
||||
{InternalValue("m", 9), true}}); |
||||
|
||||
add_iter_to_agg(2); |
||||
VerifyShouldDelete(&range_del_agg, {{InternalValue("x", 10), false}, |
||||
{InternalValue("x", 9), false}, |
||||
{InternalValue("x", 5), true}, |
||||
{InternalValue("z", 9), false}}); |
||||
|
||||
VerifyIsRangeOverlapped(&range_del_agg, {{"", "_", false}, |
||||
{"_", "a", true}, |
||||
{"a", "n", true}, |
||||
{"l", "x", true}, |
||||
{"w", "z", true}, |
||||
{"zzz", "zz", false}, |
||||
{"zz", "zzz", false}}); |
||||
} |
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue