Block access tracing: Trace referenced key for Get on non-data blocks. (#5548)

Summary:
This PR traces the referenced key for Get for all types of blocks. This is useful when evaluating hybrid row-block caches.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/5548

Test Plan: make clean && USE_CLANG=1 make check -j32

Differential Revision: D16157979

Pulled By: HaoyuHuang

fbshipit-source-id: f6327411c9deb74e35e22a35f66cdbae09ab9d87
main
haoyuhuang 5 years ago committed by Facebook Github Bot
parent 22ce462450
commit 8a008d4170
  1. 58
      table/block_based/block_based_table_reader.cc
  2. 276
      table/table_test.cc
  3. 4
      tools/block_cache_trace_analyzer.h
  4. 34
      trace_replay/block_cache_tracer.cc
  5. 40
      trace_replay/block_cache_tracer.h
  6. 9
      trace_replay/block_cache_tracer_test.cc
  7. 19
      utilities/simulator_cache/cache_simulator.cc
  8. 1
      utilities/simulator_cache/cache_simulator.h
  9. 22
      utilities/simulator_cache/cache_simulator_test.cc

@ -1983,10 +1983,12 @@ CachableEntry<UncompressionDict> BlockBasedTable::GetUncompressionDict(
/*block_size=*/usage, rep_->cf_id_for_tracing(), /*block_size=*/usage, rep_->cf_id_for_tracing(),
/*cf_name=*/"", rep_->level_for_tracing(), /*cf_name=*/"", rep_->level_for_tracing(),
rep_->sst_number_for_tracing(), lookup_context->caller, is_cache_hit, rep_->sst_number_for_tracing(), lookup_context->caller, is_cache_hit,
/*no_insert=*/no_io, lookup_context->get_id); /*no_insert=*/no_io, lookup_context->get_id,
lookup_context->get_from_user_specified_snapshot,
/*referenced_key=*/"");
block_cache_tracer_->WriteBlockAccess(access_record, cache_key, block_cache_tracer_->WriteBlockAccess(access_record, cache_key,
rep_->cf_name_for_tracing(), rep_->cf_name_for_tracing(),
/*referenced_key=*/nullptr); lookup_context->referenced_key);
} }
return {dict, cache_handle ? rep_->table_options.block_cache.get() : nullptr, return {dict, cache_handle ? rep_->table_options.block_cache.get() : nullptr,
cache_handle, false /* own_value */}; cache_handle, false /* own_value */};
@ -2237,7 +2239,6 @@ Status BlockBasedTable::MaybeReadBlockAndLoadToCache(
Slice key /* key to the block cache */; Slice key /* key to the block cache */;
Slice ckey /* key to the compressed block cache */; Slice ckey /* key to the compressed block cache */;
bool is_cache_hit = false; bool is_cache_hit = false;
bool no_insert = true;
if (block_cache != nullptr || block_cache_compressed != nullptr) { if (block_cache != nullptr || block_cache_compressed != nullptr) {
// create key for block cache // create key for block cache
if (block_cache != nullptr) { if (block_cache != nullptr) {
@ -2265,7 +2266,6 @@ Status BlockBasedTable::MaybeReadBlockAndLoadToCache(
// Can't find the block from the cache. If I/O is allowed, read from the // Can't find the block from the cache. If I/O is allowed, read from the
// file. // file.
if (block_entry->GetValue() == nullptr && !no_io && ro.fill_cache) { if (block_entry->GetValue() == nullptr && !no_io && ro.fill_cache) {
no_insert = false;
Statistics* statistics = rep_->ioptions.statistics; Statistics* statistics = rep_->ioptions.statistics;
const bool maybe_compressed = const bool maybe_compressed =
block_type != BlockType::kFilter && rep_->blocks_maybe_compressed; block_type != BlockType::kFilter && rep_->blocks_maybe_compressed;
@ -2332,11 +2332,11 @@ Status BlockBasedTable::MaybeReadBlockAndLoadToCache(
assert(false); assert(false);
break; break;
} }
if (BlockCacheTraceHelper::ShouldTraceReferencedKey( bool no_insert = no_io || !ro.fill_cache;
if (BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(
trace_block_type, lookup_context->caller)) { trace_block_type, lookup_context->caller)) {
// Defer logging the access to Get() and MultiGet() to trace additional // Defer logging the access to Get() and MultiGet() to trace additional
// information, e.g., the referenced key, // information, e.g., referenced_key_exist_in_block.
// referenced_key_exist_in_block.
// Make a copy of the block key here since it will be logged later. // Make a copy of the block key here since it will be logged later.
lookup_context->FillLookupContext( lookup_context->FillLookupContext(
@ -2351,10 +2351,12 @@ Status BlockBasedTable::MaybeReadBlockAndLoadToCache(
/*block_size=*/usage, rep_->cf_id_for_tracing(), /*block_size=*/usage, rep_->cf_id_for_tracing(),
/*cf_name=*/"", rep_->level_for_tracing(), /*cf_name=*/"", rep_->level_for_tracing(),
rep_->sst_number_for_tracing(), lookup_context->caller, is_cache_hit, rep_->sst_number_for_tracing(), lookup_context->caller, is_cache_hit,
no_insert, lookup_context->get_id); no_insert, lookup_context->get_id,
lookup_context->get_from_user_specified_snapshot,
/*referenced_key=*/"");
block_cache_tracer_->WriteBlockAccess(access_record, key, block_cache_tracer_->WriteBlockAccess(access_record, key,
rep_->cf_name_for_tracing(), rep_->cf_name_for_tracing(),
/*referenced_key=*/nullptr); lookup_context->referenced_key);
} }
} }
@ -3288,12 +3290,18 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
// First check the full filter // First check the full filter
// If full filter not useful, Then go into each block // If full filter not useful, Then go into each block
uint64_t tracing_get_id = get_context->get_tracing_get_id(); uint64_t tracing_get_id = get_context->get_tracing_get_id();
BlockCacheLookupContext lookup_context{TableReaderCaller::kUserGet, BlockCacheLookupContext lookup_context{
tracing_get_id}; TableReaderCaller::kUserGet, tracing_get_id,
/*get_from_user_specified_snapshot=*/read_options.snapshot != nullptr};
if (block_cache_tracer_ && block_cache_tracer_->is_tracing_enabled()) {
// Trace the key since it contains both user key and sequence number.
lookup_context.referenced_key = key.ToString();
lookup_context.get_from_user_specified_snapshot =
read_options.snapshot != nullptr;
}
const bool may_match = const bool may_match =
FullFilterKeyMayMatch(read_options, filter, key, no_io, prefix_extractor, FullFilterKeyMayMatch(read_options, filter, key, no_io, prefix_extractor,
get_context, &lookup_context); get_context, &lookup_context);
if (!may_match) { if (!may_match) {
RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_USEFUL); RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_USEFUL);
PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, rep_->level); PERF_COUNTER_BY_LEVEL_ADD(bloom_filter_useful, 1, rep_->level);
@ -3347,7 +3355,9 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
} }
BlockCacheLookupContext lookup_data_block_context{ BlockCacheLookupContext lookup_data_block_context{
TableReaderCaller::kUserGet, tracing_get_id}; TableReaderCaller::kUserGet, tracing_get_id,
/*get_from_user_specified_snapshot=*/read_options.snapshot !=
nullptr};
bool does_referenced_key_exist = false; bool does_referenced_key_exist = false;
DataBlockIter biter; DataBlockIter biter;
uint64_t referenced_data_size = 0; uint64_t referenced_data_size = 0;
@ -3406,7 +3416,7 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
if (does_referenced_key_exist) { if (does_referenced_key_exist) {
referenced_key = biter.key(); referenced_key = biter.key();
} else { } else {
referenced_key = ExtractUserKey(key); referenced_key = key;
} }
BlockCacheTraceRecord access_record( BlockCacheTraceRecord access_record(
rep_->ioptions.env->NowMicros(), rep_->ioptions.env->NowMicros(),
@ -3417,6 +3427,7 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
lookup_data_block_context.is_cache_hit, lookup_data_block_context.is_cache_hit,
lookup_data_block_context.no_insert, lookup_data_block_context.no_insert,
lookup_data_block_context.get_id, lookup_data_block_context.get_id,
lookup_data_block_context.get_from_user_specified_snapshot,
/*referenced_key=*/"", referenced_data_size, /*referenced_key=*/"", referenced_data_size,
lookup_data_block_context.num_keys_in_block, lookup_data_block_context.num_keys_in_block,
does_referenced_key_exist); does_referenced_key_exist);
@ -3460,8 +3471,9 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
if (!sst_file_range.empty() && sst_file_range.begin()->get_context) { if (!sst_file_range.empty() && sst_file_range.begin()->get_context) {
tracing_mget_id = sst_file_range.begin()->get_context->get_tracing_get_id(); tracing_mget_id = sst_file_range.begin()->get_context->get_tracing_get_id();
} }
BlockCacheLookupContext lookup_context{TableReaderCaller::kUserMultiGet, BlockCacheLookupContext lookup_context{
tracing_mget_id}; TableReaderCaller::kUserMultiGet, tracing_mget_id,
/*get_from_user_specified_snapshot=*/read_options.snapshot != nullptr};
FullFilterKeysMayMatch(read_options, filter, &sst_file_range, no_io, FullFilterKeysMayMatch(read_options, filter, &sst_file_range, no_io,
prefix_extractor, &lookup_context); prefix_extractor, &lookup_context);
@ -3492,11 +3504,8 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
{ {
MultiGetRange data_block_range(sst_file_range, sst_file_range.begin(), MultiGetRange data_block_range(sst_file_range, sst_file_range.begin(),
sst_file_range.end()); sst_file_range.end());
BlockCacheLookupContext lookup_compression_dict_context( auto uncompression_dict_storage = GetUncompressionDict(
TableReaderCaller::kUserMultiGet); nullptr, no_io, sst_file_range.begin()->get_context, &lookup_context);
auto uncompression_dict_storage = GetUncompressionDict(nullptr, no_io,
sst_file_range.begin()->get_context,
&lookup_compression_dict_context);
const UncompressionDict& uncompression_dict = const UncompressionDict& uncompression_dict =
uncompression_dict_storage.GetValue() == nullptr uncompression_dict_storage.GetValue() == nullptr
? UncompressionDict::GetEmptyDict() ? UncompressionDict::GetEmptyDict()
@ -3591,7 +3600,9 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
uint64_t referenced_data_size = 0; uint64_t referenced_data_size = 0;
bool does_referenced_key_exist = false; bool does_referenced_key_exist = false;
BlockCacheLookupContext lookup_data_block_context( BlockCacheLookupContext lookup_data_block_context(
TableReaderCaller::kUserMultiGet, tracing_mget_id); TableReaderCaller::kUserMultiGet, tracing_mget_id,
/*get_from_user_specified_snapshot=*/read_options.snapshot !=
nullptr);
if (first_block) { if (first_block) {
if (!block_handles[idx_in_batch].IsNull() || if (!block_handles[idx_in_batch].IsNull() ||
!results[idx_in_batch].IsEmpty()) { !results[idx_in_batch].IsEmpty()) {
@ -3685,7 +3696,7 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
if (does_referenced_key_exist) { if (does_referenced_key_exist) {
referenced_key = biter->key(); referenced_key = biter->key();
} else { } else {
referenced_key = ExtractUserKey(key); referenced_key = key;
} }
BlockCacheTraceRecord access_record( BlockCacheTraceRecord access_record(
rep_->ioptions.env->NowMicros(), rep_->ioptions.env->NowMicros(),
@ -3696,6 +3707,7 @@ void BlockBasedTable::MultiGet(const ReadOptions& read_options,
lookup_data_block_context.is_cache_hit, lookup_data_block_context.is_cache_hit,
lookup_data_block_context.no_insert, lookup_data_block_context.no_insert,
lookup_data_block_context.get_id, lookup_data_block_context.get_id,
lookup_data_block_context.get_from_user_specified_snapshot,
/*referenced_key=*/"", referenced_data_size, /*referenced_key=*/"", referenced_data_size,
lookup_data_block_context.num_keys_in_block, lookup_data_block_context.num_keys_in_block,
does_referenced_key_exist); does_referenced_key_exist);

@ -63,6 +63,8 @@ extern const uint64_t kPlainTableMagicNumber;
namespace { namespace {
const std::string kDummyValue(10000, 'o');
// DummyPropertiesCollector used to test BlockBasedTableProperties // DummyPropertiesCollector used to test BlockBasedTableProperties
class DummyPropertiesCollector : public TablePropertiesCollector { class DummyPropertiesCollector : public TablePropertiesCollector {
public: public:
@ -312,7 +314,9 @@ class TableConstructor: public Constructor {
: Constructor(cmp), : Constructor(cmp),
largest_seqno_(largest_seqno), largest_seqno_(largest_seqno),
convert_to_internal_key_(convert_to_internal_key), convert_to_internal_key_(convert_to_internal_key),
level_(level) {} level_(level) {
env_ = rocksdb::Env::Default();
}
~TableConstructor() override { Reset(); } ~TableConstructor() override { Reset(); }
Status FinishImpl(const Options& options, const ImmutableCFOptions& ioptions, Status FinishImpl(const Options& options, const ImmutableCFOptions& ioptions,
@ -371,7 +375,7 @@ class TableConstructor: public Constructor {
return ioptions.table_factory->NewTableReader( return ioptions.table_factory->NewTableReader(
TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions, TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions,
internal_comparator, !kSkipFilters, !kImmortal, internal_comparator, !kSkipFilters, !kImmortal,
level_, largest_seqno_, nullptr), level_, largest_seqno_, &block_cache_tracer_),
std::move(file_reader_), TEST_GetSink()->contents().size(), std::move(file_reader_), TEST_GetSink()->contents().size(),
&table_reader_); &table_reader_);
} }
@ -425,6 +429,8 @@ class TableConstructor: public Constructor {
return static_cast<test::StringSink*>(file_writer_->writable_file()); return static_cast<test::StringSink*>(file_writer_->writable_file());
} }
BlockCacheTracer block_cache_tracer_;
private: private:
void Reset() { void Reset() {
uniq_id_ = 0; uniq_id_ = 0;
@ -445,6 +451,7 @@ class TableConstructor: public Constructor {
static uint64_t cur_uniq_id_; static uint64_t cur_uniq_id_;
EnvOptions soptions; EnvOptions soptions;
Env* env_;
}; };
uint64_t TableConstructor::cur_uniq_id_ = 1; uint64_t TableConstructor::cur_uniq_id_ = 1;
@ -1063,7 +1070,9 @@ class BlockBasedTableTest
: public TableTest, : public TableTest,
virtual public ::testing::WithParamInterface<uint32_t> { virtual public ::testing::WithParamInterface<uint32_t> {
public: public:
BlockBasedTableTest() : format_(GetParam()) {} BlockBasedTableTest() : format_(GetParam()) {
env_ = rocksdb::Env::Default();
}
BlockBasedTableOptions GetBlockBasedTableOptions() { BlockBasedTableOptions GetBlockBasedTableOptions() {
BlockBasedTableOptions options; BlockBasedTableOptions options;
@ -1071,11 +1080,91 @@ class BlockBasedTableTest
return options; return options;
} }
void SetupTracingTest(TableConstructor* c) {
test_path_ = test::PerThreadDBPath("block_based_table_tracing_test");
EXPECT_OK(env_->CreateDir(test_path_));
trace_file_path_ = test_path_ + "/block_cache_trace_file";
TraceOptions trace_opt;
std::unique_ptr<TraceWriter> trace_writer;
EXPECT_OK(NewFileTraceWriter(env_, EnvOptions(), trace_file_path_,
&trace_writer));
c->block_cache_tracer_.StartTrace(env_, trace_opt, std::move(trace_writer));
{
std::string user_key = "k01";
InternalKey internal_key(user_key, 0, kTypeValue);
std::string encoded_key = internal_key.Encode().ToString();
c->Add(encoded_key, kDummyValue);
}
{
std::string user_key = "k02";
InternalKey internal_key(user_key, 0, kTypeValue);
std::string encoded_key = internal_key.Encode().ToString();
c->Add(encoded_key, kDummyValue);
}
}
void VerifyBlockAccessTrace(
TableConstructor* c,
const std::vector<BlockCacheTraceRecord>& expected_records) {
c->block_cache_tracer_.EndTrace();
std::unique_ptr<TraceReader> trace_reader;
Status s =
NewFileTraceReader(env_, EnvOptions(), trace_file_path_, &trace_reader);
EXPECT_OK(s);
BlockCacheTraceReader reader(std::move(trace_reader));
BlockCacheTraceHeader header;
EXPECT_OK(reader.ReadHeader(&header));
uint32_t index = 0;
while (s.ok()) {
BlockCacheTraceRecord access;
s = reader.ReadAccess(&access);
if (!s.ok()) {
break;
}
ASSERT_LT(index, expected_records.size());
EXPECT_NE("", access.block_key);
EXPECT_EQ(access.block_type, expected_records[index].block_type);
EXPECT_GT(access.block_size, 0);
EXPECT_EQ(access.caller, expected_records[index].caller);
EXPECT_EQ(access.no_insert, expected_records[index].no_insert);
EXPECT_EQ(access.is_cache_hit, expected_records[index].is_cache_hit);
// Get
if (access.caller == TableReaderCaller::kUserGet) {
EXPECT_EQ(access.referenced_key,
expected_records[index].referenced_key);
EXPECT_EQ(access.get_id, expected_records[index].get_id);
EXPECT_EQ(access.get_from_user_specified_snapshot,
expected_records[index].get_from_user_specified_snapshot);
if (access.block_type == TraceType::kBlockTraceDataBlock) {
EXPECT_GT(access.referenced_data_size, 0);
EXPECT_GT(access.num_keys_in_block, 0);
EXPECT_EQ(access.referenced_key_exist_in_block,
expected_records[index].referenced_key_exist_in_block);
}
} else {
EXPECT_EQ(access.referenced_key, "");
EXPECT_EQ(access.get_id, 0);
EXPECT_TRUE(access.get_from_user_specified_snapshot == Boolean::kFalse);
EXPECT_EQ(access.referenced_data_size, 0);
EXPECT_EQ(access.num_keys_in_block, 0);
EXPECT_TRUE(access.referenced_key_exist_in_block == Boolean::kFalse);
}
index++;
}
EXPECT_EQ(index, expected_records.size());
EXPECT_OK(env_->DeleteFile(trace_file_path_));
EXPECT_OK(env_->DeleteDir(test_path_));
}
protected: protected:
uint64_t IndexUncompressedHelper(bool indexCompress); uint64_t IndexUncompressedHelper(bool indexCompress);
private: private:
uint32_t format_; uint32_t format_;
Env* env_;
std::string trace_file_path_;
std::string test_path_;
}; };
class PlainTableTest : public TableTest {}; class PlainTableTest : public TableTest {};
class TablePropertyTest : public testing::Test {}; class TablePropertyTest : public testing::Test {};
@ -2211,6 +2300,187 @@ TEST_P(BlockBasedTableTest, NumBlockStat) {
c.ResetTableReader(); c.ResetTableReader();
} }
TEST_P(BlockBasedTableTest, TracingGetTest) {
TableConstructor c(BytewiseComparator());
Options options;
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
options.create_if_missing = true;
table_options.block_cache = NewLRUCache(1024 * 1024, 0);
table_options.cache_index_and_filter_blocks = true;
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
options.table_factory.reset(new BlockBasedTableFactory(table_options));
SetupTracingTest(&c);
std::vector<std::string> keys;
stl_wrappers::KVMap kvmap;
ImmutableCFOptions ioptions(options);
MutableCFOptions moptions(options);
c.Finish(options, ioptions, moptions, table_options,
GetPlainInternalComparator(options.comparator), &keys, &kvmap);
std::string user_key = "k01";
InternalKey internal_key(user_key, 0, kTypeValue);
std::string encoded_key = internal_key.Encode().ToString();
for (uint32_t i = 1; i <= 2; i++) {
PinnableSlice value;
GetContext get_context(options.comparator, nullptr, nullptr, nullptr,
GetContext::kNotFound, user_key, &value, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, /*get_id=*/i);
get_perf_context()->Reset();
ASSERT_OK(c.GetTableReader()->Get(ReadOptions(), encoded_key, &get_context,
moptions.prefix_extractor.get()));
ASSERT_EQ(get_context.State(), GetContext::kFound);
ASSERT_EQ(value.ToString(), kDummyValue);
}
// Verify traces.
std::vector<BlockCacheTraceRecord> expected_records;
// The first two records should be prefetching index and filter blocks.
BlockCacheTraceRecord record;
record.block_type = TraceType::kBlockTraceIndexBlock;
record.caller = TableReaderCaller::kPrefetch;
record.is_cache_hit = Boolean::kFalse;
record.no_insert = Boolean::kFalse;
expected_records.push_back(record);
record.block_type = TraceType::kBlockTraceFilterBlock;
expected_records.push_back(record);
// Then we should have three records for one index, one filter, and one data
// block access.
record.get_id = 1;
record.block_type = TraceType::kBlockTraceIndexBlock;
record.caller = TableReaderCaller::kUserGet;
record.get_from_user_specified_snapshot = Boolean::kFalse;
record.referenced_key = encoded_key;
record.referenced_key_exist_in_block = Boolean::kTrue;
record.is_cache_hit = Boolean::kTrue;
expected_records.push_back(record);
record.block_type = TraceType::kBlockTraceFilterBlock;
expected_records.push_back(record);
record.is_cache_hit = Boolean::kFalse;
record.block_type = TraceType::kBlockTraceDataBlock;
expected_records.push_back(record);
// The second get should all observe cache hits.
record.is_cache_hit = Boolean::kTrue;
record.get_id = 2;
record.block_type = TraceType::kBlockTraceIndexBlock;
record.caller = TableReaderCaller::kUserGet;
record.get_from_user_specified_snapshot = Boolean::kFalse;
record.referenced_key = encoded_key;
expected_records.push_back(record);
record.block_type = TraceType::kBlockTraceFilterBlock;
expected_records.push_back(record);
record.block_type = TraceType::kBlockTraceDataBlock;
expected_records.push_back(record);
VerifyBlockAccessTrace(&c, expected_records);
c.ResetTableReader();
}
TEST_P(BlockBasedTableTest, TracingApproximateOffsetOfTest) {
TableConstructor c(BytewiseComparator());
Options options;
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
options.create_if_missing = true;
table_options.block_cache = NewLRUCache(1024 * 1024, 0);
table_options.cache_index_and_filter_blocks = true;
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
options.table_factory.reset(new BlockBasedTableFactory(table_options));
SetupTracingTest(&c);
std::vector<std::string> keys;
stl_wrappers::KVMap kvmap;
ImmutableCFOptions ioptions(options);
MutableCFOptions moptions(options);
c.Finish(options, ioptions, moptions, table_options,
GetPlainInternalComparator(options.comparator), &keys, &kvmap);
for (uint32_t i = 1; i <= 2; i++) {
std::string user_key = "k01";
InternalKey internal_key(user_key, 0, kTypeValue);
std::string encoded_key = internal_key.Encode().ToString();
c.GetTableReader()->ApproximateOffsetOf(
encoded_key, TableReaderCaller::kUserApproximateSize);
}
// Verify traces.
std::vector<BlockCacheTraceRecord> expected_records;
// The first two records should be prefetching index and filter blocks.
BlockCacheTraceRecord record;
record.block_type = TraceType::kBlockTraceIndexBlock;
record.caller = TableReaderCaller::kPrefetch;
record.is_cache_hit = Boolean::kFalse;
record.no_insert = Boolean::kFalse;
expected_records.push_back(record);
record.block_type = TraceType::kBlockTraceFilterBlock;
expected_records.push_back(record);
// Then we should have two records for only index blocks.
record.block_type = TraceType::kBlockTraceIndexBlock;
record.caller = TableReaderCaller::kUserApproximateSize;
record.is_cache_hit = Boolean::kTrue;
expected_records.push_back(record);
expected_records.push_back(record);
VerifyBlockAccessTrace(&c, expected_records);
c.ResetTableReader();
}
TEST_P(BlockBasedTableTest, TracingIterator) {
TableConstructor c(BytewiseComparator());
Options options;
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
options.create_if_missing = true;
table_options.block_cache = NewLRUCache(1024 * 1024, 0);
table_options.cache_index_and_filter_blocks = true;
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
options.table_factory.reset(new BlockBasedTableFactory(table_options));
SetupTracingTest(&c);
std::vector<std::string> keys;
stl_wrappers::KVMap kvmap;
ImmutableCFOptions ioptions(options);
MutableCFOptions moptions(options);
c.Finish(options, ioptions, moptions, table_options,
GetPlainInternalComparator(options.comparator), &keys, &kvmap);
for (uint32_t i = 1; i <= 2; i++) {
std::unique_ptr<InternalIterator> iter(c.GetTableReader()->NewIterator(
ReadOptions(), moptions.prefix_extractor.get(), /*arena=*/nullptr,
/*skip_filters=*/false, TableReaderCaller::kUserIterator));
iter->SeekToFirst();
while (iter->Valid()) {
iter->key();
iter->value();
iter->Next();
}
ASSERT_OK(iter->status());
iter.reset();
}
// Verify traces.
std::vector<BlockCacheTraceRecord> expected_records;
// The first two records should be prefetching index and filter blocks.
BlockCacheTraceRecord record;
record.block_type = TraceType::kBlockTraceIndexBlock;
record.caller = TableReaderCaller::kPrefetch;
record.is_cache_hit = Boolean::kFalse;
record.no_insert = Boolean::kFalse;
expected_records.push_back(record);
record.block_type = TraceType::kBlockTraceFilterBlock;
expected_records.push_back(record);
// Then we should have three records for index and two data block access.
record.block_type = TraceType::kBlockTraceIndexBlock;
record.caller = TableReaderCaller::kUserIterator;
record.is_cache_hit = Boolean::kTrue;
expected_records.push_back(record);
record.block_type = TraceType::kBlockTraceDataBlock;
record.is_cache_hit = Boolean::kFalse;
expected_records.push_back(record);
expected_records.push_back(record);
// When we iterate this file for the second time, we should observe all cache
// hits.
record.block_type = TraceType::kBlockTraceIndexBlock;
record.is_cache_hit = Boolean::kTrue;
expected_records.push_back(record);
record.block_type = TraceType::kBlockTraceDataBlock;
expected_records.push_back(record);
expected_records.push_back(record);
VerifyBlockAccessTrace(&c, expected_records);
c.ResetTableReader();
}
// A simple tool that takes the snapshot of block cache statistics. // A simple tool that takes the snapshot of block cache statistics.
class BlockCachePropertiesSnapshot { class BlockCachePropertiesSnapshot {
public: public:

@ -57,8 +57,8 @@ struct BlockAccessInfo {
const uint64_t timestamp_in_seconds = const uint64_t timestamp_in_seconds =
access.access_timestamp / kMicrosInSecond; access.access_timestamp / kMicrosInSecond;
caller_num_accesses_timeline[access.caller][timestamp_in_seconds] += 1; caller_num_accesses_timeline[access.caller][timestamp_in_seconds] += 1;
if (BlockCacheTraceHelper::ShouldTraceReferencedKey(access.block_type, if (BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(access.block_type,
access.caller)) { access.caller)) {
num_keys = access.num_keys_in_block; num_keys = access.num_keys_in_block;
if (access.referenced_key_exist_in_block == Boolean::kTrue) { if (access.referenced_key_exist_in_block == Boolean::kTrue) {
if (key_num_access_map.find(access.referenced_key) == if (key_num_access_map.find(access.referenced_key) ==

@ -35,14 +35,13 @@ const std::string BlockCacheTraceHelper::kUnknownColumnFamilyName =
"UnknownColumnFamily"; "UnknownColumnFamily";
const uint64_t BlockCacheTraceHelper::kReservedGetId = 0; const uint64_t BlockCacheTraceHelper::kReservedGetId = 0;
bool BlockCacheTraceHelper::ShouldTraceReferencedKey(TraceType block_type, bool BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(
TableReaderCaller caller) { TraceType block_type, TableReaderCaller caller) {
return (block_type == TraceType::kBlockTraceDataBlock) && return (block_type == TraceType::kBlockTraceDataBlock) &&
(caller == TableReaderCaller::kUserGet || IsGetOrMultiGet(caller);
caller == TableReaderCaller::kUserMultiGet);
} }
bool BlockCacheTraceHelper::ShouldTraceGetId(TableReaderCaller caller) { bool BlockCacheTraceHelper::IsGetOrMultiGet(TableReaderCaller caller) {
return caller == TableReaderCaller::kUserGet || return caller == TableReaderCaller::kUserGet ||
caller == TableReaderCaller::kUserMultiGet; caller == TableReaderCaller::kUserMultiGet;
} }
@ -81,12 +80,13 @@ Status BlockCacheTraceWriter::WriteBlockAccess(
trace.payload.push_back(record.caller); trace.payload.push_back(record.caller);
trace.payload.push_back(record.is_cache_hit); trace.payload.push_back(record.is_cache_hit);
trace.payload.push_back(record.no_insert); trace.payload.push_back(record.no_insert);
if (BlockCacheTraceHelper::ShouldTraceGetId(record.caller)) { if (BlockCacheTraceHelper::IsGetOrMultiGet(record.caller)) {
PutFixed64(&trace.payload, record.get_id); PutFixed64(&trace.payload, record.get_id);
} trace.payload.push_back(record.get_from_user_specified_snapshot);
if (BlockCacheTraceHelper::ShouldTraceReferencedKey(record.block_type,
record.caller)) {
PutLengthPrefixedSlice(&trace.payload, referenced_key); PutLengthPrefixedSlice(&trace.payload, referenced_key);
}
if (BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(record.block_type,
record.caller)) {
PutFixed64(&trace.payload, record.referenced_data_size); PutFixed64(&trace.payload, record.referenced_data_size);
PutFixed64(&trace.payload, record.num_keys_in_block); PutFixed64(&trace.payload, record.num_keys_in_block);
trace.payload.push_back(record.referenced_key_exist_in_block); trace.payload.push_back(record.referenced_key_exist_in_block);
@ -216,20 +216,28 @@ Status BlockCacheTraceReader::ReadAccess(BlockCacheTraceRecord* record) {
} }
record->no_insert = static_cast<Boolean>(enc_slice[0]); record->no_insert = static_cast<Boolean>(enc_slice[0]);
enc_slice.remove_prefix(kCharSize); enc_slice.remove_prefix(kCharSize);
if (BlockCacheTraceHelper::ShouldTraceGetId(record->caller)) { if (BlockCacheTraceHelper::IsGetOrMultiGet(record->caller)) {
if (!GetFixed64(&enc_slice, &record->get_id)) { if (!GetFixed64(&enc_slice, &record->get_id)) {
return Status::Incomplete( return Status::Incomplete(
"Incomplete access record: Failed to read the get id."); "Incomplete access record: Failed to read the get id.");
} }
} if (enc_slice.empty()) {
if (BlockCacheTraceHelper::ShouldTraceReferencedKey(record->block_type, return Status::Incomplete(
record->caller)) { "Incomplete access record: Failed to read "
"get_from_user_specified_snapshot.");
}
record->get_from_user_specified_snapshot =
static_cast<Boolean>(enc_slice[0]);
enc_slice.remove_prefix(kCharSize);
Slice referenced_key; Slice referenced_key;
if (!GetLengthPrefixedSlice(&enc_slice, &referenced_key)) { if (!GetLengthPrefixedSlice(&enc_slice, &referenced_key)) {
return Status::Incomplete( return Status::Incomplete(
"Incomplete access record: Failed to read the referenced key."); "Incomplete access record: Failed to read the referenced key.");
} }
record->referenced_key = referenced_key.ToString(); record->referenced_key = referenced_key.ToString();
}
if (BlockCacheTraceHelper::IsGetOrMultiGetOnDataBlock(record->block_type,
record->caller)) {
if (!GetFixed64(&enc_slice, &record->referenced_data_size)) { if (!GetFixed64(&enc_slice, &record->referenced_data_size)) {
return Status::Incomplete( return Status::Incomplete(
"Incomplete access record: Failed to read the referenced data size."); "Incomplete access record: Failed to read the referenced data size.");

@ -23,9 +23,9 @@ extern const uint64_t kSecondInHour;
class BlockCacheTraceHelper { class BlockCacheTraceHelper {
public: public:
static bool ShouldTraceReferencedKey(TraceType block_type, static bool IsGetOrMultiGetOnDataBlock(TraceType block_type,
TableReaderCaller caller); TableReaderCaller caller);
static bool ShouldTraceGetId(TableReaderCaller caller); static bool IsGetOrMultiGet(TableReaderCaller caller);
static bool IsUserAccess(TableReaderCaller caller); static bool IsUserAccess(TableReaderCaller caller);
static const std::string kUnknownColumnFamilyName; static const std::string kUnknownColumnFamilyName;
@ -53,8 +53,11 @@ class BlockCacheTraceHelper {
// kUserApproximateSize). // kUserApproximateSize).
struct BlockCacheLookupContext { struct BlockCacheLookupContext {
BlockCacheLookupContext(const TableReaderCaller& _caller) : caller(_caller) {} BlockCacheLookupContext(const TableReaderCaller& _caller) : caller(_caller) {}
BlockCacheLookupContext(const TableReaderCaller& _caller, uint64_t _get_id) BlockCacheLookupContext(const TableReaderCaller& _caller, uint64_t _get_id,
: caller(_caller), get_id(_get_id) {} bool _get_from_user_specified_snapshot)
: caller(_caller),
get_id(_get_id),
get_from_user_specified_snapshot(_get_from_user_specified_snapshot) {}
const TableReaderCaller caller; const TableReaderCaller caller;
// These are populated when we perform lookup/insert on block cache. The block // These are populated when we perform lookup/insert on block cache. The block
// cache tracer uses these inforation when logging the block access at // cache tracer uses these inforation when logging the block access at
@ -69,6 +72,8 @@ struct BlockCacheLookupContext {
// how many blocks a Get/MultiGet request accesses. We can also measure the // how many blocks a Get/MultiGet request accesses. We can also measure the
// impact of row cache vs block cache. // impact of row cache vs block cache.
uint64_t get_id = 0; uint64_t get_id = 0;
std::string referenced_key;
bool get_from_user_specified_snapshot = false;
void FillLookupContext(bool _is_cache_hit, bool _no_insert, void FillLookupContext(bool _is_cache_hit, bool _no_insert,
TraceType _block_type, uint64_t _block_size, TraceType _block_type, uint64_t _block_size,
@ -100,23 +105,25 @@ struct BlockCacheTraceRecord {
Boolean no_insert = Boolean::kFalse; Boolean no_insert = Boolean::kFalse;
// Required field for Get and MultiGet // Required field for Get and MultiGet
uint64_t get_id = BlockCacheTraceHelper::kReservedGetId; uint64_t get_id = BlockCacheTraceHelper::kReservedGetId;
// Required fields for data block and user Get/Multi-Get only. Boolean get_from_user_specified_snapshot = Boolean::kFalse;
std::string referenced_key; std::string referenced_key;
// Required fields for data block and user Get/Multi-Get only.
uint64_t referenced_data_size = 0; uint64_t referenced_data_size = 0;
uint64_t num_keys_in_block = 0; uint64_t num_keys_in_block = 0;
Boolean referenced_key_exist_in_block = Boolean::kFalse; Boolean referenced_key_exist_in_block = Boolean::kFalse;
BlockCacheTraceRecord() {} BlockCacheTraceRecord() {}
BlockCacheTraceRecord(uint64_t _access_timestamp, std::string _block_key, BlockCacheTraceRecord(
TraceType _block_type, uint64_t _block_size, uint64_t _access_timestamp, std::string _block_key, TraceType _block_type,
uint64_t _cf_id, std::string _cf_name, uint32_t _level, uint64_t _block_size, uint64_t _cf_id, std::string _cf_name,
uint64_t _sst_fd_number, TableReaderCaller _caller, uint32_t _level, uint64_t _sst_fd_number, TableReaderCaller _caller,
bool _is_cache_hit, bool _no_insert, uint64_t _get_id, bool _is_cache_hit, bool _no_insert,
std::string _referenced_key = "", uint64_t _get_id = BlockCacheTraceHelper::kReservedGetId,
uint64_t _referenced_data_size = 0, bool _get_from_user_specified_snapshot = false,
uint64_t _num_keys_in_block = 0, std::string _referenced_key = "", uint64_t _referenced_data_size = 0,
bool _referenced_key_exist_in_block = false) uint64_t _num_keys_in_block = 0,
bool _referenced_key_exist_in_block = false)
: access_timestamp(_access_timestamp), : access_timestamp(_access_timestamp),
block_key(_block_key), block_key(_block_key),
block_type(_block_type), block_type(_block_type),
@ -129,6 +136,9 @@ struct BlockCacheTraceRecord {
is_cache_hit(_is_cache_hit ? Boolean::kTrue : Boolean::kFalse), is_cache_hit(_is_cache_hit ? Boolean::kTrue : Boolean::kFalse),
no_insert(_no_insert ? Boolean::kTrue : Boolean::kFalse), no_insert(_no_insert ? Boolean::kTrue : Boolean::kFalse),
get_id(_get_id), get_id(_get_id),
get_from_user_specified_snapshot(_get_from_user_specified_snapshot
? Boolean::kTrue
: Boolean::kFalse),
referenced_key(_referenced_key), referenced_key(_referenced_key),
referenced_data_size(_referenced_data_size), referenced_data_size(_referenced_data_size),
num_keys_in_block(_num_keys_in_block), num_keys_in_block(_num_keys_in_block),

@ -74,6 +74,7 @@ class BlockCacheTracerTest : public testing::Test {
// Provide get_id for all callers. The writer should only write get_id // Provide get_id for all callers. The writer should only write get_id
// when the caller is either GET or MGET. // when the caller is either GET or MGET.
record.get_id = key_id + 1; record.get_id = key_id + 1;
record.get_from_user_specified_snapshot = Boolean::kTrue;
// Provide these fields for all block types. // Provide these fields for all block types.
// The writer should only write these fields for data blocks and the // The writer should only write these fields for data blocks and the
// caller is either GET or MGET. // caller is either GET or MGET.
@ -126,20 +127,22 @@ class BlockCacheTracerTest : public testing::Test {
if (record.caller == TableReaderCaller::kUserGet || if (record.caller == TableReaderCaller::kUserGet ||
record.caller == TableReaderCaller::kUserMultiGet) { record.caller == TableReaderCaller::kUserMultiGet) {
ASSERT_EQ(key_id + 1, record.get_id); ASSERT_EQ(key_id + 1, record.get_id);
ASSERT_EQ(Boolean::kTrue, record.get_from_user_specified_snapshot);
ASSERT_EQ(kRefKeyPrefix + std::to_string(key_id),
record.referenced_key);
} else { } else {
ASSERT_EQ(BlockCacheTraceHelper::kReservedGetId, record.get_id); ASSERT_EQ(BlockCacheTraceHelper::kReservedGetId, record.get_id);
ASSERT_EQ(Boolean::kFalse, record.get_from_user_specified_snapshot);
ASSERT_EQ("", record.referenced_key);
} }
if (block_type == TraceType::kBlockTraceDataBlock && if (block_type == TraceType::kBlockTraceDataBlock &&
(record.caller == TableReaderCaller::kUserGet || (record.caller == TableReaderCaller::kUserGet ||
record.caller == TableReaderCaller::kUserMultiGet)) { record.caller == TableReaderCaller::kUserMultiGet)) {
ASSERT_EQ(kRefKeyPrefix + std::to_string(key_id),
record.referenced_key);
ASSERT_EQ(Boolean::kTrue, record.referenced_key_exist_in_block); ASSERT_EQ(Boolean::kTrue, record.referenced_key_exist_in_block);
ASSERT_EQ(kNumKeysInBlock, record.num_keys_in_block); ASSERT_EQ(kNumKeysInBlock, record.num_keys_in_block);
ASSERT_EQ(kReferencedDataSize + key_id, record.referenced_data_size); ASSERT_EQ(kReferencedDataSize + key_id, record.referenced_data_size);
continue; continue;
} }
ASSERT_EQ("", record.referenced_key);
ASSERT_EQ(Boolean::kFalse, record.referenced_key_exist_in_block); ASSERT_EQ(Boolean::kFalse, record.referenced_key_exist_in_block);
ASSERT_EQ(0, record.num_keys_in_block); ASSERT_EQ(0, record.num_keys_in_block);
ASSERT_EQ(0, record.referenced_data_size); ASSERT_EQ(0, record.referenced_data_size);

@ -110,19 +110,22 @@ void PrioritizedCacheSimulator::Access(const BlockCacheTraceRecord& access) {
std::string HybridRowBlockCacheSimulator::ComputeRowKey( std::string HybridRowBlockCacheSimulator::ComputeRowKey(
const BlockCacheTraceRecord& access) { const BlockCacheTraceRecord& access) {
assert(access.get_id != BlockCacheTraceHelper::kReservedGetId); assert(access.get_id != BlockCacheTraceHelper::kReservedGetId);
Slice key; Slice key = ExtractUserKey(access.referenced_key);
if (access.referenced_key_exist_in_block == Boolean::kTrue) { uint64_t seq_no = access.get_from_user_specified_snapshot == Boolean::kFalse
key = ExtractUserKey(access.referenced_key); ? 0
} else { : 1 + GetInternalKeySeqno(access.referenced_key);
key = access.referenced_key; return std::to_string(access.sst_fd_number) + "_" + key.ToString() + "_" +
} std::to_string(seq_no);
return std::to_string(access.sst_fd_number) + "_" + key.ToString();
} }
void HybridRowBlockCacheSimulator::Access(const BlockCacheTraceRecord& access) { void HybridRowBlockCacheSimulator::Access(const BlockCacheTraceRecord& access) {
bool is_cache_miss = true; bool is_cache_miss = true;
bool admitted = true; bool admitted = true;
if (access.get_id != BlockCacheTraceHelper::kReservedGetId) { // TODO (haoyu): We only support Get for now. We need to extend the tracing
// for MultiGet, i.e., non-data block accesses must log all keys in a
// MultiGet.
if (access.caller == TableReaderCaller::kUserGet &&
access.get_id != BlockCacheTraceHelper::kReservedGetId) {
// This is a Get/MultiGet request. // This is a Get/MultiGet request.
const std::string& row_key = ComputeRowKey(access); const std::string& row_key = ComputeRowKey(access);
if (getid_getkeys_map_[access.get_id].find(row_key) == if (getid_getkeys_map_[access.get_id].find(row_key) ==

@ -137,7 +137,6 @@ class HybridRowBlockCacheSimulator : public PrioritizedCacheSimulator {
private: private:
// Row key is a concatenation of the access's fd_number and the referenced // Row key is a concatenation of the access's fd_number and the referenced
// user key. // user key.
// TODO(haoyu): the row key should contain sequence number.
std::string ComputeRowKey(const BlockCacheTraceRecord& access); std::string ComputeRowKey(const BlockCacheTraceRecord& access);
enum InsertResult : char { enum InsertResult : char {

@ -174,10 +174,11 @@ TEST_F(CacheSimulatorTest, GhostPrioritizedCacheSimulator) {
TEST_F(CacheSimulatorTest, HybridRowBlockCacheSimulator) { TEST_F(CacheSimulatorTest, HybridRowBlockCacheSimulator) {
uint64_t block_id = 100; uint64_t block_id = 100;
BlockCacheTraceRecord first_get = GenerateGetRecord(kGetId); BlockCacheTraceRecord first_get = GenerateGetRecord(kGetId);
first_get.get_from_user_specified_snapshot = Boolean::kTrue;
BlockCacheTraceRecord second_get = GenerateGetRecord(kGetId + 1); BlockCacheTraceRecord second_get = GenerateGetRecord(kGetId + 1);
second_get.referenced_data_size = 0; second_get.referenced_data_size = 0;
second_get.referenced_key_exist_in_block = Boolean::kFalse; second_get.referenced_key_exist_in_block = Boolean::kFalse;
second_get.referenced_key = kRefKeyPrefix + std::to_string(kGetId); second_get.get_from_user_specified_snapshot = Boolean::kTrue;
BlockCacheTraceRecord third_get = GenerateGetRecord(kGetId + 2); BlockCacheTraceRecord third_get = GenerateGetRecord(kGetId + 2);
third_get.referenced_data_size = 0; third_get.referenced_data_size = 0;
third_get.referenced_key_exist_in_block = Boolean::kFalse; third_get.referenced_key_exist_in_block = Boolean::kFalse;
@ -203,9 +204,10 @@ TEST_F(CacheSimulatorTest, HybridRowBlockCacheSimulator) {
ASSERT_EQ(100, cache_simulator->miss_ratio()); ASSERT_EQ(100, cache_simulator->miss_ratio());
ASSERT_EQ(10, cache_simulator->user_accesses()); ASSERT_EQ(10, cache_simulator->user_accesses());
ASSERT_EQ(100, cache_simulator->user_miss_ratio()); ASSERT_EQ(100, cache_simulator->user_miss_ratio());
auto handle = auto handle = sim_cache->Lookup(
sim_cache->Lookup(ExtractUserKey(std::to_string(first_get.sst_fd_number) + std::to_string(first_get.sst_fd_number) + "_" +
"_" + first_get.referenced_key)); ExtractUserKey(first_get.referenced_key).ToString() + "_" +
std::to_string(1 + GetInternalKeySeqno(first_get.referenced_key)));
ASSERT_NE(nullptr, handle); ASSERT_NE(nullptr, handle);
sim_cache->Release(handle); sim_cache->Release(handle);
for (uint32_t i = 100; i < block_id; i++) { for (uint32_t i = 100; i < block_id; i++) {
@ -227,8 +229,10 @@ TEST_F(CacheSimulatorTest, HybridRowBlockCacheSimulator) {
ASSERT_EQ(66, static_cast<uint64_t>(cache_simulator->miss_ratio())); ASSERT_EQ(66, static_cast<uint64_t>(cache_simulator->miss_ratio()));
ASSERT_EQ(15, cache_simulator->user_accesses()); ASSERT_EQ(15, cache_simulator->user_accesses());
ASSERT_EQ(66, static_cast<uint64_t>(cache_simulator->user_miss_ratio())); ASSERT_EQ(66, static_cast<uint64_t>(cache_simulator->user_miss_ratio()));
handle = sim_cache->Lookup(std::to_string(second_get.sst_fd_number) + "_" + handle = sim_cache->Lookup(
second_get.referenced_key); std::to_string(second_get.sst_fd_number) + "_" +
ExtractUserKey(second_get.referenced_key).ToString() + "_" +
std::to_string(1 + GetInternalKeySeqno(second_get.referenced_key)));
ASSERT_NE(nullptr, handle); ASSERT_NE(nullptr, handle);
sim_cache->Release(handle); sim_cache->Release(handle);
for (uint32_t i = 100; i < block_id; i++) { for (uint32_t i = 100; i < block_id; i++) {
@ -283,9 +287,9 @@ TEST_F(CacheSimulatorTest, HybridRowBlockNoInsertCacheSimulator) {
cache_simulator->Access(first_get); cache_simulator->Access(first_get);
block_id++; block_id++;
} }
auto handle = auto handle = sim_cache->Lookup(
sim_cache->Lookup(ExtractUserKey(std::to_string(first_get.sst_fd_number) + std::to_string(first_get.sst_fd_number) + "_" +
"_" + first_get.referenced_key)); ExtractUserKey(first_get.referenced_key).ToString() + "_0");
ASSERT_NE(nullptr, handle); ASSERT_NE(nullptr, handle);
sim_cache->Release(handle); sim_cache->Release(handle);
// All blocks are missing from the cache since insert_blocks_row_kvpair_misses // All blocks are missing from the cache since insert_blocks_row_kvpair_misses

Loading…
Cancel
Save