mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
14
Commits
blob_shadow
...
5.8.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
def6a00740 | ||
|
|
6b77c07379 | ||
|
|
cd2e5cae7f | ||
|
|
546a63272f | ||
|
|
35dfbd58dd | ||
|
|
51c2ea0feb | ||
|
|
9089373a01 | ||
|
|
7393ef779c | ||
|
|
4bcb7fb148 | ||
|
|
e3a06f12d2 | ||
|
|
a9c8d4ef15 | ||
|
|
bb5ed4b1d1 | ||
|
|
0d5692e02b | ||
|
|
2f1a3a4d74 |
No files matched your search
@@ -485,6 +485,7 @@ set(SOURCES
|
||||
table/block_based_table_factory.cc
|
||||
table/block_based_table_reader.cc
|
||||
table/block_builder.cc
|
||||
table/block_fetcher.cc
|
||||
table/block_prefix_index.cc
|
||||
table/bloom_block.cc
|
||||
table/cuckoo_table_builder.cc
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
|
||||
## 5.10.0 (12/11/2017)
|
||||
### Public API Change
|
||||
* When running `make` with environment variable `USE_SSE` set and `PORTABLE` unset, will use all machine features available locally. Previously this combination only compiled SSE-related features.
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ cpp_library(
|
||||
"table/block_based_table_factory.cc",
|
||||
"table/block_based_table_reader.cc",
|
||||
"table/block_builder.cc",
|
||||
"table/block_fetcher.cc",
|
||||
"table/block_prefix_index.cc",
|
||||
"table/bloom_block.cc",
|
||||
"table/cuckoo_table_builder.cc",
|
||||
|
||||
@@ -184,8 +184,11 @@ void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
Slice* skip_until) {
|
||||
if (compaction_filter_ != nullptr &&
|
||||
(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex) &&
|
||||
(visible_at_tip_ || ikey_.sequence > latest_snapshot_ ||
|
||||
ignore_snapshots_)) {
|
||||
(visible_at_tip_ || ignore_snapshots_ ||
|
||||
ikey_.sequence > latest_snapshot_ ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(!snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
latest_snapshot_))))) {
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
@@ -411,7 +414,10 @@ void CompactionIterator::NextFromInput() {
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
// SingleDelete.
|
||||
if (prev_snapshot == 0 || next_ikey.sequence > prev_snapshot) {
|
||||
if (prev_snapshot == 0 || next_ikey.sequence > prev_snapshot ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(!snapshot_checker_->IsInSnapshot(next_ikey.sequence,
|
||||
prev_snapshot)))) {
|
||||
if (next_ikey.type == kTypeSingleDeletion) {
|
||||
// We encountered two SingleDeletes in a row. This could be due to
|
||||
// unexpected user input.
|
||||
@@ -422,8 +428,12 @@ void CompactionIterator::NextFromInput() {
|
||||
// input_->Next().
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
++iter_stats_.num_single_del_mismatch;
|
||||
} else if ((ikey_.sequence <= earliest_write_conflict_snapshot_) ||
|
||||
has_outputted_key_) {
|
||||
} else if (has_outputted_key_ ||
|
||||
(ikey_.sequence <= earliest_write_conflict_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(
|
||||
ikey_.sequence,
|
||||
earliest_write_conflict_snapshot_))))) {
|
||||
// Found a matching value, we can drop the single delete and the
|
||||
// value. It is safe to drop both records since we've already
|
||||
// outputted a key in this snapshot, or there is no earlier
|
||||
@@ -471,6 +481,9 @@ void CompactionIterator::NextFromInput() {
|
||||
// comparison, so the value of has_current_user_key does not matter.
|
||||
has_current_user_key_ = false;
|
||||
if (compaction_ != nullptr && ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
earliest_snapshot_))) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
// Key doesn't exist outside of this range.
|
||||
@@ -504,6 +517,9 @@ void CompactionIterator::NextFromInput() {
|
||||
input_->Next();
|
||||
} else if (compaction_ != nullptr && ikey_.type == kTypeDeletion &&
|
||||
ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
earliest_snapshot_))) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
@@ -538,6 +554,10 @@ void CompactionIterator::NextFromInput() {
|
||||
// have hit (A)
|
||||
// We encapsulate the merge related state machine in a different
|
||||
// object to minimize change to the existing flow.
|
||||
// In case snapshot_checker is present, we can probably merge further
|
||||
// beyond prev_snapshot, since there could be more keys with sequence
|
||||
// smaller than prev_snapshot, but reported by snapshot_checker as not
|
||||
// visible by prev_snapshot. But it will make the logic more complicated.
|
||||
Status s = merge_helper_->MergeUntil(input_, range_del_agg_,
|
||||
prev_snapshot, bottommost_level_);
|
||||
merge_out_iter_.SeekToFirst();
|
||||
|
||||
+12
-8
@@ -625,7 +625,8 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
"[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
|
||||
"files in(%d, %d) out(%d) "
|
||||
"MB in(%.1f, %.1f) out(%.1f), read-write-amplify(%.1f) "
|
||||
"write-amplify(%.1f) %s, records in: %d, records dropped: %d\n",
|
||||
"write-amplify(%.1f) %s, records in: %d, records dropped: %d "
|
||||
"output_compression: %s\n",
|
||||
cfd->GetName().c_str(), vstorage->LevelSummary(&tmp), bytes_read_per_sec,
|
||||
bytes_written_per_sec, compact_->compaction->output_level(),
|
||||
stats.num_input_files_in_non_output_levels,
|
||||
@@ -634,20 +635,23 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
stats.bytes_read_output_level / 1048576.0,
|
||||
stats.bytes_written / 1048576.0, read_write_amp, write_amp,
|
||||
status.ToString().c_str(), stats.num_input_records,
|
||||
stats.num_dropped_records);
|
||||
stats.num_dropped_records,
|
||||
CompressionTypeToString(compact_->compaction->output_compression())
|
||||
.c_str());
|
||||
|
||||
UpdateCompactionJobStats(stats);
|
||||
|
||||
auto stream = event_logger_->LogToBuffer(log_buffer_);
|
||||
stream << "job" << job_id_
|
||||
<< "event" << "compaction_finished"
|
||||
stream << "job" << job_id_ << "event"
|
||||
<< "compaction_finished"
|
||||
<< "compaction_time_micros" << compaction_stats_.micros
|
||||
<< "output_level" << compact_->compaction->output_level()
|
||||
<< "num_output_files" << compact_->NumOutputFiles()
|
||||
<< "total_output_size" << compact_->total_bytes
|
||||
<< "num_input_records" << compact_->num_input_records
|
||||
<< "num_output_records" << compact_->num_output_records
|
||||
<< "num_subcompactions" << compact_->sub_compact_states.size();
|
||||
<< "total_output_size" << compact_->total_bytes << "num_input_records"
|
||||
<< compact_->num_input_records << "num_output_records"
|
||||
<< compact_->num_output_records << "num_subcompactions"
|
||||
<< compact_->sub_compact_states.size() << "output_compression"
|
||||
<< CompressionTypeToString(compact_->compaction->output_compression());
|
||||
|
||||
if (compaction_job_stats_ != nullptr) {
|
||||
stream << "num_single_delete_mismatches"
|
||||
|
||||
+8
-4
@@ -521,9 +521,6 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
true /* disable_memtable */, pre_release_callback);
|
||||
if (write_options.disableWAL) {
|
||||
return status;
|
||||
}
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
|
||||
@@ -580,7 +577,13 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
// LastAllocatedSequence is increased inside WriteToWAL under
|
||||
// wal_write_mutex_ to ensure ordered events in WAL
|
||||
size_t seq_inc = seq_per_batch_ ? write_group.size : 0 /*total_count*/;
|
||||
status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
if (!write_options.disableWAL) {
|
||||
status =
|
||||
ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
} else {
|
||||
// Otherwise we inc seq number to do solely the seq allocation
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
}
|
||||
auto curr_seq = last_sequence + 1;
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
@@ -593,6 +596,7 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
// else seq advances only by memtable writes
|
||||
}
|
||||
if (status.ok() && write_options.sync) {
|
||||
assert(!write_options.disableWAL);
|
||||
// Requesting sync with two_write_queues_ is expected to be very rare. We
|
||||
// hance provide a simple implementation that is not necessarily efficient.
|
||||
if (manual_wal_flush_) {
|
||||
|
||||
@@ -376,6 +376,30 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", 1024 * 1024 /* 1 MB/sec */));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
Destroy(last_options_);
|
||||
|
||||
// Add some trash files to the db directory so the DB can clean them up
|
||||
env_->CreateDirIfMissing(dbname_);
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "003.sst.trash"));
|
||||
|
||||
// Reopen the DB and verify that it deletes existing trash files
|
||||
ASSERT_OK(TryReopen(options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "003.sst.trash"));
|
||||
}
|
||||
|
||||
|
||||
// Create a DB with 2 db_paths, and generate multiple files in the 2
|
||||
// db_paths using CompactRangeOptions, make sure that files that were
|
||||
// deleted from first db_path were deleted using DeleteScheduler and
|
||||
|
||||
+1
-1
@@ -4375,7 +4375,6 @@ TEST_F(DBTest, DynamicFIFOCompactionOptions) {
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
true);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, DynamicUniversalCompactionOptions) {
|
||||
Options options;
|
||||
@@ -4444,6 +4443,7 @@ TEST_F(DBTest, DynamicUniversalCompactionOptions) {
|
||||
dbfull()->GetOptions().compaction_options_universal.allow_trivial_move,
|
||||
false);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, FileCreationRandomFailure) {
|
||||
Options options;
|
||||
|
||||
@@ -209,6 +209,8 @@ Status FlushJob::Run(FileMetaData* file_meta) {
|
||||
auto stream = event_logger_->LogToBuffer(log_buffer_);
|
||||
stream << "job" << job_context_->job_id << "event"
|
||||
<< "flush_finished";
|
||||
stream << "output_compression"
|
||||
<< CompressionTypeToString(output_compression_);
|
||||
stream << "lsm_state";
|
||||
stream.StartArray();
|
||||
auto vstorage = cfd_->current()->storage_info();
|
||||
|
||||
+18
-2
@@ -980,10 +980,12 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
storage_info_.num_non_empty_levels_, &storage_info_.file_indexer_,
|
||||
user_comparator(), internal_comparator());
|
||||
FdWithKeyRange* f = fp.GetNextFile();
|
||||
|
||||
while (f != nullptr) {
|
||||
if (get_context.sample()) {
|
||||
sample_file_read_inc(f->file_metadata);
|
||||
}
|
||||
|
||||
*status = table_cache_->Get(
|
||||
read_options, *internal_comparator(), f->fd, ikey, &get_context,
|
||||
cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()),
|
||||
@@ -995,10 +997,21 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
return;
|
||||
}
|
||||
|
||||
// report the counters before returning
|
||||
if (get_context.State() != GetContext::kNotFound &&
|
||||
get_context.State() != GetContext::kMerge) {
|
||||
for (uint32_t t = 0; t < Tickers::TICKER_ENUM_MAX; t++) {
|
||||
if (get_context.tickers_value[t] > 0) {
|
||||
RecordTick(db_statistics_, t, get_context.tickers_value[t]);
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (get_context.State()) {
|
||||
case GetContext::kNotFound:
|
||||
// Keep searching in other files
|
||||
break;
|
||||
case GetContext::kMerge:
|
||||
break;
|
||||
case GetContext::kFound:
|
||||
if (fp.GetHitFileLevel() == 0) {
|
||||
RecordTick(db_statistics_, GET_HIT_L0);
|
||||
@@ -1015,8 +1028,6 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
case GetContext::kCorrupt:
|
||||
*status = Status::Corruption("corrupted key for ", user_key);
|
||||
return;
|
||||
case GetContext::kMerge:
|
||||
break;
|
||||
case GetContext::kBlobIndex:
|
||||
ROCKS_LOG_ERROR(info_log_, "Encounter unexpected blob index.");
|
||||
*status = Status::NotSupported(
|
||||
@@ -1027,6 +1038,11 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
f = fp.GetNextFile();
|
||||
}
|
||||
|
||||
for (uint32_t t = 0; t < Tickers::TICKER_ENUM_MAX; t++) {
|
||||
if (get_context.tickers_value[t] > 0) {
|
||||
RecordTick(db_statistics_, t, get_context.tickers_value[t]);
|
||||
}
|
||||
}
|
||||
if (GetContext::kMerge == get_context.State()) {
|
||||
if (!merge_operator_) {
|
||||
*status = Status::InvalidArgument(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 5
|
||||
#define ROCKSDB_MINOR 9
|
||||
#define ROCKSDB_MINOR 10
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
@@ -96,6 +96,7 @@ LIB_SOURCES = \
|
||||
table/block_based_table_factory.cc \
|
||||
table/block_based_table_reader.cc \
|
||||
table/block_builder.cc \
|
||||
table/block_fetcher.cc \
|
||||
table/block_prefix_index.cc \
|
||||
table/bloom_block.cc \
|
||||
table/cuckoo_table_builder.cc \
|
||||
|
||||
@@ -527,11 +527,11 @@ void BlockBasedTableBuilder::WriteBlock(const Slice& raw_block_contents,
|
||||
RecordTick(r->ioptions.statistics, NUMBER_BLOCK_NOT_COMPRESSED);
|
||||
type = kNoCompression;
|
||||
block_contents = raw_block_contents;
|
||||
} else if (type != kNoCompression &&
|
||||
ShouldReportDetailedTime(r->ioptions.env,
|
||||
r->ioptions.statistics)) {
|
||||
MeasureTime(r->ioptions.statistics, COMPRESSION_TIMES_NANOS,
|
||||
timer.ElapsedNanos());
|
||||
} else if (type != kNoCompression) {
|
||||
if (ShouldReportDetailedTime(r->ioptions.env, r->ioptions.statistics)) {
|
||||
MeasureTime(r->ioptions.statistics, COMPRESSION_TIMES_NANOS,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
MeasureTime(r->ioptions.statistics, BYTES_COMPRESSED,
|
||||
raw_block_contents.size());
|
||||
RecordTick(r->ioptions.statistics, NUMBER_BLOCK_COMPRESSED);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "table/block.h"
|
||||
#include "table/block_based_filter_block.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "table/block_fetcher.h"
|
||||
#include "table/block_prefix_index.h"
|
||||
#include "table/filter_block.h"
|
||||
#include "table/format.h"
|
||||
@@ -78,9 +79,10 @@ Status ReadBlockFromFile(
|
||||
const PersistentCacheOptions& cache_options, SequenceNumber global_seqno,
|
||||
size_t read_amp_bytes_per_bit) {
|
||||
BlockContents contents;
|
||||
Status s = ReadBlockContents(file, prefetch_buffer, footer, options, handle,
|
||||
&contents, ioptions, do_uncompress,
|
||||
compression_dict, cache_options);
|
||||
BlockFetcher block_fetcher(file, prefetch_buffer, footer, options, handle,
|
||||
&contents, ioptions, do_uncompress,
|
||||
compression_dict, cache_options);
|
||||
Status s = block_fetcher.ReadBlockContents();
|
||||
if (s.ok()) {
|
||||
result->reset(new Block(std::move(contents), global_seqno,
|
||||
read_amp_bytes_per_bit, ioptions.statistics));
|
||||
@@ -126,22 +128,37 @@ Slice GetCacheKeyFromOffset(const char* cache_key_prefix,
|
||||
Cache::Handle* GetEntryFromCache(Cache* block_cache, const Slice& key,
|
||||
Tickers block_cache_miss_ticker,
|
||||
Tickers block_cache_hit_ticker,
|
||||
Statistics* statistics) {
|
||||
Statistics* statistics,
|
||||
GetContext* get_context) {
|
||||
auto cache_handle = block_cache->Lookup(key, statistics);
|
||||
if (cache_handle != nullptr) {
|
||||
PERF_COUNTER_ADD(block_cache_hit_count, 1);
|
||||
// overall cache hit
|
||||
RecordTick(statistics, BLOCK_CACHE_HIT);
|
||||
// total bytes read from cache
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_READ,
|
||||
block_cache->GetUsage(cache_handle));
|
||||
// block-type specific cache hit
|
||||
RecordTick(statistics, block_cache_hit_ticker);
|
||||
if (get_context != nullptr) {
|
||||
// overall cache hit
|
||||
get_context->RecordCounters(BLOCK_CACHE_HIT, 1);
|
||||
// total bytes read from cache
|
||||
get_context->RecordCounters(BLOCK_CACHE_BYTES_READ,
|
||||
block_cache->GetUsage(cache_handle));
|
||||
// block-type specific cache hit
|
||||
get_context->RecordCounters(block_cache_hit_ticker, 1);
|
||||
} else {
|
||||
// overall cache hit
|
||||
RecordTick(statistics, BLOCK_CACHE_HIT);
|
||||
// total bytes read from cache
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_READ,
|
||||
block_cache->GetUsage(cache_handle));
|
||||
RecordTick(statistics, block_cache_hit_ticker);
|
||||
}
|
||||
} else {
|
||||
// overall cache miss
|
||||
RecordTick(statistics, BLOCK_CACHE_MISS);
|
||||
// block-type specific cache miss
|
||||
RecordTick(statistics, block_cache_miss_ticker);
|
||||
if (get_context != nullptr) {
|
||||
// overall cache miss
|
||||
get_context->RecordCounters(BLOCK_CACHE_MISS, 1);
|
||||
// block-type specific cache miss
|
||||
get_context->RecordCounters(block_cache_miss_ticker, 1);
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_MISS);
|
||||
RecordTick(statistics, block_cache_miss_ticker);
|
||||
}
|
||||
}
|
||||
|
||||
return cache_handle;
|
||||
@@ -253,9 +270,11 @@ class PartitionIndexReader : public IndexReader, public Cleanable {
|
||||
compression_dict = rep->compression_dict_block->data;
|
||||
}
|
||||
const bool is_index = true;
|
||||
s = table_->MaybeLoadDataBlockToCache(prefetch_buffer.get(), rep, ro,
|
||||
handle, compression_dict, &block,
|
||||
is_index);
|
||||
// TODO: Support counter batch update for partitioned index and
|
||||
// filter blocks
|
||||
s = table_->MaybeLoadDataBlockToCache(
|
||||
prefetch_buffer.get(), rep, ro, handle, compression_dict, &block,
|
||||
is_index, nullptr /* get_context */);
|
||||
|
||||
assert(s.ok() || block.value == nullptr);
|
||||
if (s.ok() && block.value != nullptr) {
|
||||
@@ -408,20 +427,23 @@ class HashIndexReader : public IndexReader {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Slice dummy_comp_dict;
|
||||
// Read contents for the blocks
|
||||
BlockContents prefixes_contents;
|
||||
s = ReadBlockContents(file, prefetch_buffer, footer, ReadOptions(),
|
||||
prefixes_handle, &prefixes_contents, ioptions,
|
||||
true /* decompress */, Slice() /*compression dict*/,
|
||||
cache_options);
|
||||
BlockFetcher prefixes_block_fetcher(
|
||||
file, prefetch_buffer, footer, ReadOptions(), prefixes_handle,
|
||||
&prefixes_contents, ioptions, true /* decompress */,
|
||||
dummy_comp_dict /*compression dict*/, cache_options);
|
||||
s = prefixes_block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
BlockContents prefixes_meta_contents;
|
||||
s = ReadBlockContents(file, prefetch_buffer, footer, ReadOptions(),
|
||||
prefixes_meta_handle, &prefixes_meta_contents,
|
||||
ioptions, true /* decompress */,
|
||||
Slice() /*compression dict*/, cache_options);
|
||||
BlockFetcher prefixes_meta_block_fetcher(
|
||||
file, prefetch_buffer, footer, ReadOptions(), prefixes_meta_handle,
|
||||
&prefixes_meta_contents, ioptions, true /* decompress */,
|
||||
dummy_comp_dict /*compression dict*/, cache_options);
|
||||
prefixes_meta_block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
// TODO: log error
|
||||
return Status::OK();
|
||||
@@ -779,7 +801,8 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
ReadOptions read_options;
|
||||
s = MaybeLoadDataBlockToCache(
|
||||
prefetch_buffer.get(), rep, read_options, rep->range_del_handle,
|
||||
Slice() /* compression_dict */, &rep->range_del_entry);
|
||||
Slice() /* compression_dict */, &rep->range_del_entry,
|
||||
false /* is_index */, nullptr /* get_context */);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
rep->ioptions.info_log,
|
||||
@@ -955,8 +978,8 @@ Status BlockBasedTable::GetDataBlockFromCache(
|
||||
Cache* block_cache, Cache* block_cache_compressed,
|
||||
const ImmutableCFOptions& ioptions, const ReadOptions& read_options,
|
||||
BlockBasedTable::CachableEntry<Block>* block, uint32_t format_version,
|
||||
const Slice& compression_dict, size_t read_amp_bytes_per_bit,
|
||||
bool is_index) {
|
||||
const Slice& compression_dict, size_t read_amp_bytes_per_bit, bool is_index,
|
||||
GetContext* get_context) {
|
||||
Status s;
|
||||
Block* compressed_block = nullptr;
|
||||
Cache::Handle* block_cache_compressed_handle = nullptr;
|
||||
@@ -967,7 +990,8 @@ Status BlockBasedTable::GetDataBlockFromCache(
|
||||
block->cache_handle = GetEntryFromCache(
|
||||
block_cache, block_cache_key,
|
||||
is_index ? BLOCK_CACHE_INDEX_MISS : BLOCK_CACHE_DATA_MISS,
|
||||
is_index ? BLOCK_CACHE_INDEX_HIT : BLOCK_CACHE_DATA_HIT, statistics);
|
||||
is_index ? BLOCK_CACHE_INDEX_HIT : BLOCK_CACHE_DATA_HIT, statistics,
|
||||
get_context);
|
||||
if (block->cache_handle != nullptr) {
|
||||
block->value =
|
||||
reinterpret_cast<Block*>(block_cache->Value(block->cache_handle));
|
||||
@@ -1020,18 +1044,36 @@ Status BlockBasedTable::GetDataBlockFromCache(
|
||||
block_cache->TEST_mark_as_data_block(block_cache_key,
|
||||
block->value->usable_size());
|
||||
if (s.ok()) {
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD);
|
||||
if (is_index) {
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
if (get_context != nullptr) {
|
||||
get_context->RecordCounters(BLOCK_CACHE_ADD, 1);
|
||||
get_context->RecordCounters(BLOCK_CACHE_BYTES_WRITE,
|
||||
block->value->usable_size());
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_DATA_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_DATA_BYTES_INSERT,
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE,
|
||||
block->value->usable_size());
|
||||
}
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE,
|
||||
block->value->usable_size());
|
||||
if (is_index) {
|
||||
if (get_context != nullptr) {
|
||||
get_context->RecordCounters(BLOCK_CACHE_INDEX_ADD, 1);
|
||||
get_context->RecordCounters(BLOCK_CACHE_INDEX_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
}
|
||||
} else {
|
||||
if (get_context != nullptr) {
|
||||
get_context->RecordCounters(BLOCK_CACHE_DATA_ADD, 1);
|
||||
get_context->RecordCounters(BLOCK_CACHE_DATA_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_DATA_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_DATA_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD_FAILURES);
|
||||
delete block->value;
|
||||
@@ -1051,7 +1093,7 @@ Status BlockBasedTable::PutDataBlockToCache(
|
||||
const ReadOptions& read_options, const ImmutableCFOptions& ioptions,
|
||||
CachableEntry<Block>* block, Block* raw_block, uint32_t format_version,
|
||||
const Slice& compression_dict, size_t read_amp_bytes_per_bit, bool is_index,
|
||||
Cache::Priority priority) {
|
||||
Cache::Priority priority, GetContext* get_context) {
|
||||
assert(raw_block->compression_type() == kNoCompression ||
|
||||
block_cache_compressed != nullptr);
|
||||
|
||||
@@ -1104,18 +1146,36 @@ Status BlockBasedTable::PutDataBlockToCache(
|
||||
block->value->usable_size());
|
||||
if (s.ok()) {
|
||||
assert(block->cache_handle != nullptr);
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD);
|
||||
if (is_index) {
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
if (get_context != nullptr) {
|
||||
get_context->RecordCounters(BLOCK_CACHE_ADD, 1);
|
||||
get_context->RecordCounters(BLOCK_CACHE_BYTES_WRITE,
|
||||
block->value->usable_size());
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_DATA_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_DATA_BYTES_INSERT,
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE,
|
||||
block->value->usable_size());
|
||||
}
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE,
|
||||
block->value->usable_size());
|
||||
if (is_index) {
|
||||
if (get_context != nullptr) {
|
||||
get_context->RecordCounters(BLOCK_CACHE_INDEX_ADD, 1);
|
||||
get_context->RecordCounters(BLOCK_CACHE_INDEX_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
}
|
||||
} else {
|
||||
if (get_context != nullptr) {
|
||||
get_context->RecordCounters(BLOCK_CACHE_DATA_ADD, 1);
|
||||
get_context->RecordCounters(BLOCK_CACHE_DATA_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_DATA_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_DATA_BYTES_INSERT,
|
||||
block->value->usable_size());
|
||||
}
|
||||
}
|
||||
assert(reinterpret_cast<Block*>(
|
||||
block_cache->Value(block->cache_handle)) == block->value);
|
||||
} else {
|
||||
@@ -1138,11 +1198,16 @@ FilterBlockReader* BlockBasedTable::ReadFilter(
|
||||
return nullptr;
|
||||
}
|
||||
BlockContents block;
|
||||
if (!ReadBlockContents(rep->file.get(), prefetch_buffer, rep->footer,
|
||||
ReadOptions(), filter_handle, &block, rep->ioptions,
|
||||
false /* decompress */, Slice() /*compression dict*/,
|
||||
rep->persistent_cache_options)
|
||||
.ok()) {
|
||||
|
||||
Slice dummy_comp_dict;
|
||||
|
||||
BlockFetcher block_fetcher(rep->file.get(), prefetch_buffer, rep->footer,
|
||||
ReadOptions(), filter_handle, &block,
|
||||
rep->ioptions, false /* decompress */,
|
||||
dummy_comp_dict, rep->persistent_cache_options);
|
||||
Status s = block_fetcher.ReadBlockContents();
|
||||
|
||||
if (!s.ok()) {
|
||||
// Error reading the block
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1188,16 +1253,18 @@ FilterBlockReader* BlockBasedTable::ReadFilter(
|
||||
}
|
||||
|
||||
BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
FilePrefetchBuffer* prefetch_buffer, bool no_io) const {
|
||||
FilePrefetchBuffer* prefetch_buffer, bool no_io,
|
||||
GetContext* get_context) const {
|
||||
const BlockHandle& filter_blk_handle = rep_->filter_handle;
|
||||
const bool is_a_filter_partition = true;
|
||||
return GetFilter(prefetch_buffer, filter_blk_handle, !is_a_filter_partition,
|
||||
no_io);
|
||||
no_io, get_context);
|
||||
}
|
||||
|
||||
BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_blk_handle,
|
||||
const bool is_a_filter_partition, bool no_io) const {
|
||||
const bool is_a_filter_partition, bool no_io,
|
||||
GetContext* get_context) const {
|
||||
// If cache_index_and_filter_blocks is false, filter should be pre-populated.
|
||||
// We will return rep_->filter anyway. rep_->filter can be nullptr if filter
|
||||
// read fails at Open() time. We don't want to reload again since it will
|
||||
@@ -1227,7 +1294,7 @@ BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
Statistics* statistics = rep_->ioptions.statistics;
|
||||
auto cache_handle =
|
||||
GetEntryFromCache(block_cache, key, BLOCK_CACHE_FILTER_MISS,
|
||||
BLOCK_CACHE_FILTER_HIT, statistics);
|
||||
BLOCK_CACHE_FILTER_HIT, statistics, get_context);
|
||||
|
||||
FilterBlockReader* filter = nullptr;
|
||||
if (cache_handle != nullptr) {
|
||||
@@ -1247,10 +1314,19 @@ BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
? Cache::Priority::HIGH
|
||||
: Cache::Priority::LOW);
|
||||
if (s.ok()) {
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_FILTER_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_FILTER_BYTES_INSERT, filter->size());
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, filter->size());
|
||||
if (get_context != nullptr) {
|
||||
get_context->RecordCounters(BLOCK_CACHE_ADD, 1);
|
||||
get_context->RecordCounters(BLOCK_CACHE_BYTES_WRITE, filter->size());
|
||||
get_context->RecordCounters(BLOCK_CACHE_FILTER_ADD, 1);
|
||||
get_context->RecordCounters(BLOCK_CACHE_FILTER_BYTES_INSERT,
|
||||
filter->size());
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, filter->size());
|
||||
RecordTick(statistics, BLOCK_CACHE_FILTER_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_FILTER_BYTES_INSERT,
|
||||
filter->size());
|
||||
}
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD_FAILURES);
|
||||
delete filter;
|
||||
@@ -1264,7 +1340,7 @@ BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
|
||||
InternalIterator* BlockBasedTable::NewIndexIterator(
|
||||
const ReadOptions& read_options, BlockIter* input_iter,
|
||||
CachableEntry<IndexReader>* index_entry) {
|
||||
CachableEntry<IndexReader>* index_entry, GetContext* get_context) {
|
||||
// index reader has already been pre-populated.
|
||||
if (rep_->index_reader) {
|
||||
return rep_->index_reader->NewIterator(
|
||||
@@ -1287,7 +1363,7 @@ InternalIterator* BlockBasedTable::NewIndexIterator(
|
||||
Statistics* statistics = rep_->ioptions.statistics;
|
||||
auto cache_handle =
|
||||
GetEntryFromCache(block_cache, key, BLOCK_CACHE_INDEX_MISS,
|
||||
BLOCK_CACHE_INDEX_HIT, statistics);
|
||||
BLOCK_CACHE_INDEX_HIT, statistics, get_context);
|
||||
|
||||
if (cache_handle == nullptr && no_io) {
|
||||
if (input_iter != nullptr) {
|
||||
@@ -1322,10 +1398,15 @@ InternalIterator* BlockBasedTable::NewIndexIterator(
|
||||
|
||||
if (s.ok()) {
|
||||
size_t usable_size = index_reader->usable_size();
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD);
|
||||
if (get_context != nullptr) {
|
||||
get_context->RecordCounters(BLOCK_CACHE_ADD, 1);
|
||||
get_context->RecordCounters(BLOCK_CACHE_BYTES_WRITE, usable_size);
|
||||
} else {
|
||||
RecordTick(statistics, BLOCK_CACHE_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, usable_size);
|
||||
}
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_ADD);
|
||||
RecordTick(statistics, BLOCK_CACHE_INDEX_BYTES_INSERT, usable_size);
|
||||
RecordTick(statistics, BLOCK_CACHE_BYTES_WRITE, usable_size);
|
||||
} else {
|
||||
if (index_reader != nullptr) {
|
||||
delete index_reader;
|
||||
@@ -1359,13 +1440,14 @@ InternalIterator* BlockBasedTable::NewIndexIterator(
|
||||
|
||||
InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
Rep* rep, const ReadOptions& ro, const Slice& index_value,
|
||||
BlockIter* input_iter, bool is_index) {
|
||||
BlockIter* input_iter, bool is_index, GetContext* get_context) {
|
||||
BlockHandle handle;
|
||||
Slice input = index_value;
|
||||
// We intentionally allow extra stuff in index_value so that we
|
||||
// can add more features in the future.
|
||||
Status s = handle.DecodeFrom(&input);
|
||||
return NewDataBlockIterator(rep, ro, handle, input_iter, is_index, s);
|
||||
return NewDataBlockIterator(rep, ro, handle, input_iter, is_index,
|
||||
get_context, s);
|
||||
}
|
||||
|
||||
// Convert an index iterator value (i.e., an encoded BlockHandle)
|
||||
@@ -1374,7 +1456,7 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
// If input_iter is not null, update this iter and return it
|
||||
InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
Rep* rep, const ReadOptions& ro, const BlockHandle& handle,
|
||||
BlockIter* input_iter, bool is_index, Status s) {
|
||||
BlockIter* input_iter, bool is_index, GetContext* get_context, Status s) {
|
||||
PERF_TIMER_GUARD(new_table_block_iter_nanos);
|
||||
|
||||
const bool no_io = (ro.read_tier == kBlockCacheTier);
|
||||
@@ -1386,7 +1468,8 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
compression_dict = rep->compression_dict_block->data;
|
||||
}
|
||||
s = MaybeLoadDataBlockToCache(nullptr /*prefetch_buffer*/, rep, ro, handle,
|
||||
compression_dict, &block, is_index);
|
||||
compression_dict, &block, is_index,
|
||||
get_context);
|
||||
}
|
||||
|
||||
// Didn't get any data from block caches.
|
||||
@@ -1437,7 +1520,7 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
Status BlockBasedTable::MaybeLoadDataBlockToCache(
|
||||
FilePrefetchBuffer* prefetch_buffer, Rep* rep, const ReadOptions& ro,
|
||||
const BlockHandle& handle, Slice compression_dict,
|
||||
CachableEntry<Block>* block_entry, bool is_index) {
|
||||
CachableEntry<Block>* block_entry, bool is_index, GetContext* get_context) {
|
||||
assert(block_entry != nullptr);
|
||||
const bool no_io = (ro.read_tier == kBlockCacheTier);
|
||||
Cache* block_cache = rep->table_options.block_cache.get();
|
||||
@@ -1468,7 +1551,7 @@ Status BlockBasedTable::MaybeLoadDataBlockToCache(
|
||||
s = GetDataBlockFromCache(
|
||||
key, ckey, block_cache, block_cache_compressed, rep->ioptions, ro,
|
||||
block_entry, rep->table_options.format_version, compression_dict,
|
||||
rep->table_options.read_amp_bytes_per_bit, is_index);
|
||||
rep->table_options.read_amp_bytes_per_bit, is_index, get_context);
|
||||
|
||||
if (block_entry->value == nullptr && !no_io && ro.fill_cache) {
|
||||
std::unique_ptr<Block> raw_block;
|
||||
@@ -1487,11 +1570,11 @@ Status BlockBasedTable::MaybeLoadDataBlockToCache(
|
||||
block_entry, raw_block.release(), rep->table_options.format_version,
|
||||
compression_dict, rep->table_options.read_amp_bytes_per_bit,
|
||||
is_index,
|
||||
is_index &&
|
||||
rep->table_options
|
||||
.cache_index_and_filter_blocks_with_high_priority
|
||||
is_index && rep->table_options
|
||||
.cache_index_and_filter_blocks_with_high_priority
|
||||
? Cache::Priority::HIGH
|
||||
: Cache::Priority::LOW);
|
||||
: Cache::Priority::LOW,
|
||||
get_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1535,8 +1618,9 @@ BlockBasedTable::BlockEntryIteratorState::NewSecondaryIterator(
|
||||
&rep->internal_comparator, nullptr, true, rep->ioptions.statistics);
|
||||
}
|
||||
}
|
||||
return NewDataBlockIterator(rep, read_options_, handle, nullptr, is_index_,
|
||||
s);
|
||||
return NewDataBlockIterator(rep, read_options_, handle,
|
||||
/* input_iter */ nullptr, is_index_,
|
||||
/* get_context */ nullptr, s);
|
||||
}
|
||||
|
||||
bool BlockBasedTable::BlockEntryIteratorState::PrefixMayMatch(
|
||||
@@ -1730,8 +1814,9 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
|
||||
const bool no_io = read_options.read_tier == kBlockCacheTier;
|
||||
CachableEntry<FilterBlockReader> filter_entry;
|
||||
if (!skip_filters) {
|
||||
filter_entry = GetFilter(/*prefetch_buffer*/ nullptr,
|
||||
read_options.read_tier == kBlockCacheTier);
|
||||
filter_entry =
|
||||
GetFilter(/*prefetch_buffer*/ nullptr,
|
||||
read_options.read_tier == kBlockCacheTier, get_context);
|
||||
}
|
||||
FilterBlockReader* filter = filter_entry.value;
|
||||
|
||||
@@ -1741,7 +1826,8 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
|
||||
RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_USEFUL);
|
||||
} else {
|
||||
BlockIter iiter_on_stack;
|
||||
auto iiter = NewIndexIterator(read_options, &iiter_on_stack);
|
||||
auto iiter = NewIndexIterator(read_options, &iiter_on_stack,
|
||||
/* index_entry */ nullptr, get_context);
|
||||
std::unique_ptr<InternalIterator> iiter_unique_ptr;
|
||||
if (iiter != &iiter_on_stack) {
|
||||
iiter_unique_ptr.reset(iiter);
|
||||
@@ -1765,7 +1851,8 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
|
||||
break;
|
||||
} else {
|
||||
BlockIter biter;
|
||||
NewDataBlockIterator(rep_, read_options, iiter->value(), &biter);
|
||||
NewDataBlockIterator(rep_, read_options, iiter->value(), &biter, false,
|
||||
get_context);
|
||||
|
||||
if (read_options.read_tier == kBlockCacheTier &&
|
||||
biter.status().IsIncomplete()) {
|
||||
@@ -1906,11 +1993,13 @@ Status BlockBasedTable::VerifyChecksumInBlocks(InternalIterator* index_iter) {
|
||||
break;
|
||||
}
|
||||
BlockContents contents;
|
||||
s = ReadBlockContents(rep_->file.get(), nullptr /* prefetch buffer */,
|
||||
rep_->footer, ReadOptions(), handle, &contents,
|
||||
rep_->ioptions, false /* decompress */,
|
||||
Slice() /*compression dict*/,
|
||||
rep_->persistent_cache_options);
|
||||
Slice dummy_comp_dict;
|
||||
BlockFetcher block_fetcher(rep_->file.get(), nullptr /* prefetch buffer */,
|
||||
rep_->footer, ReadOptions(), handle, &contents,
|
||||
rep_->ioptions, false /* decompress */,
|
||||
dummy_comp_dict /*compression dict*/,
|
||||
rep_->persistent_cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -2195,12 +2284,14 @@ Status BlockBasedTable::DumpTable(WritableFile* out_file) {
|
||||
BlockHandle handle;
|
||||
if (FindMetaBlock(meta_iter.get(), filter_block_key, &handle).ok()) {
|
||||
BlockContents block;
|
||||
if (ReadBlockContents(rep_->file.get(), nullptr /* prefetch_buffer */,
|
||||
rep_->footer, ReadOptions(), handle, &block,
|
||||
rep_->ioptions, false /*decompress*/,
|
||||
Slice() /*compression dict*/,
|
||||
rep_->persistent_cache_options)
|
||||
.ok()) {
|
||||
Slice dummy_comp_dict;
|
||||
BlockFetcher block_fetcher(
|
||||
rep_->file.get(), nullptr /* prefetch_buffer */, rep_->footer,
|
||||
ReadOptions(), handle, &block, rep_->ioptions, false /*decompress*/,
|
||||
dummy_comp_dict /*compression dict*/,
|
||||
rep_->persistent_cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
rep_->filter.reset(new BlockBasedFilterBlockReader(
|
||||
rep_->ioptions.prefix_extractor, table_options,
|
||||
table_options.whole_key_filtering, std::move(block),
|
||||
|
||||
@@ -215,15 +215,14 @@ class BlockBasedTable : public TableReader {
|
||||
private:
|
||||
friend class MockedBlockBasedTable;
|
||||
// input_iter: if it is not null, update this one and return it as Iterator
|
||||
static InternalIterator* NewDataBlockIterator(Rep* rep, const ReadOptions& ro,
|
||||
const Slice& index_value,
|
||||
BlockIter* input_iter = nullptr,
|
||||
bool is_index = false);
|
||||
static InternalIterator* NewDataBlockIterator(Rep* rep, const ReadOptions& ro,
|
||||
const BlockHandle& block_hanlde,
|
||||
BlockIter* input_iter = nullptr,
|
||||
bool is_index = false,
|
||||
Status s = Status());
|
||||
static InternalIterator* NewDataBlockIterator(
|
||||
Rep* rep, const ReadOptions& ro, const Slice& index_value,
|
||||
BlockIter* input_iter = nullptr, bool is_index = false,
|
||||
GetContext* get_context = nullptr);
|
||||
static InternalIterator* NewDataBlockIterator(
|
||||
Rep* rep, const ReadOptions& ro, const BlockHandle& block_hanlde,
|
||||
BlockIter* input_iter = nullptr, bool is_index = false,
|
||||
GetContext* get_context = nullptr, Status s = Status());
|
||||
// If block cache enabled (compressed or uncompressed), looks for the block
|
||||
// identified by handle in (1) uncompressed cache, (2) compressed cache, and
|
||||
// then (3) file. If found, inserts into the cache(s) that were searched
|
||||
@@ -238,16 +237,19 @@ class BlockBasedTable : public TableReader {
|
||||
const BlockHandle& handle,
|
||||
Slice compression_dict,
|
||||
CachableEntry<Block>* block_entry,
|
||||
bool is_index = false);
|
||||
bool is_index = false,
|
||||
GetContext* get_context = nullptr);
|
||||
|
||||
// For the following two functions:
|
||||
// if `no_io == true`, we will not try to read filter/index from sst file
|
||||
// were they not present in cache yet.
|
||||
CachableEntry<FilterBlockReader> GetFilter(
|
||||
FilePrefetchBuffer* prefetch_buffer = nullptr, bool no_io = false) const;
|
||||
FilePrefetchBuffer* prefetch_buffer = nullptr, bool no_io = false,
|
||||
GetContext* get_context = nullptr) const;
|
||||
virtual CachableEntry<FilterBlockReader> GetFilter(
|
||||
FilePrefetchBuffer* prefetch_buffer, const BlockHandle& filter_blk_handle,
|
||||
const bool is_a_filter_partition, bool no_io) const;
|
||||
const bool is_a_filter_partition, bool no_io,
|
||||
GetContext* get_context) const;
|
||||
|
||||
// Get the iterator from the index reader.
|
||||
// If input_iter is not set, return new Iterator
|
||||
@@ -261,7 +263,8 @@ class BlockBasedTable : public TableReader {
|
||||
// kBlockCacheTier
|
||||
InternalIterator* NewIndexIterator(
|
||||
const ReadOptions& read_options, BlockIter* input_iter = nullptr,
|
||||
CachableEntry<IndexReader>* index_entry = nullptr);
|
||||
CachableEntry<IndexReader>* index_entry = nullptr,
|
||||
GetContext* get_context = nullptr);
|
||||
|
||||
// Read block cache from block caches (if set): block_cache and
|
||||
// block_cache_compressed.
|
||||
@@ -275,7 +278,7 @@ class BlockBasedTable : public TableReader {
|
||||
const ImmutableCFOptions& ioptions, const ReadOptions& read_options,
|
||||
BlockBasedTable::CachableEntry<Block>* block, uint32_t format_version,
|
||||
const Slice& compression_dict, size_t read_amp_bytes_per_bit,
|
||||
bool is_index = false);
|
||||
bool is_index = false, GetContext* get_context = nullptr);
|
||||
|
||||
// Put a raw block (maybe compressed) to the corresponding block caches.
|
||||
// This method will perform decompression against raw_block if needed and then
|
||||
@@ -293,7 +296,8 @@ class BlockBasedTable : public TableReader {
|
||||
const ReadOptions& read_options, const ImmutableCFOptions& ioptions,
|
||||
CachableEntry<Block>* block, Block* raw_block, uint32_t format_version,
|
||||
const Slice& compression_dict, size_t read_amp_bytes_per_bit,
|
||||
bool is_index = false, Cache::Priority pri = Cache::Priority::LOW);
|
||||
bool is_index = false, Cache::Priority pri = Cache::Priority::LOW,
|
||||
GetContext* get_context = nullptr);
|
||||
|
||||
// Calls (*handle_result)(arg, ...) repeatedly, starting with the entry found
|
||||
// after a call to Seek(key), until handle_result returns false.
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "table/block_fetcher.h"
|
||||
|
||||
#include <string>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_based_table_reader.h"
|
||||
#include "table/persistent_cache_helper.h"
|
||||
#include "table/format.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/xxhash.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
void BlockFetcher::CheckBlockChecksum() {
|
||||
// Check the crc of the type and the block contents
|
||||
if (read_options_.verify_checksums) {
|
||||
const char* data = slice_.data(); // Pointer to where Read put the data
|
||||
PERF_TIMER_GUARD(block_checksum_time);
|
||||
uint32_t value = DecodeFixed32(data + block_size_ + 1);
|
||||
uint32_t actual = 0;
|
||||
switch (footer_.checksum()) {
|
||||
case kNoChecksum:
|
||||
break;
|
||||
case kCRC32c:
|
||||
value = crc32c::Unmask(value);
|
||||
actual = crc32c::Value(data, block_size_ + 1);
|
||||
break;
|
||||
case kxxHash:
|
||||
actual = XXH32(data, static_cast<int>(block_size_) + 1, 0);
|
||||
break;
|
||||
default:
|
||||
status_ = Status::Corruption(
|
||||
"unknown checksum type " + ToString(footer_.checksum()) + " in " +
|
||||
file_->file_name() + " offset " + ToString(handle_.offset()) +
|
||||
" size " + ToString(block_size_));
|
||||
}
|
||||
if (status_.ok() && actual != value) {
|
||||
status_ = Status::Corruption(
|
||||
"block checksum mismatch: expected " + ToString(actual) + ", got " +
|
||||
ToString(value) + " in " + file_->file_name() + " offset " +
|
||||
ToString(handle_.offset()) + " size " + ToString(block_size_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BlockFetcher::TryGetUncompressBlockFromPersistentCache() {
|
||||
if (cache_options_.persistent_cache &&
|
||||
!cache_options_.persistent_cache->IsCompressed()) {
|
||||
Status status = PersistentCacheHelper::LookupUncompressedPage(
|
||||
cache_options_, handle_, contents_);
|
||||
if (status.ok()) {
|
||||
// uncompressed page is found for the block handle
|
||||
return true;
|
||||
} else {
|
||||
// uncompressed page is not found
|
||||
if (ioptions_.info_log && !status.IsNotFound()) {
|
||||
assert(!status.ok());
|
||||
ROCKS_LOG_INFO(ioptions_.info_log,
|
||||
"Error reading from persistent cache. %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BlockFetcher::TryGetFromPrefetchBuffer() {
|
||||
if (prefetch_buffer_ != nullptr &&
|
||||
prefetch_buffer_->TryReadFromCache(
|
||||
handle_.offset(),
|
||||
static_cast<size_t>(handle_.size()) + kBlockTrailerSize, &slice_)) {
|
||||
block_size_ = static_cast<size_t>(handle_.size());
|
||||
CheckBlockChecksum();
|
||||
if (!status_.ok()) {
|
||||
return true;
|
||||
}
|
||||
got_from_prefetch_buffer_ = true;
|
||||
used_buf_ = const_cast<char*>(slice_.data());
|
||||
}
|
||||
return got_from_prefetch_buffer_;
|
||||
}
|
||||
|
||||
bool BlockFetcher::TryGetCompressedBlockFromPersistentCache() {
|
||||
if (cache_options_.persistent_cache &&
|
||||
cache_options_.persistent_cache->IsCompressed()) {
|
||||
// lookup uncompressed cache mode p-cache
|
||||
status_ = PersistentCacheHelper::LookupRawPage(
|
||||
cache_options_, handle_, &heap_buf_, block_size_ + kBlockTrailerSize);
|
||||
if (status_.ok()) {
|
||||
used_buf_ = heap_buf_.get();
|
||||
slice_ = Slice(heap_buf_.get(), block_size_);
|
||||
return true;
|
||||
} else if (!status_.IsNotFound() && ioptions_.info_log) {
|
||||
assert(!status_.ok());
|
||||
ROCKS_LOG_INFO(ioptions_.info_log,
|
||||
"Error reading from persistent cache. %s",
|
||||
status_.ToString().c_str());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BlockFetcher::PrepareBufferForBlockFromFile() {
|
||||
// cache miss read from device
|
||||
if (do_uncompress_ &&
|
||||
block_size_ + kBlockTrailerSize < kDefaultStackBufferSize) {
|
||||
// If we've got a small enough hunk of data, read it in to the
|
||||
// trivially allocated stack buffer instead of needing a full malloc()
|
||||
used_buf_ = &stack_buf_[0];
|
||||
} else {
|
||||
heap_buf_ =
|
||||
std::unique_ptr<char[]>(new char[block_size_ + kBlockTrailerSize]);
|
||||
used_buf_ = heap_buf_.get();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockFetcher::InsertCompressedBlockToPersistentCacheIfNeeded() {
|
||||
if (status_.ok() && read_options_.fill_cache &&
|
||||
cache_options_.persistent_cache &&
|
||||
cache_options_.persistent_cache->IsCompressed()) {
|
||||
// insert to raw cache
|
||||
PersistentCacheHelper::InsertRawPage(cache_options_, handle_, used_buf_,
|
||||
block_size_ + kBlockTrailerSize);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockFetcher::InsertUncompressedBlockToPersistentCacheIfNeeded() {
|
||||
if (status_.ok() && !got_from_prefetch_buffer_ && read_options_.fill_cache &&
|
||||
cache_options_.persistent_cache &&
|
||||
!cache_options_.persistent_cache->IsCompressed()) {
|
||||
// insert to uncompressed cache
|
||||
PersistentCacheHelper::InsertUncompressedPage(cache_options_, handle_,
|
||||
*contents_);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockFetcher::GetBlockContents() {
|
||||
if (slice_.data() != used_buf_) {
|
||||
// the slice content is not the buffer provided
|
||||
*contents_ = BlockContents(Slice(slice_.data(), block_size_), false,
|
||||
compression_type);
|
||||
} else {
|
||||
// page is uncompressed, the buffer either stack or heap provided
|
||||
if (got_from_prefetch_buffer_ || used_buf_ == &stack_buf_[0]) {
|
||||
heap_buf_ = std::unique_ptr<char[]>(new char[block_size_]);
|
||||
memcpy(heap_buf_.get(), used_buf_, block_size_);
|
||||
}
|
||||
*contents_ = BlockContents(std::move(heap_buf_), block_size_, true,
|
||||
compression_type);
|
||||
}
|
||||
}
|
||||
|
||||
Status BlockFetcher::ReadBlockContents() {
|
||||
block_size_ = static_cast<size_t>(handle_.size());
|
||||
|
||||
if (TryGetUncompressBlockFromPersistentCache()) {
|
||||
return Status::OK();
|
||||
}
|
||||
if (TryGetFromPrefetchBuffer()) {
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
}
|
||||
} else if (!TryGetCompressedBlockFromPersistentCache()) {
|
||||
PrepareBufferForBlockFromFile();
|
||||
Status s;
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(block_read_time);
|
||||
// Actual file read
|
||||
status_ = file_->Read(handle_.offset(), block_size_ + kBlockTrailerSize,
|
||||
&slice_, used_buf_);
|
||||
}
|
||||
PERF_COUNTER_ADD(block_read_count, 1);
|
||||
PERF_COUNTER_ADD(block_read_byte, block_size_ + kBlockTrailerSize);
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
}
|
||||
|
||||
if (slice_.size() != block_size_ + kBlockTrailerSize) {
|
||||
return Status::Corruption("truncated block read from " +
|
||||
file_->file_name() + " offset " +
|
||||
ToString(handle_.offset()) + ", expected " +
|
||||
ToString(block_size_ + kBlockTrailerSize) +
|
||||
" bytes, got " + ToString(slice_.size()));
|
||||
}
|
||||
|
||||
CheckBlockChecksum();
|
||||
if (status_.ok()) {
|
||||
InsertCompressedBlockToPersistentCacheIfNeeded();
|
||||
} else {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
|
||||
PERF_TIMER_GUARD(block_decompress_time);
|
||||
|
||||
compression_type =
|
||||
static_cast<rocksdb::CompressionType>(slice_.data()[block_size_]);
|
||||
|
||||
if (do_uncompress_ && compression_type != kNoCompression) {
|
||||
// compressed page, uncompress, update cache
|
||||
status_ = UncompressBlockContents(slice_.data(), block_size_, contents_,
|
||||
footer_.version(), compression_dict_,
|
||||
ioptions_);
|
||||
} else {
|
||||
GetBlockContents();
|
||||
}
|
||||
|
||||
InsertUncompressedBlockToPersistentCacheIfNeeded();
|
||||
|
||||
return status_;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include "table/block.h"
|
||||
#include "table/format.h"
|
||||
|
||||
namespace rocksdb {
|
||||
class BlockFetcher {
|
||||
public:
|
||||
// Read the block identified by "handle" from "file".
|
||||
// The only relevant option is options.verify_checksums for now.
|
||||
// On failure return non-OK.
|
||||
// On success fill *result and return OK - caller owns *result
|
||||
// @param compression_dict Data for presetting the compression library's
|
||||
// dictionary.
|
||||
BlockFetcher(RandomAccessFileReader* file,
|
||||
FilePrefetchBuffer* prefetch_buffer, const Footer& footer,
|
||||
const ReadOptions& read_options, const BlockHandle& handle,
|
||||
BlockContents* contents,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
bool do_uncompress, const Slice& compression_dict,
|
||||
const PersistentCacheOptions& cache_options)
|
||||
: file_(file),
|
||||
prefetch_buffer_(prefetch_buffer),
|
||||
footer_(footer),
|
||||
read_options_(read_options),
|
||||
handle_(handle),
|
||||
contents_(contents),
|
||||
ioptions_(ioptions),
|
||||
do_uncompress_(do_uncompress),
|
||||
compression_dict_(compression_dict),
|
||||
cache_options_(cache_options) {}
|
||||
Status ReadBlockContents();
|
||||
|
||||
private:
|
||||
static const uint32_t kDefaultStackBufferSize = 5000;
|
||||
|
||||
RandomAccessFileReader* file_;
|
||||
FilePrefetchBuffer* prefetch_buffer_;
|
||||
const Footer& footer_;
|
||||
const ReadOptions read_options_;
|
||||
const BlockHandle& handle_;
|
||||
BlockContents* contents_;
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
bool do_uncompress_;
|
||||
const Slice& compression_dict_;
|
||||
const PersistentCacheOptions& cache_options_;
|
||||
Status status_;
|
||||
Slice slice_;
|
||||
char* used_buf_ = nullptr;
|
||||
size_t block_size_;
|
||||
std::unique_ptr<char[]> heap_buf_;
|
||||
char stack_buf_[kDefaultStackBufferSize];
|
||||
bool got_from_prefetch_buffer_ = false;
|
||||
rocksdb::CompressionType compression_type;
|
||||
|
||||
// return true if found
|
||||
bool TryGetUncompressBlockFromPersistentCache();
|
||||
// return true if found
|
||||
bool TryGetFromPrefetchBuffer();
|
||||
bool TryGetCompressedBlockFromPersistentCache();
|
||||
void PrepareBufferForBlockFromFile();
|
||||
void GetBlockContents();
|
||||
void InsertCompressedBlockToPersistentCacheIfNeeded();
|
||||
void InsertUncompressedBlockToPersistentCacheIfNeeded();
|
||||
void CheckBlockChecksum();
|
||||
};
|
||||
} // namespace rocksdb
|
||||
+3
-202
@@ -17,6 +17,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_based_table_reader.h"
|
||||
#include "table/block_fetcher.h"
|
||||
#include "table/persistent_cache_helper.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/compression.h"
|
||||
@@ -40,7 +41,6 @@ extern const uint64_t kPlainTableMagicNumber;
|
||||
const uint64_t kLegacyPlainTableMagicNumber = 0;
|
||||
const uint64_t kPlainTableMagicNumber = 0;
|
||||
#endif
|
||||
const uint32_t DefaultStackBufferSize = 5000;
|
||||
|
||||
bool ShouldReportDetailedTime(Env* env, Statistics* stats) {
|
||||
return env != nullptr && stats != nullptr &&
|
||||
@@ -264,205 +264,6 @@ Status ReadFooterFromFile(RandomAccessFileReader* file,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Without anonymous namespace here, we fail the warning -Wmissing-prototypes
|
||||
namespace {
|
||||
Status CheckBlockChecksum(const ReadOptions& options, const Footer& footer,
|
||||
const Slice& contents, size_t block_size,
|
||||
RandomAccessFileReader* file,
|
||||
const BlockHandle& handle) {
|
||||
Status s;
|
||||
// Check the crc of the type and the block contents
|
||||
if (options.verify_checksums) {
|
||||
const char* data = contents.data(); // Pointer to where Read put the data
|
||||
PERF_TIMER_GUARD(block_checksum_time);
|
||||
uint32_t value = DecodeFixed32(data + block_size + 1);
|
||||
uint32_t actual = 0;
|
||||
switch (footer.checksum()) {
|
||||
case kNoChecksum:
|
||||
break;
|
||||
case kCRC32c:
|
||||
value = crc32c::Unmask(value);
|
||||
actual = crc32c::Value(data, block_size + 1);
|
||||
break;
|
||||
case kxxHash:
|
||||
actual = XXH32(data, static_cast<int>(block_size) + 1, 0);
|
||||
break;
|
||||
default:
|
||||
s = Status::Corruption(
|
||||
"unknown checksum type " + ToString(footer.checksum()) + " in " +
|
||||
file->file_name() + " offset " + ToString(handle.offset()) +
|
||||
" size " + ToString(block_size));
|
||||
}
|
||||
if (s.ok() && actual != value) {
|
||||
s = Status::Corruption(
|
||||
"block checksum mismatch: expected " + ToString(actual) + ", got " +
|
||||
ToString(value) + " in " + file->file_name() + " offset " +
|
||||
ToString(handle.offset()) + " size " + ToString(block_size));
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Read a block and check its CRC
|
||||
// contents is the result of reading.
|
||||
// According to the implementation of file->Read, contents may not point to buf
|
||||
Status ReadBlock(RandomAccessFileReader* file, const Footer& footer,
|
||||
const ReadOptions& options, const BlockHandle& handle,
|
||||
Slice* contents, /* result of reading */ char* buf) {
|
||||
size_t n = static_cast<size_t>(handle.size());
|
||||
Status s;
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(block_read_time);
|
||||
s = file->Read(handle.offset(), n + kBlockTrailerSize, contents, buf);
|
||||
}
|
||||
|
||||
PERF_COUNTER_ADD(block_read_count, 1);
|
||||
PERF_COUNTER_ADD(block_read_byte, n + kBlockTrailerSize);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (contents->size() != n + kBlockTrailerSize) {
|
||||
return Status::Corruption("truncated block read from " + file->file_name() +
|
||||
" offset " + ToString(handle.offset()) +
|
||||
", expected " + ToString(n + kBlockTrailerSize) +
|
||||
" bytes, got " + ToString(contents->size()));
|
||||
}
|
||||
return CheckBlockChecksum(options, footer, *contents, n, file, handle);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status ReadBlockContents(RandomAccessFileReader* file,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
const Footer& footer, const ReadOptions& read_options,
|
||||
const BlockHandle& handle, BlockContents* contents,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
bool decompression_requested,
|
||||
const Slice& compression_dict,
|
||||
const PersistentCacheOptions& cache_options) {
|
||||
Status status;
|
||||
Slice slice;
|
||||
size_t n = static_cast<size_t>(handle.size());
|
||||
std::unique_ptr<char[]> heap_buf;
|
||||
char stack_buf[DefaultStackBufferSize];
|
||||
char* used_buf = nullptr;
|
||||
rocksdb::CompressionType compression_type;
|
||||
|
||||
if (cache_options.persistent_cache &&
|
||||
!cache_options.persistent_cache->IsCompressed()) {
|
||||
status = PersistentCacheHelper::LookupUncompressedPage(cache_options,
|
||||
handle, contents);
|
||||
if (status.ok()) {
|
||||
// uncompressed page is found for the block handle
|
||||
return status;
|
||||
} else {
|
||||
// uncompressed page is not found
|
||||
if (ioptions.info_log && !status.IsNotFound()) {
|
||||
assert(!status.ok());
|
||||
ROCKS_LOG_INFO(ioptions.info_log,
|
||||
"Error reading from persistent cache. %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool got_from_prefetch_buffer = false;
|
||||
if (prefetch_buffer != nullptr &&
|
||||
prefetch_buffer->TryReadFromCache(
|
||||
handle.offset(),
|
||||
static_cast<size_t>(handle.size()) + kBlockTrailerSize, &slice)) {
|
||||
status =
|
||||
CheckBlockChecksum(read_options, footer, slice,
|
||||
static_cast<size_t>(handle.size()), file, handle);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
got_from_prefetch_buffer = true;
|
||||
used_buf = const_cast<char*>(slice.data());
|
||||
} else if (cache_options.persistent_cache &&
|
||||
cache_options.persistent_cache->IsCompressed()) {
|
||||
// lookup uncompressed cache mode p-cache
|
||||
status = PersistentCacheHelper::LookupRawPage(
|
||||
cache_options, handle, &heap_buf, n + kBlockTrailerSize);
|
||||
} else {
|
||||
status = Status::NotFound();
|
||||
}
|
||||
|
||||
if (!got_from_prefetch_buffer) {
|
||||
if (status.ok()) {
|
||||
// cache hit
|
||||
used_buf = heap_buf.get();
|
||||
slice = Slice(heap_buf.get(), n);
|
||||
} else {
|
||||
if (ioptions.info_log && !status.IsNotFound()) {
|
||||
assert(!status.ok());
|
||||
ROCKS_LOG_INFO(ioptions.info_log,
|
||||
"Error reading from persistent cache. %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
// cache miss read from device
|
||||
if (decompression_requested &&
|
||||
n + kBlockTrailerSize < DefaultStackBufferSize) {
|
||||
// If we've got a small enough hunk of data, read it in to the
|
||||
// trivially allocated stack buffer instead of needing a full malloc()
|
||||
used_buf = &stack_buf[0];
|
||||
} else {
|
||||
heap_buf = std::unique_ptr<char[]>(new char[n + kBlockTrailerSize]);
|
||||
used_buf = heap_buf.get();
|
||||
}
|
||||
|
||||
status = ReadBlock(file, footer, read_options, handle, &slice, used_buf);
|
||||
if (status.ok() && read_options.fill_cache &&
|
||||
cache_options.persistent_cache &&
|
||||
cache_options.persistent_cache->IsCompressed()) {
|
||||
// insert to raw cache
|
||||
PersistentCacheHelper::InsertRawPage(cache_options, handle, used_buf,
|
||||
n + kBlockTrailerSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
PERF_TIMER_GUARD(block_decompress_time);
|
||||
|
||||
compression_type = static_cast<rocksdb::CompressionType>(slice.data()[n]);
|
||||
|
||||
if (decompression_requested && compression_type != kNoCompression) {
|
||||
// compressed page, uncompress, update cache
|
||||
status = UncompressBlockContents(slice.data(), n, contents,
|
||||
footer.version(), compression_dict,
|
||||
ioptions);
|
||||
} else if (slice.data() != used_buf) {
|
||||
// the slice content is not the buffer provided
|
||||
*contents = BlockContents(Slice(slice.data(), n), false, compression_type);
|
||||
} else {
|
||||
// page is uncompressed, the buffer either stack or heap provided
|
||||
if (got_from_prefetch_buffer || used_buf == &stack_buf[0]) {
|
||||
heap_buf = std::unique_ptr<char[]>(new char[n]);
|
||||
memcpy(heap_buf.get(), used_buf, n);
|
||||
}
|
||||
*contents = BlockContents(std::move(heap_buf), n, true, compression_type);
|
||||
}
|
||||
|
||||
if (status.ok() && !got_from_prefetch_buffer && read_options.fill_cache &&
|
||||
cache_options.persistent_cache &&
|
||||
!cache_options.persistent_cache->IsCompressed()) {
|
||||
// insert to uncompressed cache
|
||||
PersistentCacheHelper::InsertUncompressedPage(cache_options, handle,
|
||||
*contents);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status UncompressBlockContentsForCompressionType(
|
||||
const char* data, size_t n, BlockContents* contents,
|
||||
uint32_t format_version, const Slice& compression_dict,
|
||||
@@ -568,9 +369,9 @@ Status UncompressBlockContentsForCompressionType(
|
||||
if(ShouldReportDetailedTime(ioptions.env, ioptions.statistics)){
|
||||
MeasureTime(ioptions.statistics, DECOMPRESSION_TIMES_NANOS,
|
||||
timer.ElapsedNanos());
|
||||
MeasureTime(ioptions.statistics, BYTES_DECOMPRESSED, contents->data.size());
|
||||
RecordTick(ioptions.statistics, NUMBER_BLOCK_DECOMPRESSED);
|
||||
}
|
||||
MeasureTime(ioptions.statistics, BYTES_DECOMPRESSED, contents->data.size());
|
||||
RecordTick(ioptions.statistics, NUMBER_BLOCK_DECOMPRESSED);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Block;
|
||||
class RandomAccessFile;
|
||||
struct ReadOptions;
|
||||
|
||||
|
||||
@@ -87,6 +87,13 @@ void GetContext::SaveValue(const Slice& value, SequenceNumber seq) {
|
||||
}
|
||||
}
|
||||
|
||||
void GetContext::RecordCounters(Tickers ticker, size_t val) {
|
||||
if (ticker == Tickers::TICKER_ENUM_MAX) {
|
||||
return;
|
||||
}
|
||||
tickers_value[ticker] += static_cast<uint64_t>(val);
|
||||
}
|
||||
|
||||
bool GetContext::SaveValue(const ParsedInternalKey& parsed_key,
|
||||
const Slice& value, Cleanable* value_pinner) {
|
||||
assert((state_ != kMerge && parsed_key.type != kTypeMerge) ||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/read_callback.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "table/block.h"
|
||||
|
||||
@@ -26,6 +27,7 @@ class GetContext {
|
||||
kMerge, // saver contains the current merge result (the operands)
|
||||
kBlobIndex,
|
||||
};
|
||||
uint64_t tickers_value[Tickers::TICKER_ENUM_MAX] = {0};
|
||||
|
||||
GetContext(const Comparator* ucmp, const MergeOperator* merge_operator,
|
||||
Logger* logger, Statistics* statistics, GetState init_state,
|
||||
@@ -72,6 +74,8 @@ class GetContext {
|
||||
return true;
|
||||
}
|
||||
|
||||
void RecordCounters(Tickers ticker, size_t val);
|
||||
|
||||
private:
|
||||
const Comparator* ucmp_;
|
||||
const MergeOperator* merge_operator_;
|
||||
|
||||
+34
-13
@@ -11,6 +11,7 @@
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_fetcher.h"
|
||||
#include "table/format.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/persistent_cache_helper.h"
|
||||
@@ -176,8 +177,13 @@ Status ReadProperties(const Slice& handle_value, RandomAccessFileReader* file,
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
Status s;
|
||||
s = ReadBlockContents(file, prefetch_buffer, footer, read_options, handle,
|
||||
&block_contents, ioptions, false /* decompress */);
|
||||
Slice compression_dict;
|
||||
PersistentCacheOptions cache_options;
|
||||
|
||||
BlockFetcher block_fetcher(
|
||||
file, prefetch_buffer, footer, read_options, handle, &block_contents,
|
||||
ioptions, false /* decompress */, compression_dict, cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -292,9 +298,14 @@ Status ReadTableProperties(RandomAccessFileReader* file, uint64_t file_size,
|
||||
BlockContents metaindex_contents;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
s = ReadBlockContents(file, nullptr /* prefetch_buffer */, footer,
|
||||
read_options, metaindex_handle, &metaindex_contents,
|
||||
ioptions, false /* decompress */);
|
||||
Slice compression_dict;
|
||||
PersistentCacheOptions cache_options;
|
||||
|
||||
BlockFetcher block_fetcher(
|
||||
file, nullptr /* prefetch_buffer */, footer, read_options,
|
||||
metaindex_handle, &metaindex_contents, ioptions, false /* decompress */,
|
||||
compression_dict, cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -350,9 +361,13 @@ Status FindMetaBlock(RandomAccessFileReader* file, uint64_t file_size,
|
||||
BlockContents metaindex_contents;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
s = ReadBlockContents(file, nullptr /* prefetch_buffer */, footer,
|
||||
read_options, metaindex_handle, &metaindex_contents,
|
||||
ioptions, false /* do decompression */);
|
||||
Slice compression_dict;
|
||||
PersistentCacheOptions cache_options;
|
||||
BlockFetcher block_fetcher(
|
||||
file, nullptr /* prefetch_buffer */, footer, read_options,
|
||||
metaindex_handle, &metaindex_contents, ioptions,
|
||||
false /* do decompression */, compression_dict, cache_options);
|
||||
s = block_fetcher.ReadBlockContents();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -384,9 +399,14 @@ Status ReadMetaBlock(RandomAccessFileReader* file,
|
||||
BlockContents metaindex_contents;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
status = ReadBlockContents(file, prefetch_buffer, footer, read_options,
|
||||
Slice compression_dict;
|
||||
PersistentCacheOptions cache_options;
|
||||
|
||||
BlockFetcher block_fetcher(file, prefetch_buffer, footer, read_options,
|
||||
metaindex_handle, &metaindex_contents, ioptions,
|
||||
false /* decompress */);
|
||||
false /* decompress */, compression_dict,
|
||||
cache_options);
|
||||
status = block_fetcher.ReadBlockContents();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -406,9 +426,10 @@ Status ReadMetaBlock(RandomAccessFileReader* file,
|
||||
}
|
||||
|
||||
// Reading metablock
|
||||
return ReadBlockContents(file, prefetch_buffer, footer, read_options,
|
||||
block_handle, contents, ioptions,
|
||||
false /* decompress */);
|
||||
BlockFetcher block_fetcher2(
|
||||
file, prefetch_buffer, footer, read_options, block_handle, contents,
|
||||
ioptions, false /* decompress */, compression_dict, cache_options);
|
||||
return block_fetcher2.ReadBlockContents();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -231,7 +231,8 @@ PartitionedFilterBlockReader::GetFilterPartition(
|
||||
}
|
||||
}
|
||||
return table_->GetFilter(/*prefetch_buffer*/ nullptr, fltr_blk_handle,
|
||||
is_a_filter_partition, no_io);
|
||||
is_a_filter_partition, no_io,
|
||||
/* get_context */ nullptr);
|
||||
} else {
|
||||
auto filter = table_->ReadFilter(prefetch_buffer, fltr_blk_handle,
|
||||
is_a_filter_partition);
|
||||
@@ -295,7 +296,8 @@ void PartitionedFilterBlockReader::CacheDependencies(bool pin) {
|
||||
const bool no_io = true;
|
||||
const bool is_a_filter_partition = true;
|
||||
auto filter = table_->GetFilter(prefetch_buffer.get(), handle,
|
||||
is_a_filter_partition, !no_io);
|
||||
is_a_filter_partition, !no_io,
|
||||
/* get_context */ nullptr);
|
||||
if (LIKELY(filter.IsSet())) {
|
||||
if (pin) {
|
||||
filter_map_[handle.offset()] = std::move(filter);
|
||||
|
||||
@@ -29,7 +29,8 @@ class MockedBlockBasedTable : public BlockBasedTable {
|
||||
|
||||
virtual CachableEntry<FilterBlockReader> GetFilter(
|
||||
FilePrefetchBuffer*, const BlockHandle& filter_blk_handle,
|
||||
const bool /* unused */, bool /* unused */) const override {
|
||||
const bool /* unused */, bool /* unused */,
|
||||
GetContext* /* unused */) const override {
|
||||
Slice slice = slices[filter_blk_handle.offset()];
|
||||
auto obj = new FullFilterBlockReader(
|
||||
nullptr, true, BlockContents(slice, false, kNoCompression),
|
||||
|
||||
+195
-23
@@ -51,6 +51,8 @@ int main() {
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/utilities/db_ttl.h"
|
||||
#include "rocksdb/utilities/transaction.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/compression.h"
|
||||
@@ -350,6 +352,10 @@ DEFINE_uint64(rate_limiter_bytes_per_sec, 0, "Set options.rate_limiter value.");
|
||||
DEFINE_bool(rate_limit_bg_reads, false,
|
||||
"Use options.rate_limiter on compaction reads");
|
||||
|
||||
DEFINE_bool(use_txn, false,
|
||||
"Use TransactionDB. Currently the default write policy is "
|
||||
"TxnDBWritePolicy::WRITE_PREPARED");
|
||||
|
||||
// Temporarily disable this to allows it to detect new bugs
|
||||
DEFINE_int32(compact_files_one_in, 0,
|
||||
"If non-zero, then CompactFiles() will be called one for every N "
|
||||
@@ -981,11 +987,22 @@ const uint32_t SharedState::SENTINEL = 0xffffffff;
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
struct ThreadState {
|
||||
uint32_t tid; // 0..n-1
|
||||
Random rand; // Has different seeds for different threads
|
||||
uint32_t tid; // 0..n-1
|
||||
Random rand; // Has different seeds for different threads
|
||||
SharedState* shared;
|
||||
Stats stats;
|
||||
std::queue<std::pair<uint64_t, const Snapshot*> > snapshot_queue;
|
||||
struct SnapshotState {
|
||||
const Snapshot* snapshot;
|
||||
// The cf from which we did a Get at this stapshot
|
||||
int cf_at;
|
||||
// The key with which we did a Get at this stapshot
|
||||
std::string key;
|
||||
// The status of the Get
|
||||
Status status;
|
||||
// The value of the Get
|
||||
std::string value;
|
||||
};
|
||||
std::queue<std::pair<uint64_t, SnapshotState> > snapshot_queue;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rand(1000 + index + _shared->GetSeed()), shared(_shared) {}
|
||||
@@ -1109,6 +1126,9 @@ class StressTest {
|
||||
: NewBloomFilterPolicy(FLAGS_bloom_bits, false)
|
||||
: nullptr),
|
||||
db_(nullptr),
|
||||
#ifndef ROCKSDB_LITE
|
||||
txn_db_(nullptr),
|
||||
#endif
|
||||
new_column_family_name_(1),
|
||||
num_times_reopened_(0) {
|
||||
if (FLAGS_destroy_db_initially) {
|
||||
@@ -1316,6 +1336,32 @@ class StressTest {
|
||||
}
|
||||
|
||||
private:
|
||||
Status AssertSame(DB* db, ColumnFamilyHandle* cf,
|
||||
ThreadState::SnapshotState& snap_state) {
|
||||
ReadOptions ropt;
|
||||
ropt.snapshot = snap_state.snapshot;
|
||||
PinnableSlice exp_v(&snap_state.value);
|
||||
exp_v.PinSelf();
|
||||
Status s;
|
||||
PinnableSlice v;
|
||||
s = db->Get(ropt, cf, snap_state.key, &v);
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
return s;
|
||||
}
|
||||
if (snap_state.status != s) {
|
||||
return Status::Corruption("The snapshot gave inconsistent results: (" +
|
||||
snap_state.status.ToString() + ") vs. (" +
|
||||
s.ToString() + ")");
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (exp_v != v) {
|
||||
return Status::Corruption("The snapshot gave inconsistent values: (" +
|
||||
exp_v.ToString() + ") vs. (" + v.ToString() +
|
||||
")");
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
static void ThreadBody(void* v) {
|
||||
ThreadState* thread = reinterpret_cast<ThreadState*>(v);
|
||||
@@ -1641,6 +1687,32 @@ class StressTest {
|
||||
return db_->SetOptions(cfh, opts);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status NewTxn(WriteOptions& write_opts, Transaction** txn) {
|
||||
if (!FLAGS_use_txn) {
|
||||
return Status::InvalidArgument("NewTxn when FLAGS_use_txn is not set");
|
||||
}
|
||||
static std::atomic<uint64_t> txn_id = {0};
|
||||
TransactionOptions txn_options;
|
||||
*txn = txn_db_->BeginTransaction(write_opts, txn_options);
|
||||
auto istr = std::to_string(txn_id.fetch_add(1));
|
||||
Status s = (*txn)->SetName("xid" + istr);
|
||||
return s;
|
||||
}
|
||||
|
||||
Status CommitTxn(Transaction* txn) {
|
||||
if (!FLAGS_use_txn) {
|
||||
return Status::InvalidArgument("CommitTxn when FLAGS_use_txn is not set");
|
||||
}
|
||||
Status s = txn->Prepare();
|
||||
if (s.ok()) {
|
||||
s = txn->Commit();
|
||||
}
|
||||
delete txn;
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
void OperateDb(ThreadState* thread) {
|
||||
ReadOptions read_opts(FLAGS_verify_checksum, true);
|
||||
WriteOptions write_opts;
|
||||
@@ -1667,7 +1739,8 @@ class StressTest {
|
||||
thread->stats.FinishedSingleOp();
|
||||
MutexLock l(thread->shared->GetMutex());
|
||||
while (!thread->snapshot_queue.empty()) {
|
||||
db_->ReleaseSnapshot(thread->snapshot_queue.front().second);
|
||||
db_->ReleaseSnapshot(
|
||||
thread->snapshot_queue.front().second.snapshot);
|
||||
thread->snapshot_queue.pop();
|
||||
}
|
||||
thread->shared->IncVotedReopen();
|
||||
@@ -1781,19 +1854,6 @@ class StressTest {
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
if (FLAGS_acquire_snapshot_one_in > 0 &&
|
||||
thread->rand.Uniform(FLAGS_acquire_snapshot_one_in) == 0) {
|
||||
thread->snapshot_queue.emplace(
|
||||
std::min(FLAGS_ops_per_thread - 1, i + FLAGS_snapshot_hold_ops),
|
||||
db_->GetSnapshot());
|
||||
}
|
||||
if (!thread->snapshot_queue.empty()) {
|
||||
while (i == thread->snapshot_queue.front().first) {
|
||||
db_->ReleaseSnapshot(thread->snapshot_queue.front().second);
|
||||
thread->snapshot_queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
const double completed_ratio =
|
||||
static_cast<double>(i) / FLAGS_ops_per_thread;
|
||||
const int64_t base_key = static_cast<int64_t>(
|
||||
@@ -1807,8 +1867,38 @@ class StressTest {
|
||||
l.reset(new MutexLock(
|
||||
shared->GetMutexForKey(rand_column_family, rand_key)));
|
||||
}
|
||||
|
||||
auto column_family = column_families_[rand_column_family];
|
||||
|
||||
if (FLAGS_acquire_snapshot_one_in > 0 &&
|
||||
thread->rand.Uniform(FLAGS_acquire_snapshot_one_in) == 0) {
|
||||
auto snapshot = db_->GetSnapshot();
|
||||
ReadOptions ropt;
|
||||
ropt.snapshot = snapshot;
|
||||
std::string value_at;
|
||||
// When taking a snapshot, we also read a key from that snapshot. We
|
||||
// will later read the same key before releasing the snapshot and verify
|
||||
// that the results are the same.
|
||||
auto status_at = db_->Get(ropt, column_family, key, &value_at);
|
||||
ThreadState::SnapshotState snap_state = {snapshot, rand_column_family,
|
||||
keystr, status_at, value_at};
|
||||
thread->snapshot_queue.emplace(
|
||||
std::min(FLAGS_ops_per_thread - 1, i + FLAGS_snapshot_hold_ops),
|
||||
snap_state);
|
||||
}
|
||||
while (!thread->snapshot_queue.empty() &&
|
||||
i == thread->snapshot_queue.front().first) {
|
||||
auto snap_state = thread->snapshot_queue.front().second;
|
||||
assert(snap_state.snapshot);
|
||||
Status s =
|
||||
AssertSame(db_, column_families_[snap_state.cf_at], snap_state);
|
||||
if (!s.ok()) {
|
||||
VerificationAbort(shared, "Snapshot gave inconsistent state", s);
|
||||
}
|
||||
db_->ReleaseSnapshot(snap_state.snapshot);
|
||||
thread->snapshot_queue.pop();
|
||||
}
|
||||
|
||||
int prob_op = thread->rand.Uniform(100);
|
||||
if (prob_op >= 0 && prob_op < (int)FLAGS_readpercent) {
|
||||
// OPERATION read
|
||||
@@ -1885,9 +1975,35 @@ class StressTest {
|
||||
shared->Put(rand_column_family, rand_key, value_base);
|
||||
Status s;
|
||||
if (FLAGS_use_merge) {
|
||||
s = db_->Merge(write_opts, column_family, key, v);
|
||||
if (!FLAGS_use_txn) {
|
||||
s = db_->Merge(write_opts, column_family, key, v);
|
||||
} else {
|
||||
#ifndef ROCKSDB_LITE
|
||||
Transaction* txn;
|
||||
s = NewTxn(write_opts, &txn);
|
||||
if (s.ok()) {
|
||||
s = txn->Merge(column_family, key, v);
|
||||
if (s.ok()) {
|
||||
s = CommitTxn(txn);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
s = db_->Put(write_opts, column_family, key, v);
|
||||
if (!FLAGS_use_txn) {
|
||||
s = db_->Put(write_opts, column_family, key, v);
|
||||
} else {
|
||||
#ifndef ROCKSDB_LITE
|
||||
Transaction* txn;
|
||||
s = NewTxn(write_opts, &txn);
|
||||
if (s.ok()) {
|
||||
s = txn->Put(column_family, key, v);
|
||||
if (s.ok()) {
|
||||
s = CommitTxn(txn);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "put or merge error: %s\n", s.ToString().c_str());
|
||||
@@ -1921,7 +2037,21 @@ class StressTest {
|
||||
// otherwise.
|
||||
if (shared->AllowsOverwrite(rand_column_family, rand_key)) {
|
||||
shared->Delete(rand_column_family, rand_key);
|
||||
Status s = db_->Delete(write_opts, column_family, key);
|
||||
Status s;
|
||||
if (!FLAGS_use_txn) {
|
||||
s = db_->Delete(write_opts, column_family, key);
|
||||
} else {
|
||||
#ifndef ROCKSDB_LITE
|
||||
Transaction* txn;
|
||||
s = NewTxn(write_opts, &txn);
|
||||
if (s.ok()) {
|
||||
s = txn->Delete(column_family, key);
|
||||
if (s.ok()) {
|
||||
s = CommitTxn(txn);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
thread->stats.AddDeletes(1);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "delete error: %s\n", s.ToString().c_str());
|
||||
@@ -1929,7 +2059,21 @@ class StressTest {
|
||||
}
|
||||
} else {
|
||||
shared->SingleDelete(rand_column_family, rand_key);
|
||||
Status s = db_->SingleDelete(write_opts, column_family, key);
|
||||
Status s;
|
||||
if (!FLAGS_use_txn) {
|
||||
s = db_->SingleDelete(write_opts, column_family, key);
|
||||
} else {
|
||||
#ifndef ROCKSDB_LITE
|
||||
Transaction* txn;
|
||||
s = NewTxn(write_opts, &txn);
|
||||
if (s.ok()) {
|
||||
s = txn->SingleDelete(column_family, key);
|
||||
if (s.ok()) {
|
||||
s = CommitTxn(txn);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
thread->stats.AddSingleDeletes(1);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "single delete error: %s\n",
|
||||
@@ -2067,6 +2211,12 @@ class StressTest {
|
||||
}
|
||||
}
|
||||
|
||||
void VerificationAbort(SharedState* shared, std::string msg, Status s) const {
|
||||
printf("Verification failed: %s. Status is %s\n", msg.c_str(),
|
||||
s.ToString().c_str());
|
||||
shared->SetVerificationFailure();
|
||||
}
|
||||
|
||||
void VerificationAbort(SharedState* shared, std::string msg, int cf,
|
||||
int64_t key) const {
|
||||
printf("Verification failed for column family %d key %" PRIi64 ": %s\n", cf, key,
|
||||
@@ -2138,6 +2288,8 @@ class StressTest {
|
||||
void PrintEnv() const {
|
||||
fprintf(stdout, "RocksDB version : %d.%d\n", kMajorVersion,
|
||||
kMinorVersion);
|
||||
fprintf(stdout, "TransactionDB : %s\n",
|
||||
FLAGS_use_txn ? "true" : "false");
|
||||
fprintf(stdout, "Column families : %d\n", FLAGS_column_families);
|
||||
if (!FLAGS_test_batches_snapshots) {
|
||||
fprintf(stdout, "Clear CFs one in : %d\n",
|
||||
@@ -2210,6 +2362,9 @@ class StressTest {
|
||||
|
||||
void Open() {
|
||||
assert(db_ == nullptr);
|
||||
#ifndef ROCKSDB_LITE
|
||||
assert(txn_db_ == nullptr);
|
||||
#endif
|
||||
BlockBasedTableOptions block_based_options;
|
||||
block_based_options.block_cache = cache_;
|
||||
block_based_options.block_cache_compressed = compressed_cache_;
|
||||
@@ -2382,8 +2537,19 @@ class StressTest {
|
||||
options_.listeners.emplace_back(
|
||||
new DbStressListener(FLAGS_db, options_.db_paths));
|
||||
options_.create_missing_column_families = true;
|
||||
s = DB::Open(DBOptions(options_), FLAGS_db, cf_descriptors,
|
||||
&column_families_, &db_);
|
||||
if (!FLAGS_use_txn) {
|
||||
s = DB::Open(DBOptions(options_), FLAGS_db, cf_descriptors,
|
||||
&column_families_, &db_);
|
||||
} else {
|
||||
#ifndef ROCKSDB_LITE
|
||||
TransactionDBOptions txn_db_options;
|
||||
// For the moment it is sufficient to test WRITE_PREPARED policy
|
||||
txn_db_options.write_policy = TxnDBWritePolicy::WRITE_PREPARED;
|
||||
s = TransactionDB::Open(options_, txn_db_options, FLAGS_db,
|
||||
cf_descriptors, &column_families_, &txn_db_);
|
||||
db_ = txn_db_;
|
||||
#endif
|
||||
}
|
||||
assert(!s.ok() || column_families_.size() ==
|
||||
static_cast<size_t>(FLAGS_column_families));
|
||||
} else {
|
||||
@@ -2409,6 +2575,9 @@ class StressTest {
|
||||
column_families_.clear();
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
#ifndef ROCKSDB_LITE
|
||||
txn_db_ = nullptr;
|
||||
#endif
|
||||
|
||||
num_times_reopened_++;
|
||||
auto now = FLAGS_env->NowMicros();
|
||||
@@ -2429,6 +2598,9 @@ class StressTest {
|
||||
std::shared_ptr<Cache> compressed_cache_;
|
||||
std::shared_ptr<const FilterPolicy> filter_policy_;
|
||||
DB* db_;
|
||||
#ifndef ROCKSDB_LITE
|
||||
TransactionDB* txn_db_;
|
||||
#endif
|
||||
Options options_;
|
||||
std::vector<ColumnFamilyHandle*> column_families_;
|
||||
std::vector<std::string> column_family_names_;
|
||||
|
||||
@@ -151,6 +151,7 @@ Status DeleteScheduler::MarkAsTrash(const std::string& file_path,
|
||||
Status s;
|
||||
if (DeleteScheduler::IsTrashFile(file_path)) {
|
||||
// This is already a trash file
|
||||
*trash_file = file_path;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ Status RandomTransactionInserter::DBGet(
|
||||
// four digits and zero end char
|
||||
char prefix_buf[5];
|
||||
// Pad prefix appropriately so we can iterate over each set
|
||||
assert(set_i + 1 <= 9999);
|
||||
snprintf(prefix_buf, sizeof(prefix_buf), "%.4u", set_i + 1);
|
||||
// key format: [SET#][random#]
|
||||
std::string skey = ToString(ikey);
|
||||
@@ -263,6 +264,7 @@ Status RandomTransactionInserter::Verify(DB* db, uint16_t num_sets,
|
||||
for (uint16_t set_i : set_vec) {
|
||||
// four digits and zero end char
|
||||
char prefix_buf[5];
|
||||
assert(set_i + 1 <= 9999);
|
||||
snprintf(prefix_buf, sizeof(prefix_buf), "%.4u", set_i + 1);
|
||||
uint64_t total = 0;
|
||||
|
||||
|
||||
@@ -436,6 +436,83 @@ TEST_P(WritePreparedTransactionTest, MaybeUpdateOldCommitMap) {
|
||||
MaybeUpdateOldCommitMapTestWithNext(p, c, s, ns, false);
|
||||
}
|
||||
|
||||
// Test that the entries in old_commit_map_ get garbage collected properly
|
||||
TEST_P(WritePreparedTransactionTest, OldCommitMapGC) {
|
||||
const size_t snapshot_cache_bits = 0;
|
||||
const size_t commit_cache_bits = 0;
|
||||
DBImpl* mock_db = new DBImpl(options, dbname);
|
||||
std::unique_ptr<WritePreparedTxnDBMock> wp_db(new WritePreparedTxnDBMock(
|
||||
mock_db, txn_db_options, snapshot_cache_bits, commit_cache_bits));
|
||||
|
||||
SequenceNumber seq = 0;
|
||||
// Take the first snapshot that overlaps with two txn
|
||||
auto prep_seq = ++seq;
|
||||
wp_db->AddPrepared(prep_seq);
|
||||
auto prep_seq2 = ++seq;
|
||||
wp_db->AddPrepared(prep_seq2);
|
||||
auto snap_seq1 = seq;
|
||||
wp_db->TakeSnapshot(snap_seq1);
|
||||
auto commit_seq = ++seq;
|
||||
wp_db->AddCommitted(prep_seq, commit_seq);
|
||||
auto commit_seq2 = ++seq;
|
||||
wp_db->AddCommitted(prep_seq2, commit_seq2);
|
||||
// Take the 2nd and 3rd snapshot that overlap with the same txn
|
||||
prep_seq = ++seq;
|
||||
wp_db->AddPrepared(prep_seq);
|
||||
auto snap_seq2 = seq;
|
||||
wp_db->TakeSnapshot(snap_seq2);
|
||||
seq++;
|
||||
auto snap_seq3 = seq;
|
||||
wp_db->TakeSnapshot(snap_seq3);
|
||||
seq++;
|
||||
commit_seq = ++seq;
|
||||
wp_db->AddCommitted(prep_seq, commit_seq);
|
||||
// Make sure max_evicted_seq_ will be larger than 2nd snapshot by evicting the
|
||||
// only item in the commit_cache_ via another commit.
|
||||
prep_seq = ++seq;
|
||||
wp_db->AddPrepared(prep_seq);
|
||||
commit_seq = ++seq;
|
||||
wp_db->AddCommitted(prep_seq, commit_seq);
|
||||
|
||||
// Verify that the evicted commit entries for all snapshots are in the
|
||||
// old_commit_map_
|
||||
{
|
||||
ASSERT_FALSE(wp_db->old_commit_map_empty_.load());
|
||||
ReadLock rl(&wp_db->old_commit_map_mutex_);
|
||||
ASSERT_EQ(3, wp_db->old_commit_map_.size());
|
||||
ASSERT_EQ(2, wp_db->old_commit_map_[snap_seq1].size());
|
||||
ASSERT_EQ(1, wp_db->old_commit_map_[snap_seq2].size());
|
||||
ASSERT_EQ(1, wp_db->old_commit_map_[snap_seq3].size());
|
||||
}
|
||||
|
||||
// Verify that the 2nd snapshot is cleaned up after the release
|
||||
wp_db->ReleaseSnapshotInternal(snap_seq2);
|
||||
{
|
||||
ASSERT_FALSE(wp_db->old_commit_map_empty_.load());
|
||||
ReadLock rl(&wp_db->old_commit_map_mutex_);
|
||||
ASSERT_EQ(2, wp_db->old_commit_map_.size());
|
||||
ASSERT_EQ(2, wp_db->old_commit_map_[snap_seq1].size());
|
||||
ASSERT_EQ(1, wp_db->old_commit_map_[snap_seq3].size());
|
||||
}
|
||||
|
||||
// Verify that the 1st snapshot is cleaned up after the release
|
||||
wp_db->ReleaseSnapshotInternal(snap_seq1);
|
||||
{
|
||||
ASSERT_FALSE(wp_db->old_commit_map_empty_.load());
|
||||
ReadLock rl(&wp_db->old_commit_map_mutex_);
|
||||
ASSERT_EQ(1, wp_db->old_commit_map_.size());
|
||||
ASSERT_EQ(1, wp_db->old_commit_map_[snap_seq3].size());
|
||||
}
|
||||
|
||||
// Verify that the 3rd snapshot is cleaned up after the release
|
||||
wp_db->ReleaseSnapshotInternal(snap_seq3);
|
||||
{
|
||||
ASSERT_TRUE(wp_db->old_commit_map_empty_.load());
|
||||
ReadLock rl(&wp_db->old_commit_map_mutex_);
|
||||
ASSERT_EQ(0, wp_db->old_commit_map_.size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(WritePreparedTransactionTest, CheckAgainstSnapshotsTest) {
|
||||
std::vector<SequenceNumber> snapshots = {100l, 200l, 300l, 400l, 500l,
|
||||
600l, 700l, 800l, 900l};
|
||||
@@ -480,20 +557,24 @@ TEST_P(WritePreparedTransactionTest, SnapshotConcurrentAccessTest) {
|
||||
// We have a sync point in the method under test after checking each snapshot.
|
||||
// If you increase the max number of snapshots in this test, more sync points
|
||||
// in the methods must also be added.
|
||||
const std::vector<SequenceNumber> snapshots = {10l, 20l, 30l, 40l, 50l,
|
||||
60l, 70l, 80l, 90l, 100l};
|
||||
const std::vector<SequenceNumber> snapshots = {10l, 20l, 30l, 40l, 50l, 60l};
|
||||
// TODO(myabandeh): increase the snapshots list for pre-release tests
|
||||
// const std::vector<SequenceNumber> snapshots = {10l, 20l, 30l, 40l, 50l,
|
||||
// 60l, 70l, 80l, 90l, 100l};
|
||||
const size_t snapshot_cache_bits = 2;
|
||||
// Safety check to express the intended size in the test. Can be adjusted if
|
||||
// the snapshots lists changed.
|
||||
assert((1ul << snapshot_cache_bits) * 2 + 2 == snapshots.size());
|
||||
assert((1ul << snapshot_cache_bits) + 2 == snapshots.size());
|
||||
SequenceNumber version = 1000l;
|
||||
// Choose the cache size so that the new snapshot list could replace all the
|
||||
// existing items in the cache and also have some overflow.
|
||||
DBImpl* mock_db = new DBImpl(options, dbname);
|
||||
std::unique_ptr<WritePreparedTxnDBMock> wp_db(
|
||||
new WritePreparedTxnDBMock(mock_db, txn_db_options, snapshot_cache_bits));
|
||||
// Add up to 2 items that do not fit into the cache
|
||||
for (size_t old_size = 1; old_size <= wp_db->SNAPSHOT_CACHE_SIZE + 2;
|
||||
// TODO(myabandeh): increase this number for pre-release tests
|
||||
const size_t extra = 1;
|
||||
// Add up to extra items that do not fit into the cache
|
||||
for (size_t old_size = 1; old_size <= wp_db->SNAPSHOT_CACHE_SIZE + extra;
|
||||
old_size++) {
|
||||
const std::vector<SequenceNumber> old_snapshots(
|
||||
snapshots.begin(), snapshots.begin() + old_size);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "utilities/transactions/write_prepared_txn_db.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
@@ -234,8 +235,8 @@ bool WritePreparedTxnDB::IsInSnapshot(uint64_t prep_seq,
|
||||
// delayed_prepared_. Also we move evicted entries from commit cache to
|
||||
// old_commit_map_ if it overlaps with any snapshot. Since prep_seq <=
|
||||
// max_evicted_seq_, we have three cases: i) in delayed_prepared_, ii) in
|
||||
// old_commit_map_, iii) committed with no conflict with any snapshot (i)
|
||||
// delayed_prepared_ is checked above
|
||||
// old_commit_map_, iii) committed with no conflict with any snapshot. Case
|
||||
// (i) delayed_prepared_ is checked above
|
||||
if (max_evicted_seq < snapshot_seq) { // then (ii) cannot be the case
|
||||
// only (iii) is the case: committed
|
||||
// commit_seq <= max_evicted_seq_ < snapshot_seq => commit_seq <
|
||||
@@ -255,12 +256,17 @@ bool WritePreparedTxnDB::IsInSnapshot(uint64_t prep_seq,
|
||||
return true;
|
||||
}
|
||||
{
|
||||
// We should not normally reach here
|
||||
// TODO(myabandeh): check only if snapshot_seq is in the list of snaphots
|
||||
// We should not normally reach here unless sapshot_seq is old. This is a
|
||||
// rare case and it is ok to pay the cost of mutex ReadLock for such old,
|
||||
// reading transactions.
|
||||
ReadLock rl(&old_commit_map_mutex_);
|
||||
auto old_commit_entry = old_commit_map_.find(prep_seq);
|
||||
if (old_commit_entry == old_commit_map_.end() ||
|
||||
old_commit_entry->second <= snapshot_seq) {
|
||||
auto prep_set_entry = old_commit_map_.find(snapshot_seq);
|
||||
bool found = prep_set_entry != old_commit_map_.end();
|
||||
if (found) {
|
||||
auto& vec = prep_set_entry->second;
|
||||
found = std::binary_search(vec.begin(), vec.end(), prep_seq);
|
||||
}
|
||||
if (!found) {
|
||||
ROCKSDB_LOG_DETAILS(
|
||||
info_log_, "IsInSnapshot %" PRIu64 " in %" PRIu64 " returns %" PRId32,
|
||||
prep_seq, snapshot_seq, 1);
|
||||
@@ -322,6 +328,9 @@ void WritePreparedTxnDB::AddCommitted(uint64_t prepare_seq,
|
||||
bool to_be_evicted = GetCommitEntry(indexed_seq, &evicted_64b, &evicted);
|
||||
if (to_be_evicted) {
|
||||
auto prev_max = max_evicted_seq_.load(std::memory_order_acquire);
|
||||
ROCKSDB_LOG_DETAILS(info_log_,
|
||||
"Evicting %" PRIu64 ",%" PRIu64 " with max %" PRIu64,
|
||||
evicted.prep_seq, evicted.commit_seq, prev_max);
|
||||
if (prev_max < evicted.commit_seq) {
|
||||
// Inc max in larger steps to avoid frequent updates
|
||||
auto max_evicted_seq = evicted.commit_seq + INC_STEP_FOR_MAX_EVICTED;
|
||||
@@ -431,6 +440,36 @@ const std::vector<SequenceNumber> WritePreparedTxnDB::GetSnapshotListFromDB(
|
||||
return db_impl_->snapshots().GetAll(nullptr, max);
|
||||
}
|
||||
|
||||
void WritePreparedTxnDB::ReleaseSnapshot(const Snapshot* snapshot) {
|
||||
auto snap_seq = snapshot->GetSequenceNumber();
|
||||
ReleaseSnapshotInternal(snap_seq);
|
||||
db_impl_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
void WritePreparedTxnDB::ReleaseSnapshotInternal(
|
||||
const SequenceNumber snap_seq) {
|
||||
// relax is enough since max increases monotonically, i.e., if snap_seq <
|
||||
// old_max => snap_seq < new_max as well.
|
||||
if (snap_seq < max_evicted_seq_.load(std::memory_order_relaxed)) {
|
||||
// Then this is a rare case that transaction did not finish before max
|
||||
// advances. It is expected for a few read-only backup snapshots. For such
|
||||
// snapshots we might have kept around a couple of entries in the
|
||||
// old_commit_map_. Check and do garbage collection if that is the case.
|
||||
bool need_gc = false;
|
||||
{
|
||||
ReadLock rl(&old_commit_map_mutex_);
|
||||
auto prep_set_entry = old_commit_map_.find(snap_seq);
|
||||
need_gc = prep_set_entry != old_commit_map_.end();
|
||||
}
|
||||
if (need_gc) {
|
||||
WriteLock wl(&old_commit_map_mutex_);
|
||||
old_commit_map_.erase(snap_seq);
|
||||
old_commit_map_empty_.store(old_commit_map_.empty(),
|
||||
std::memory_order_release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WritePreparedTxnDB::UpdateSnapshots(
|
||||
const std::vector<SequenceNumber>& snapshots,
|
||||
const SequenceNumber& version) {
|
||||
@@ -541,8 +580,8 @@ void WritePreparedTxnDB::CheckAgainstSnapshots(const CommitEntry& evicted) {
|
||||
bool WritePreparedTxnDB::MaybeUpdateOldCommitMap(
|
||||
const uint64_t& prep_seq, const uint64_t& commit_seq,
|
||||
const uint64_t& snapshot_seq, const bool next_is_larger = true) {
|
||||
// If we do not store an entry in old_commit_map we assume it is committed in
|
||||
// all snapshots. if commit_seq <= snapshot_seq, it is considered already in
|
||||
// If we do not store an entry in old_commit_map_ we assume it is committed in
|
||||
// all snapshots. If commit_seq <= snapshot_seq, it is considered already in
|
||||
// the snapshot so we need not to keep the entry around for this snapshot.
|
||||
if (commit_seq <= snapshot_seq) {
|
||||
// continue the search if the next snapshot could be smaller than commit_seq
|
||||
@@ -552,9 +591,11 @@ bool WritePreparedTxnDB::MaybeUpdateOldCommitMap(
|
||||
if (prep_seq <= snapshot_seq) { // overlapping range
|
||||
WriteLock wl(&old_commit_map_mutex_);
|
||||
old_commit_map_empty_.store(false, std::memory_order_release);
|
||||
old_commit_map_[prep_seq] = commit_seq;
|
||||
// Storing once is enough. No need to check it for other snapshots.
|
||||
return false;
|
||||
auto& vec = old_commit_map_[snapshot_seq];
|
||||
vec.insert(std::upper_bound(vec.begin(), vec.end(), prep_seq), prep_seq);
|
||||
// We need to store it once for each overlapping snapshot. Returning true to
|
||||
// continue the search if there is more overlapping snapshot.
|
||||
return true;
|
||||
}
|
||||
// continue the search if the next snapshot could be larger than prep_seq
|
||||
return next_is_larger;
|
||||
|
||||
@@ -90,6 +90,8 @@ class WritePreparedTxnDB : public PessimisticTransactionDB {
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot) override;
|
||||
|
||||
// Check whether the transaction that wrote the value with seqeunce number seq
|
||||
// is visible to the snapshot with sequence number snapshot_seq
|
||||
bool IsInSnapshot(uint64_t seq, uint64_t snapshot_seq) const;
|
||||
@@ -198,6 +200,7 @@ class WritePreparedTxnDB : public PessimisticTransactionDB {
|
||||
friend class WritePreparedTransactionTest_AdvanceMaxEvictedSeqBasicTest_Test;
|
||||
friend class WritePreparedTransactionTest_BasicRecoveryTest_Test;
|
||||
friend class WritePreparedTransactionTest_IsInSnapshotEmptyMapTest_Test;
|
||||
friend class WritePreparedTransactionTest_OldCommitMapGC_Test;
|
||||
friend class WritePreparedTransactionTest_RollbackTest_Test;
|
||||
|
||||
void Init(const TransactionDBOptions& /* unused */);
|
||||
@@ -269,6 +272,10 @@ class WritePreparedTxnDB : public PessimisticTransactionDB {
|
||||
virtual const std::vector<SequenceNumber> GetSnapshotListFromDB(
|
||||
SequenceNumber max);
|
||||
|
||||
// Will be called by the public ReleaseSnapshot method. Does the maintenance
|
||||
// internal to WritePreparedTxnDB
|
||||
void ReleaseSnapshotInternal(const SequenceNumber snap_seq);
|
||||
|
||||
// Update the list of snapshots corresponding to the soon-to-be-updated
|
||||
// max_eviceted_seq_. Thread-safety: this function can be called concurrently.
|
||||
// The concurrent invocations of this function is equivalent to a serial
|
||||
@@ -287,9 +294,8 @@ class WritePreparedTxnDB : public PessimisticTransactionDB {
|
||||
|
||||
// Add a new entry to old_commit_map_ if prep_seq <= snapshot_seq <
|
||||
// commit_seq. Return false if checking the next snapshot(s) is not needed.
|
||||
// This is the case if the entry already added to old_commit_map_ or none of
|
||||
// the next snapshots could satisfy the condition. next_is_larger: the next
|
||||
// snapshot will be a larger value
|
||||
// This is the case if none of the next snapshots could satisfy the condition.
|
||||
// next_is_larger: the next snapshot will be a larger value
|
||||
bool MaybeUpdateOldCommitMap(const uint64_t& prep_seq,
|
||||
const uint64_t& commit_seq,
|
||||
const uint64_t& snapshot_seq,
|
||||
@@ -333,10 +339,14 @@ class WritePreparedTxnDB : public PessimisticTransactionDB {
|
||||
// it to be too large either as it would cause stalls by doing too much
|
||||
// maintenance work under the lock.
|
||||
size_t INC_STEP_FOR_MAX_EVICTED = 1;
|
||||
// A map of the evicted entries from commit_cache_ that has to be kept around
|
||||
// to service the old snapshots. This is expected to be empty normally.
|
||||
// A map from old snapshots (expected to be used by a few read-only txns) to
|
||||
// prpared sequence number of the evicted entries from commit_cache_ that
|
||||
// overlaps with such snapshot. These are the prepared sequence numbers that
|
||||
// the snapshot, to which they are mapped, cannot assume to be committed just
|
||||
// because it is no longer in the commit_cache_. The vector must be sorted
|
||||
// after each update.
|
||||
// Thread-safety is provided with old_commit_map_mutex_.
|
||||
std::map<uint64_t, uint64_t> old_commit_map_;
|
||||
std::map<SequenceNumber, std::vector<SequenceNumber>> old_commit_map_;
|
||||
// A set of long-running prepared transactions that are not finished by the
|
||||
// time max_evicted_seq_ advances their sequence number. This is expected to
|
||||
// be empty normally. Thread-safety is provided with prepared_mutex_.
|
||||
|
||||
Reference in new issue
Block a user