Add an internal iterator that can measure the inflow of blobs (#8443)
Summary: Follow-up to https://github.com/facebook/rocksdb/issues/8426 . The patch adds a new kind of `InternalIterator` that wraps another one and passes each key-value encountered to `BlobGarbageMeter` as inflow. This iterator will be used as an input iterator for compactions when the input SSTs reference blob files. Pull Request resolved: https://github.com/facebook/rocksdb/pull/8443 Test Plan: `make check` Reviewed By: jay-zhuang Differential Revision: D29311987 Pulled By: ltamasi fbshipit-source-id: b4493b4c0c0c2e3c2ecc33c8969a5ef02de5d9d8main
parent
3f20925dc4
commit
6adc39e1bf
@ -0,0 +1,142 @@ |
||||
// 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 <cassert> |
||||
|
||||
#include "db/blob/blob_garbage_meter.h" |
||||
#include "rocksdb/rocksdb_namespace.h" |
||||
#include "rocksdb/status.h" |
||||
#include "table/internal_iterator.h" |
||||
|
||||
namespace ROCKSDB_NAMESPACE { |
||||
|
||||
// An internal iterator that passes each key-value encountered to
|
||||
// BlobGarbageMeter as inflow in order to measure the total number and size of
|
||||
// blobs in the compaction input on a per-blob file basis.
|
||||
class BlobCountingIterator : public InternalIterator { |
||||
public: |
||||
BlobCountingIterator(InternalIterator* iter, |
||||
BlobGarbageMeter* blob_garbage_meter) |
||||
: iter_(iter), blob_garbage_meter_(blob_garbage_meter) { |
||||
assert(iter_); |
||||
assert(blob_garbage_meter_); |
||||
|
||||
UpdateAndCountBlobIfNeeded(); |
||||
} |
||||
|
||||
bool Valid() const override { return iter_->Valid() && status_.ok(); } |
||||
|
||||
void SeekToFirst() override { |
||||
iter_->SeekToFirst(); |
||||
UpdateAndCountBlobIfNeeded(); |
||||
} |
||||
|
||||
void SeekToLast() override { |
||||
iter_->SeekToLast(); |
||||
UpdateAndCountBlobIfNeeded(); |
||||
} |
||||
|
||||
void Seek(const Slice& target) override { |
||||
iter_->Seek(target); |
||||
UpdateAndCountBlobIfNeeded(); |
||||
} |
||||
|
||||
void SeekForPrev(const Slice& target) override { |
||||
iter_->SeekForPrev(target); |
||||
UpdateAndCountBlobIfNeeded(); |
||||
} |
||||
|
||||
void Next() override { |
||||
assert(Valid()); |
||||
|
||||
iter_->Next(); |
||||
UpdateAndCountBlobIfNeeded(); |
||||
} |
||||
|
||||
bool NextAndGetResult(IterateResult* result) override { |
||||
assert(Valid()); |
||||
|
||||
const bool res = iter_->NextAndGetResult(result); |
||||
UpdateAndCountBlobIfNeeded(); |
||||
return res; |
||||
} |
||||
|
||||
void Prev() override { |
||||
assert(Valid()); |
||||
|
||||
iter_->Prev(); |
||||
UpdateAndCountBlobIfNeeded(); |
||||
} |
||||
|
||||
Slice key() const override { |
||||
assert(Valid()); |
||||
return iter_->key(); |
||||
} |
||||
|
||||
Slice user_key() const override { |
||||
assert(Valid()); |
||||
return iter_->user_key(); |
||||
} |
||||
|
||||
Slice value() const override { |
||||
assert(Valid()); |
||||
return iter_->value(); |
||||
} |
||||
|
||||
Status status() const override { return status_; } |
||||
|
||||
bool PrepareValue() override { |
||||
assert(Valid()); |
||||
return iter_->PrepareValue(); |
||||
} |
||||
|
||||
bool MayBeOutOfLowerBound() override { |
||||
assert(Valid()); |
||||
return iter_->MayBeOutOfLowerBound(); |
||||
} |
||||
|
||||
IterBoundCheck UpperBoundCheckResult() override { |
||||
assert(Valid()); |
||||
return iter_->UpperBoundCheckResult(); |
||||
} |
||||
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override { |
||||
iter_->SetPinnedItersMgr(pinned_iters_mgr); |
||||
} |
||||
|
||||
bool IsKeyPinned() const override { |
||||
assert(Valid()); |
||||
return iter_->IsKeyPinned(); |
||||
} |
||||
|
||||
bool IsValuePinned() const override { |
||||
assert(Valid()); |
||||
return iter_->IsValuePinned(); |
||||
} |
||||
|
||||
Status GetProperty(std::string prop_name, std::string* prop) override { |
||||
return iter_->GetProperty(prop_name, prop); |
||||
} |
||||
|
||||
private: |
||||
void UpdateAndCountBlobIfNeeded() { |
||||
assert(!iter_->Valid() || iter_->status().ok()); |
||||
|
||||
if (!iter_->Valid()) { |
||||
status_ = iter_->status(); |
||||
return; |
||||
} |
||||
|
||||
status_ = blob_garbage_meter_->ProcessInFlow(key(), value()); |
||||
} |
||||
|
||||
InternalIterator* iter_; |
||||
BlobGarbageMeter* blob_garbage_meter_; |
||||
Status status_; |
||||
}; |
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
@ -0,0 +1,325 @@ |
||||
// 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).
|
||||
|
||||
#include "db/blob/blob_counting_iterator.h" |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "db/blob/blob_garbage_meter.h" |
||||
#include "db/blob/blob_index.h" |
||||
#include "db/blob/blob_log_format.h" |
||||
#include "db/dbformat.h" |
||||
#include "test_util/testharness.h" |
||||
#include "test_util/testutil.h" |
||||
|
||||
namespace ROCKSDB_NAMESPACE { |
||||
|
||||
void CheckInFlow(const BlobGarbageMeter& blob_garbage_meter, |
||||
uint64_t blob_file_number, uint64_t count, uint64_t bytes) { |
||||
const auto& flows = blob_garbage_meter.flows(); |
||||
|
||||
const auto it = flows.find(blob_file_number); |
||||
if (it == flows.end()) { |
||||
ASSERT_EQ(count, 0); |
||||
ASSERT_EQ(bytes, 0); |
||||
return; |
||||
} |
||||
|
||||
const auto& in = it->second.GetInFlow(); |
||||
|
||||
ASSERT_EQ(in.GetCount(), count); |
||||
ASSERT_EQ(in.GetBytes(), bytes); |
||||
} |
||||
|
||||
TEST(BlobCountingIteratorTest, CountBlobs) { |
||||
// Note: the input consists of three key-values: two are blob references to
|
||||
// different blob files, while the third one is a plain value.
|
||||
constexpr char user_key0[] = "key0"; |
||||
constexpr char user_key1[] = "key1"; |
||||
constexpr char user_key2[] = "key2"; |
||||
|
||||
const std::vector<std::string> keys{ |
||||
test::KeyStr(user_key0, 1, kTypeBlobIndex), |
||||
test::KeyStr(user_key1, 2, kTypeBlobIndex), |
||||
test::KeyStr(user_key2, 3, kTypeValue)}; |
||||
|
||||
constexpr uint64_t first_blob_file_number = 4; |
||||
constexpr uint64_t first_offset = 1000; |
||||
constexpr uint64_t first_size = 2000; |
||||
|
||||
std::string first_blob_index; |
||||
BlobIndex::EncodeBlob(&first_blob_index, first_blob_file_number, first_offset, |
||||
first_size, kNoCompression); |
||||
|
||||
constexpr uint64_t second_blob_file_number = 6; |
||||
constexpr uint64_t second_offset = 2000; |
||||
constexpr uint64_t second_size = 4000; |
||||
|
||||
std::string second_blob_index; |
||||
BlobIndex::EncodeBlob(&second_blob_index, second_blob_file_number, |
||||
second_offset, second_size, kNoCompression); |
||||
|
||||
const std::vector<std::string> values{first_blob_index, second_blob_index, |
||||
"raw_value"}; |
||||
|
||||
assert(keys.size() == values.size()); |
||||
|
||||
test::VectorIterator input(keys, values); |
||||
BlobGarbageMeter blob_garbage_meter; |
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter); |
||||
|
||||
constexpr uint64_t first_expected_bytes = |
||||
first_size + |
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(user_key0) - 1); |
||||
constexpr uint64_t second_expected_bytes = |
||||
second_size + |
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(user_key1) - 1); |
||||
|
||||
// Call SeekToFirst and iterate forward
|
||||
blob_counter.SeekToFirst(); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[0]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key0); |
||||
ASSERT_EQ(blob_counter.value(), values[0]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1, |
||||
first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 0, 0); |
||||
|
||||
blob_counter.Next(); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[1]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key1); |
||||
ASSERT_EQ(blob_counter.value(), values[1]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1, |
||||
first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1, |
||||
second_expected_bytes); |
||||
|
||||
blob_counter.Next(); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[2]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key2); |
||||
ASSERT_EQ(blob_counter.value(), values[2]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1, |
||||
first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1, |
||||
second_expected_bytes); |
||||
|
||||
blob_counter.Next(); |
||||
ASSERT_FALSE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1, |
||||
first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1, |
||||
second_expected_bytes); |
||||
|
||||
// Do it again using NextAndGetResult
|
||||
blob_counter.SeekToFirst(); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[0]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key0); |
||||
ASSERT_EQ(blob_counter.value(), values[0]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2, |
||||
2 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1, |
||||
second_expected_bytes); |
||||
|
||||
{ |
||||
IterateResult result; |
||||
ASSERT_TRUE(blob_counter.NextAndGetResult(&result)); |
||||
ASSERT_EQ(result.key, keys[1]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key1); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[1]); |
||||
ASSERT_EQ(blob_counter.value(), values[1]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2, |
||||
2 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2, |
||||
2 * second_expected_bytes); |
||||
} |
||||
|
||||
{ |
||||
IterateResult result; |
||||
ASSERT_TRUE(blob_counter.NextAndGetResult(&result)); |
||||
ASSERT_EQ(result.key, keys[2]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key2); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[2]); |
||||
ASSERT_EQ(blob_counter.value(), values[2]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2, |
||||
2 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2, |
||||
2 * second_expected_bytes); |
||||
} |
||||
|
||||
{ |
||||
IterateResult result; |
||||
ASSERT_FALSE(blob_counter.NextAndGetResult(&result)); |
||||
ASSERT_FALSE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2, |
||||
2 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2, |
||||
2 * second_expected_bytes); |
||||
} |
||||
|
||||
// Call SeekToLast and iterate backward
|
||||
blob_counter.SeekToLast(); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[2]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key2); |
||||
ASSERT_EQ(blob_counter.value(), values[2]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2, |
||||
2 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2, |
||||
2 * second_expected_bytes); |
||||
|
||||
blob_counter.Prev(); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[1]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key1); |
||||
ASSERT_EQ(blob_counter.value(), values[1]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2, |
||||
2 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3, |
||||
3 * second_expected_bytes); |
||||
|
||||
blob_counter.Prev(); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[0]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key0); |
||||
ASSERT_EQ(blob_counter.value(), values[0]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 3, |
||||
3 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3, |
||||
3 * second_expected_bytes); |
||||
|
||||
blob_counter.Prev(); |
||||
ASSERT_FALSE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 3, |
||||
3 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3, |
||||
3 * second_expected_bytes); |
||||
|
||||
// Call Seek for all keys (plus one that's greater than all of them)
|
||||
blob_counter.Seek(keys[0]); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[0]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key0); |
||||
ASSERT_EQ(blob_counter.value(), values[0]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4, |
||||
4 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3, |
||||
3 * second_expected_bytes); |
||||
|
||||
blob_counter.Seek(keys[1]); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[1]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key1); |
||||
ASSERT_EQ(blob_counter.value(), values[1]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4, |
||||
4 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4, |
||||
4 * second_expected_bytes); |
||||
|
||||
blob_counter.Seek(keys[2]); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[2]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key2); |
||||
ASSERT_EQ(blob_counter.value(), values[2]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4, |
||||
4 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4, |
||||
4 * second_expected_bytes); |
||||
|
||||
blob_counter.Seek("zzz"); |
||||
ASSERT_FALSE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4, |
||||
4 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4, |
||||
4 * second_expected_bytes); |
||||
|
||||
// Call SeekForPrev for all keys (plus one that's less than all of them)
|
||||
blob_counter.SeekForPrev("aaa"); |
||||
ASSERT_FALSE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4, |
||||
4 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4, |
||||
4 * second_expected_bytes); |
||||
|
||||
blob_counter.SeekForPrev(keys[0]); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[0]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key0); |
||||
ASSERT_EQ(blob_counter.value(), values[0]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 5, |
||||
5 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4, |
||||
4 * second_expected_bytes); |
||||
|
||||
blob_counter.SeekForPrev(keys[1]); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[1]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key1); |
||||
ASSERT_EQ(blob_counter.value(), values[1]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 5, |
||||
5 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 5, |
||||
5 * second_expected_bytes); |
||||
|
||||
blob_counter.SeekForPrev(keys[2]); |
||||
ASSERT_TRUE(blob_counter.Valid()); |
||||
ASSERT_OK(blob_counter.status()); |
||||
ASSERT_EQ(blob_counter.key(), keys[2]); |
||||
ASSERT_EQ(blob_counter.user_key(), user_key2); |
||||
ASSERT_EQ(blob_counter.value(), values[2]); |
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 5, |
||||
5 * first_expected_bytes); |
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 5, |
||||
5 * second_expected_bytes); |
||||
} |
||||
|
||||
TEST(BlobCountingIteratorTest, CorruptBlobIndex) { |
||||
const std::vector<std::string> keys{ |
||||
test::KeyStr("user_key", 1, kTypeBlobIndex)}; |
||||
const std::vector<std::string> values{"i_am_not_a_blob_index"}; |
||||
|
||||
assert(keys.size() == values.size()); |
||||
|
||||
test::VectorIterator input(keys, values); |
||||
BlobGarbageMeter blob_garbage_meter; |
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter); |
||||
|
||||
blob_counter.SeekToFirst(); |
||||
ASSERT_FALSE(blob_counter.Valid()); |
||||
ASSERT_NOK(blob_counter.status()); |
||||
} |
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue