mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59495ff26a | |||
| 3acb421f68 | |||
| 01cd296b07 | |||
| 3c3112d33e | |||
| 7b491e4218 | |||
| 73782b260d | |||
| 13f9b5716d | |||
| 8a0c1e7ce5 | |||
| d03498175d | |||
| 9b1817df6e | |||
| 50386918ff | |||
| 50a63bc48a |
+18
-1
@@ -1,5 +1,18 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 7.7.2 (10/05/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in iterator refresh that was not freeing up SuperVersion, which could cause excessive resource pinniung (#10770).
|
||||
* Fixed a bug where RocksDB could be doing compaction endlessly when allow_ingest_behind is true and the bottommost level is not filled (#10767).
|
||||
|
||||
### Behavior Changes
|
||||
* Sanitize min_write_buffer_number_to_merge to 1 if atomic flush is enabled to prevent unexpected data loss when WAL is disabled in a multi-column-family setting (#10773).
|
||||
|
||||
## 7.7.1 (09/26/2022)
|
||||
### Bug Fixes
|
||||
* Fixed an optimistic transaction validation bug caused by DBImpl::GetLatestSequenceForKey() returning non-latest seq for merge (#10724).
|
||||
* Fixed a bug in iterator refresh which could segfault for DeleteRange users (#10739).
|
||||
|
||||
## 7.7.0 (09/18/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a hang when an operation such as `GetLiveFiles` or `CreateNewBackup` is asked to trigger and wait for memtable flush on a read-only DB. Such indirect requests for memtable flush are now ignored on a read-only DB.
|
||||
* Fixed bug where `FlushWAL(true /* sync */)` (used by `GetLiveFilesStorageInfo()`, which is used by checkpoint and backup) could cause parallel writes at the tail of a WAL file to never be synced.
|
||||
@@ -10,6 +23,10 @@
|
||||
* Fix a bug in key range overlap checking with concurrent compactions when user-defined timestamp is enabled. User-defined timestamps should be EXCLUDED when checking if two ranges overlap.
|
||||
* Fixed a bug where the blob cache prepopulating logic did not consider the secondary cache (see #10603).
|
||||
* Fixed the rocksdb.num.sst.read.per.level, rocksdb.num.index.and.filter.blocks.read.per.level and rocksdb.num.level.read.per.multiget stats in the MultiGet coroutines
|
||||
* Fix a bug in io_uring_prep_cancel in AbortIO API for posix which expects sqe->addr to match with read request submitted and wrong paramter was being passed.
|
||||
* Fixed a regression in iterator performance when the entire DB is a single memtable introduced in #10449. The fix is in #10705 and #10716.
|
||||
* Fix a bug in io_uring_prep_cancel in AbortIO API for posix which expects sqe->addr to match with read request submitted and wrong paramter was being passed.
|
||||
* Fixed a regression in iterator performance when the entire DB is a single memtable introduced in #10449. The fix is in #10705 and #10716.
|
||||
|
||||
### Public API changes
|
||||
* Add `rocksdb_column_family_handle_get_id`, `rocksdb_column_family_handle_get_name` to get name, id of column family in C API
|
||||
|
||||
+50
-29
@@ -45,6 +45,7 @@ void ArenaWrappedDBIter::Init(
|
||||
sv_number_ = version_number;
|
||||
read_options_ = read_options;
|
||||
allow_refresh_ = allow_refresh;
|
||||
memtable_range_tombstone_iter_ = nullptr;
|
||||
}
|
||||
|
||||
Status ArenaWrappedDBIter::Refresh() {
|
||||
@@ -58,48 +59,68 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
uint64_t cur_sv_number = cfd_->GetSuperVersionNumber();
|
||||
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:1");
|
||||
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:2");
|
||||
auto reinit_internal_iter = [&]() {
|
||||
Env* env = db_iter_->env();
|
||||
db_iter_->~DBIter();
|
||||
arena_.~Arena();
|
||||
new (&arena_) Arena();
|
||||
|
||||
SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_);
|
||||
SequenceNumber latest_seq = db_impl_->GetLatestSequenceNumber();
|
||||
if (read_callback_) {
|
||||
read_callback_->Refresh(latest_seq);
|
||||
}
|
||||
Init(env, read_options_, *(cfd_->ioptions()), sv->mutable_cf_options,
|
||||
sv->current, latest_seq,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
cur_sv_number, read_callback_, db_impl_, cfd_, expose_blob_index_,
|
||||
allow_refresh_);
|
||||
|
||||
InternalIterator* internal_iter = db_impl_->NewInternalIterator(
|
||||
read_options_, cfd_, sv, &arena_, latest_seq,
|
||||
/* allow_unprepared_value */ true, /* db_iter */ this);
|
||||
SetIterUnderDBIter(internal_iter);
|
||||
};
|
||||
while (true) {
|
||||
if (sv_number_ != cur_sv_number) {
|
||||
Env* env = db_iter_->env();
|
||||
db_iter_->~DBIter();
|
||||
arena_.~Arena();
|
||||
new (&arena_) Arena();
|
||||
|
||||
SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_);
|
||||
SequenceNumber latest_seq = db_impl_->GetLatestSequenceNumber();
|
||||
if (read_callback_) {
|
||||
read_callback_->Refresh(latest_seq);
|
||||
}
|
||||
Init(env, read_options_, *(cfd_->ioptions()), sv->mutable_cf_options,
|
||||
sv->current, latest_seq,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
cur_sv_number, read_callback_, db_impl_, cfd_, expose_blob_index_,
|
||||
allow_refresh_);
|
||||
|
||||
InternalIterator* internal_iter = db_impl_->NewInternalIterator(
|
||||
read_options_, cfd_, sv, &arena_, latest_seq,
|
||||
/* allow_unprepared_value */ true, /* db_iter */ this);
|
||||
SetIterUnderDBIter(internal_iter);
|
||||
reinit_internal_iter();
|
||||
break;
|
||||
} else {
|
||||
SequenceNumber latest_seq = db_impl_->GetLatestSequenceNumber();
|
||||
// Refresh range-tombstones in MemTable
|
||||
if (!read_options_.ignore_range_deletions) {
|
||||
assert(memtable_range_tombstone_iter_ != nullptr);
|
||||
if (memtable_range_tombstone_iter_ != nullptr) {
|
||||
SuperVersion* sv = cfd_->GetThreadLocalSuperVersion(db_impl_);
|
||||
auto t = sv->mem->NewRangeTombstoneIterator(
|
||||
read_options_, latest_seq, false /* immutable_memtable */);
|
||||
delete *memtable_range_tombstone_iter_;
|
||||
if (t == nullptr || t->empty()) {
|
||||
*memtable_range_tombstone_iter_ = nullptr;
|
||||
SuperVersion* sv = cfd_->GetThreadLocalSuperVersion(db_impl_);
|
||||
TEST_SYNC_POINT_CALLBACK("ArenaWrappedDBIter::Refresh:SV", nullptr);
|
||||
auto t = sv->mem->NewRangeTombstoneIterator(
|
||||
read_options_, latest_seq, false /* immutable_memtable */);
|
||||
if (!t || t->empty()) {
|
||||
// If memtable_range_tombstone_iter_ points to a non-empty tombstone
|
||||
// iterator, then it means sv->mem is not the memtable that
|
||||
// memtable_range_tombstone_iter_ points to, so SV must have changed
|
||||
// after the sv_number_ != cur_sv_number check above. We will fall
|
||||
// back to re-init the InternalIterator, and the tombstone iterator
|
||||
// will be freed during db_iter destruction there.
|
||||
if (memtable_range_tombstone_iter_) {
|
||||
assert(!*memtable_range_tombstone_iter_ ||
|
||||
sv_number_ != cfd_->GetSuperVersionNumber());
|
||||
}
|
||||
delete t;
|
||||
} else { // current mutable memtable has range tombstones
|
||||
if (!memtable_range_tombstone_iter_) {
|
||||
delete t;
|
||||
db_impl_->ReturnAndCleanupSuperVersion(cfd_, sv);
|
||||
// The memtable under DBIter did not have range tombstone before
|
||||
// refresh.
|
||||
reinit_internal_iter();
|
||||
break;
|
||||
} else {
|
||||
delete *memtable_range_tombstone_iter_;
|
||||
*memtable_range_tombstone_iter_ = new TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(t),
|
||||
&cfd_->internal_comparator(), nullptr, nullptr);
|
||||
}
|
||||
cfd_->ReturnThreadLocalSuperVersion(sv);
|
||||
}
|
||||
db_impl_->ReturnAndCleanupSuperVersion(cfd_, sv);
|
||||
}
|
||||
// Refresh latest sequence number
|
||||
db_iter_->set_sequence(latest_seq);
|
||||
|
||||
@@ -107,6 +107,8 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
ReadCallback* read_callback_;
|
||||
bool expose_blob_index_ = false;
|
||||
bool allow_refresh_ = true;
|
||||
// If this is nullptr, it means the mutable memtable does not contain range
|
||||
// tombstone when added under this DBIter.
|
||||
TruncatedRangeDelIterator** memtable_range_tombstone_iter_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
+17
-1
@@ -233,6 +233,21 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
result.min_write_buffer_number_to_merge = 1;
|
||||
}
|
||||
|
||||
if (db_options.atomic_flush && result.min_write_buffer_number_to_merge > 1) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options.logger,
|
||||
"Currently, if atomic_flush is true, then triggering flush for any "
|
||||
"column family internally (non-manual flush) will trigger flushing "
|
||||
"all column families even if the number of memtables is smaller "
|
||||
"min_write_buffer_number_to_merge. Therefore, configuring "
|
||||
"min_write_buffer_number_to_merge > 1 is not compatible and should "
|
||||
"be satinized to 1. Not doing so will lead to data loss and "
|
||||
"inconsistent state across multiple column families when WAL is "
|
||||
"disabled, which is a common setting for atomic flush");
|
||||
|
||||
result.min_write_buffer_number_to_merge = 1;
|
||||
}
|
||||
|
||||
if (result.num_levels < 1) {
|
||||
result.num_levels = 1;
|
||||
}
|
||||
@@ -1144,7 +1159,8 @@ Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
MergeIteratorBuilder merge_iter_builder(&internal_comparator_, &arena);
|
||||
merge_iter_builder.AddIterator(
|
||||
super_version->mem->NewIterator(read_opts, &arena));
|
||||
super_version->imm->AddIterators(read_opts, &merge_iter_builder);
|
||||
super_version->imm->AddIterators(read_opts, &merge_iter_builder,
|
||||
false /* add_range_tombstone_iter */);
|
||||
ScopedArenaIterator memtable_iter(merge_iter_builder.Finish());
|
||||
|
||||
auto read_seq = super_version->current->version_set()->LastSequence();
|
||||
|
||||
@@ -7958,6 +7958,42 @@ TEST_F(DBCompactionTest, BottomPriCompactionCountsTowardConcurrencyLimit) {
|
||||
compact_range_thread.join();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, BottommostFileCompactionAllowIngestBehind) {
|
||||
// allow_ingest_behind prevents seqnum zeroing, and could cause
|
||||
// compaction loop with reason kBottommostFiles.
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.allow_ingest_behind = true;
|
||||
options.comparator = BytewiseComparator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
WriteOptions write_opts;
|
||||
ASSERT_OK(db_->Put(write_opts, "infinite", "compaction loop"));
|
||||
ASSERT_OK(db_->Put(write_opts, "infinite", "loop"));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_OK(db_->Put(write_opts, "bumpseqnum", ""));
|
||||
ASSERT_OK(Flush());
|
||||
auto snapshot = db_->GetSnapshot();
|
||||
// Bump up oldest_snapshot_seqnum_ in VersionStorageInfo.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
bool compacted = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* /* arg */) {
|
||||
// There should not be a compaction.
|
||||
compacted = true;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// Wait for compaction to be scheduled.
|
||||
env_->SleepForMicroseconds(2000000);
|
||||
ASSERT_FALSE(compacted);
|
||||
// The following assert can be used to check for compaction loop:
|
||||
// it used to wait forever before the fix.
|
||||
// ASSERT_OK(dbfull()->TEST_WaitForCompact(true /* wait_unscheduled */));
|
||||
}
|
||||
|
||||
#endif // !defined(ROCKSDB_LITE)
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+26
-23
@@ -1803,31 +1803,31 @@ InternalIterator* DBImpl::NewInternalIterator(
|
||||
&cfd->internal_comparator(), arena,
|
||||
!read_options.total_order_seek &&
|
||||
super_version->mutable_cf_options.prefix_extractor != nullptr);
|
||||
// Collect iterator for mutable mem
|
||||
merge_iter_builder.AddIterator(
|
||||
super_version->mem->NewIterator(read_options, arena));
|
||||
// Collect iterator for mutable memtable
|
||||
auto mem_iter = super_version->mem->NewIterator(read_options, arena);
|
||||
Status s;
|
||||
if (!read_options.ignore_range_deletions) {
|
||||
TruncatedRangeDelIterator* mem_tombstone_iter = nullptr;
|
||||
auto range_del_iter = super_version->mem->NewRangeTombstoneIterator(
|
||||
read_options, sequence, false /* immutable_memtable */);
|
||||
if (range_del_iter == nullptr || range_del_iter->empty()) {
|
||||
delete range_del_iter;
|
||||
merge_iter_builder.AddRangeTombstoneIterator(nullptr);
|
||||
} else {
|
||||
merge_iter_builder.AddRangeTombstoneIterator(
|
||||
new TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(range_del_iter),
|
||||
&cfd->ioptions()->internal_comparator, nullptr /* smallest */,
|
||||
nullptr /* largest */));
|
||||
mem_tombstone_iter = new TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(range_del_iter),
|
||||
&cfd->ioptions()->internal_comparator, nullptr /* smallest */,
|
||||
nullptr /* largest */);
|
||||
}
|
||||
merge_iter_builder.AddPointAndTombstoneIterator(mem_iter,
|
||||
mem_tombstone_iter);
|
||||
} else {
|
||||
merge_iter_builder.AddIterator(mem_iter);
|
||||
}
|
||||
|
||||
// Collect all needed child iterators for immutable memtables
|
||||
if (s.ok()) {
|
||||
super_version->imm->AddIterators(read_options, &merge_iter_builder);
|
||||
if (!read_options.ignore_range_deletions) {
|
||||
s = super_version->imm->AddRangeTombstoneIterators(read_options, arena,
|
||||
merge_iter_builder);
|
||||
}
|
||||
super_version->imm->AddIterators(read_options, &merge_iter_builder,
|
||||
!read_options.ignore_range_deletions);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::NewInternalIterator:StatusCallback", &s);
|
||||
if (s.ok()) {
|
||||
@@ -3662,14 +3662,16 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
if (oldest_snapshot > bottommost_files_mark_threshold_) {
|
||||
CfdList cf_scheduled;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(oldest_snapshot);
|
||||
if (!cfd->current()
|
||||
->storage_info()
|
||||
->BottommostFilesMarkedForCompaction()
|
||||
.empty()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
cf_scheduled.push_back(cfd);
|
||||
if (!cfd->ioptions()->allow_ingest_behind) {
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(oldest_snapshot);
|
||||
if (!cfd->current()
|
||||
->storage_info()
|
||||
->BottommostFilesMarkedForCompaction()
|
||||
.empty()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
cf_scheduled.push_back(cfd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3678,7 +3680,8 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
// mutex might be unlocked during the loop, making the result inaccurate.
|
||||
SequenceNumber new_bottommost_files_mark_threshold = kMaxSequenceNumber;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (CfdListContains(cf_scheduled, cfd)) {
|
||||
if (CfdListContains(cf_scheduled, cfd) ||
|
||||
cfd->ioptions()->allow_ingest_behind) {
|
||||
continue;
|
||||
}
|
||||
new_bottommost_files_mark_threshold = std::min(
|
||||
|
||||
@@ -3756,9 +3756,11 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
// triggered soon anyway.
|
||||
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
for (auto* my_cfd : *versions_->GetColumnFamilySet()) {
|
||||
bottommost_files_mark_threshold_ = std::min(
|
||||
bottommost_files_mark_threshold_,
|
||||
my_cfd->current()->storage_info()->bottommost_files_mark_threshold());
|
||||
if (!my_cfd->ioptions()->allow_ingest_behind) {
|
||||
bottommost_files_mark_threshold_ = std::min(
|
||||
bottommost_files_mark_threshold_,
|
||||
my_cfd->current()->storage_info()->bottommost_files_mark_threshold());
|
||||
}
|
||||
}
|
||||
|
||||
// Whenever we install new SuperVersion, we might need to issue new flushes or
|
||||
|
||||
@@ -89,6 +89,7 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
if (iter_.iter()) {
|
||||
iter_.iter()->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
status_.PermitUncheckedError();
|
||||
assert(timestamp_size_ == user_comparator_.timestamp_size());
|
||||
}
|
||||
|
||||
|
||||
@@ -3232,6 +3232,28 @@ TEST_F(DBIteratorTest, BackwardIterationOnInplaceUpdateMemtable) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, IteratorRefreshReturnSV) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
std::unique_ptr<Iterator> iter{db_->NewIterator(ReadOptions())};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ArenaWrappedDBIter::Refresh:SV", [&](void*) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "dummy", "new SV"));
|
||||
// This makes the local SV obselete.
|
||||
ASSERT_OK(Flush());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(iter->Refresh());
|
||||
iter.reset();
|
||||
// iter used to not cleanup SV, so the Close() below would hit an assertion
|
||||
// error.
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+17
-1
@@ -2303,7 +2303,6 @@ TEST_F(DBRangeDelTest, TombstoneOnlyLevel) {
|
||||
InternalIterator* level_iter = sv->current->TEST_GetLevelIterator(
|
||||
read_options, &merge_iter_builder, 1 /* level */, true);
|
||||
// This is needed to make LevelIterator range tombstone aware
|
||||
merge_iter_builder.AddIterator(level_iter);
|
||||
auto miter = merge_iter_builder.Finish();
|
||||
auto k = Key(3);
|
||||
IterKey target;
|
||||
@@ -2688,6 +2687,23 @@ TEST_F(DBRangeDelTest, PrefixSentinelKey) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, RefreshMemtableIter) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
ReadOptions ro;
|
||||
ro.read_tier = kMemtableTier;
|
||||
std::unique_ptr<Iterator> iter{db_->NewIterator(ro)};
|
||||
ASSERT_OK(Flush());
|
||||
// First refresh reinits iter, which had a bug where
|
||||
// iter.memtable_range_tombstone_iter_ was not set to nullptr, and caused
|
||||
// subsequent refresh to double free.
|
||||
ASSERT_OK(iter->Refresh());
|
||||
ASSERT_OK(iter->Refresh());
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+18
-9
@@ -958,7 +958,24 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
return true; // to continue to the next seq
|
||||
}
|
||||
|
||||
s->seq = seq;
|
||||
if (s->seq == kMaxSequenceNumber) {
|
||||
s->seq = seq;
|
||||
}
|
||||
|
||||
s->seq = std::max(s->seq, max_covering_tombstone_seq);
|
||||
|
||||
if (ts_sz > 0 && s->timestamp != nullptr) {
|
||||
if (!s->timestamp->empty()) {
|
||||
assert(ts_sz == s->timestamp->size());
|
||||
}
|
||||
// TODO optimize for smaller size ts
|
||||
const std::string kMaxTs(ts_sz, '\xff');
|
||||
if (s->timestamp->empty() ||
|
||||
user_comparator->CompareTimestamp(*(s->timestamp), kMaxTs) == 0) {
|
||||
Slice ts = ExtractTimestampFromUserKey(user_key_slice, ts_sz);
|
||||
s->timestamp->assign(ts.data(), ts_sz);
|
||||
}
|
||||
}
|
||||
|
||||
if ((type == kTypeValue || type == kTypeMerge || type == kTypeBlobIndex ||
|
||||
type == kTypeWideColumnEntity) &&
|
||||
@@ -1039,10 +1056,6 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->is_blob_index) = (type == kTypeBlobIndex);
|
||||
}
|
||||
|
||||
if (ts_sz > 0 && s->timestamp != nullptr) {
|
||||
Slice ts = ExtractTimestampFromUserKey(user_key_slice, ts_sz);
|
||||
s->timestamp->assign(ts.data(), ts.size());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case kTypeDeletion:
|
||||
@@ -1058,10 +1071,6 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
}
|
||||
} else {
|
||||
*(s->status) = Status::NotFound();
|
||||
if (ts_sz > 0 && s->timestamp != nullptr) {
|
||||
Slice ts = ExtractTimestampFromUserKey(user_key_slice, ts_sz);
|
||||
s->timestamp->assign(ts.data(), ts.size());
|
||||
}
|
||||
}
|
||||
*(s->found_final_value) = true;
|
||||
return false;
|
||||
|
||||
+26
-28
@@ -210,30 +210,6 @@ Status MemTableListVersion::AddRangeTombstoneIterators(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status MemTableListVersion::AddRangeTombstoneIterators(
|
||||
const ReadOptions& read_opts, Arena* /*arena*/,
|
||||
MergeIteratorBuilder& builder) {
|
||||
// Except for snapshot read, using kMaxSequenceNumber is OK because these
|
||||
// are immutable memtables.
|
||||
SequenceNumber read_seq = read_opts.snapshot != nullptr
|
||||
? read_opts.snapshot->GetSequenceNumber()
|
||||
: kMaxSequenceNumber;
|
||||
for (auto& m : memlist_) {
|
||||
auto range_del_iter = m->NewRangeTombstoneIterator(
|
||||
read_opts, read_seq, true /* immutale_memtable */);
|
||||
if (range_del_iter == nullptr || range_del_iter->empty()) {
|
||||
delete range_del_iter;
|
||||
builder.AddRangeTombstoneIterator(nullptr);
|
||||
} else {
|
||||
builder.AddRangeTombstoneIterator(new TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(range_del_iter),
|
||||
&m->GetInternalKeyComparator(), nullptr /* smallest */,
|
||||
nullptr /* largest */));
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void MemTableListVersion::AddIterators(
|
||||
const ReadOptions& options, std::vector<InternalIterator*>* iterator_list,
|
||||
Arena* arena) {
|
||||
@@ -242,11 +218,33 @@ void MemTableListVersion::AddIterators(
|
||||
}
|
||||
}
|
||||
|
||||
void MemTableListVersion::AddIterators(
|
||||
const ReadOptions& options, MergeIteratorBuilder* merge_iter_builder) {
|
||||
void MemTableListVersion::AddIterators(const ReadOptions& options,
|
||||
MergeIteratorBuilder* merge_iter_builder,
|
||||
bool add_range_tombstone_iter) {
|
||||
for (auto& m : memlist_) {
|
||||
merge_iter_builder->AddIterator(
|
||||
m->NewIterator(options, merge_iter_builder->GetArena()));
|
||||
auto mem_iter = m->NewIterator(options, merge_iter_builder->GetArena());
|
||||
if (!add_range_tombstone_iter || options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(mem_iter);
|
||||
} else {
|
||||
// Except for snapshot read, using kMaxSequenceNumber is OK because these
|
||||
// are immutable memtables.
|
||||
SequenceNumber read_seq = options.snapshot != nullptr
|
||||
? options.snapshot->GetSequenceNumber()
|
||||
: kMaxSequenceNumber;
|
||||
TruncatedRangeDelIterator* mem_tombstone_iter = nullptr;
|
||||
auto range_del_iter = m->NewRangeTombstoneIterator(
|
||||
options, read_seq, true /* immutale_memtable */);
|
||||
if (range_del_iter == nullptr || range_del_iter->empty()) {
|
||||
delete range_del_iter;
|
||||
} else {
|
||||
mem_tombstone_iter = new TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(range_del_iter),
|
||||
&m->GetInternalKeyComparator(), nullptr /* smallest */,
|
||||
nullptr /* largest */);
|
||||
}
|
||||
merge_iter_builder->AddPointAndTombstoneIterator(mem_iter,
|
||||
mem_tombstone_iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -111,15 +111,13 @@ class MemTableListVersion {
|
||||
Status AddRangeTombstoneIterators(const ReadOptions& read_opts, Arena* arena,
|
||||
RangeDelAggregator* range_del_agg);
|
||||
|
||||
Status AddRangeTombstoneIterators(const ReadOptions& read_opts, Arena* arena,
|
||||
MergeIteratorBuilder& builder);
|
||||
|
||||
void AddIterators(const ReadOptions& options,
|
||||
std::vector<InternalIterator*>* iterator_list,
|
||||
Arena* arena);
|
||||
|
||||
void AddIterators(const ReadOptions& options,
|
||||
MergeIteratorBuilder* merge_iter_builder);
|
||||
MergeIteratorBuilder* merge_iter_builder,
|
||||
bool add_range_tombstone_iter);
|
||||
|
||||
uint64_t GetTotalNumEntries() const;
|
||||
|
||||
|
||||
+48
-27
@@ -941,18 +941,18 @@ namespace {
|
||||
class LevelIterator final : public InternalIterator {
|
||||
public:
|
||||
// @param read_options Must outlive this iterator.
|
||||
LevelIterator(TableCache* table_cache, const ReadOptions& read_options,
|
||||
const FileOptions& file_options,
|
||||
const InternalKeyComparator& icomparator,
|
||||
const LevelFilesBrief* flevel,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
bool should_sample, HistogramImpl* file_read_hist,
|
||||
TableReaderCaller caller, bool skip_filters, int level,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const std::vector<AtomicCompactionUnitBoundary>*
|
||||
compaction_boundaries = nullptr,
|
||||
bool allow_unprepared_value = false,
|
||||
MergeIteratorBuilder* merge_iter_builder = nullptr)
|
||||
LevelIterator(
|
||||
TableCache* table_cache, const ReadOptions& read_options,
|
||||
const FileOptions& file_options, const InternalKeyComparator& icomparator,
|
||||
const LevelFilesBrief* flevel,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
bool should_sample, HistogramImpl* file_read_hist,
|
||||
TableReaderCaller caller, bool skip_filters, int level,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const std::vector<AtomicCompactionUnitBoundary>* compaction_boundaries =
|
||||
nullptr,
|
||||
bool allow_unprepared_value = false,
|
||||
TruncatedRangeDelIterator**** range_tombstone_iter_ptr_ = nullptr)
|
||||
: table_cache_(table_cache),
|
||||
read_options_(read_options),
|
||||
file_options_(file_options),
|
||||
@@ -975,10 +975,8 @@ class LevelIterator final : public InternalIterator {
|
||||
to_return_sentinel_(false) {
|
||||
// Empty level is not supported.
|
||||
assert(flevel_ != nullptr && flevel_->num_files > 0);
|
||||
if (merge_iter_builder && !read_options.ignore_range_deletions) {
|
||||
// lazily initialize range_tombstone_iter_ together with file_iter_
|
||||
merge_iter_builder->AddRangeTombstoneIterator(nullptr,
|
||||
&range_tombstone_iter_);
|
||||
if (range_tombstone_iter_ptr_) {
|
||||
*range_tombstone_iter_ptr_ = &range_tombstone_iter_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1840,14 +1838,22 @@ InternalIterator* Version::TEST_GetLevelIterator(
|
||||
int level, bool allow_unprepared_value) {
|
||||
auto* arena = merge_iter_builder->GetArena();
|
||||
auto* mem = arena->AllocateAligned(sizeof(LevelIterator));
|
||||
return new (mem) LevelIterator(
|
||||
TruncatedRangeDelIterator*** tombstone_iter_ptr = nullptr;
|
||||
auto level_iter = new (mem) LevelIterator(
|
||||
cfd_->table_cache(), read_options, file_options_,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
mutable_cf_options_.prefix_extractor, should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
nullptr /* range_del_agg */, nullptr /* compaction_boundaries */,
|
||||
allow_unprepared_value, merge_iter_builder);
|
||||
allow_unprepared_value, &tombstone_iter_ptr);
|
||||
if (read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(level_iter);
|
||||
} else {
|
||||
merge_iter_builder->AddPointAndTombstoneIterator(
|
||||
level_iter, nullptr /* tombstone_iter */, tombstone_iter_ptr);
|
||||
}
|
||||
return level_iter;
|
||||
}
|
||||
|
||||
uint64_t VersionStorageInfo::GetEstimatedActiveKeys() const {
|
||||
@@ -1927,10 +1933,10 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
auto* arena = merge_iter_builder->GetArena();
|
||||
if (level == 0) {
|
||||
// Merge all level zero files together since they may overlap
|
||||
TruncatedRangeDelIterator* iter = nullptr;
|
||||
TruncatedRangeDelIterator* tombstone_iter = nullptr;
|
||||
for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
|
||||
const auto& file = storage_info_.LevelFilesBrief(0).files[i];
|
||||
merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator(
|
||||
auto table_iter = cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(),
|
||||
*file.file_metadata, /*range_del_agg=*/nullptr,
|
||||
mutable_cf_options_.prefix_extractor, nullptr,
|
||||
@@ -1938,9 +1944,13 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
TableReaderCaller::kUserIterator, arena,
|
||||
/*skip_filters=*/false, /*level=*/0, max_file_size_for_l0_meta_pin_,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value, &iter));
|
||||
if (!read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddRangeTombstoneIterator(iter);
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value,
|
||||
&tombstone_iter);
|
||||
if (read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(table_iter);
|
||||
} else {
|
||||
merge_iter_builder->AddPointAndTombstoneIterator(table_iter,
|
||||
tombstone_iter);
|
||||
}
|
||||
}
|
||||
if (should_sample) {
|
||||
@@ -1957,14 +1967,21 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
// walks through the non-overlapping files in the level, opening them
|
||||
// lazily.
|
||||
auto* mem = arena->AllocateAligned(sizeof(LevelIterator));
|
||||
merge_iter_builder->AddIterator(new (mem) LevelIterator(
|
||||
TruncatedRangeDelIterator*** tombstone_iter_ptr = nullptr;
|
||||
auto level_iter = new (mem) LevelIterator(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
mutable_cf_options_.prefix_extractor, should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
/*range_del_agg=*/nullptr, /*compaction_boundaries=*/nullptr,
|
||||
allow_unprepared_value, merge_iter_builder));
|
||||
allow_unprepared_value, &tombstone_iter_ptr);
|
||||
if (read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(level_iter);
|
||||
} else {
|
||||
merge_iter_builder->AddPointAndTombstoneIterator(
|
||||
level_iter, nullptr /* tombstone_iter */, tombstone_iter_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2897,7 +2914,9 @@ void VersionStorageInfo::PrepareForVersionAppend(
|
||||
GenerateFileIndexer();
|
||||
GenerateLevelFilesBrief();
|
||||
GenerateLevel0NonOverlapping();
|
||||
GenerateBottommostFiles();
|
||||
if (!immutable_options.allow_ingest_behind) {
|
||||
GenerateBottommostFiles();
|
||||
}
|
||||
GenerateFileLocationIndex();
|
||||
}
|
||||
|
||||
@@ -3337,7 +3356,9 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
}
|
||||
}
|
||||
ComputeFilesMarkedForCompaction();
|
||||
ComputeBottommostFilesMarkedForCompaction();
|
||||
if (!immutable_options.allow_ingest_behind) {
|
||||
ComputeBottommostFilesMarkedForCompaction();
|
||||
}
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
ComputeExpiredTtlFiles(immutable_options, mutable_cf_options.ttl);
|
||||
}
|
||||
|
||||
Vendored
+6
-4
@@ -1115,10 +1115,11 @@ class PosixFileSystem : public FileSystem {
|
||||
// Prepare the cancel request.
|
||||
struct io_uring_sqe* sqe;
|
||||
sqe = io_uring_get_sqe(iu);
|
||||
// prep_cancel changed API in liburing, but we need to support both old
|
||||
// and new versions so do it by hand
|
||||
io_uring_prep_cancel(sqe, 0, 0);
|
||||
sqe->addr = reinterpret_cast<uint64_t>(posix_handle);
|
||||
|
||||
// In order to cancel the request, sqe->addr of cancel request should
|
||||
// match with the read request submitted which is posix_handle->iov.
|
||||
io_uring_prep_cancel(sqe, &posix_handle->iov, 0);
|
||||
// Sets sqe->user_data to posix_handle.
|
||||
io_uring_sqe_set_data(sqe, posix_handle);
|
||||
|
||||
// submit the request.
|
||||
@@ -1146,6 +1147,7 @@ class PosixFileSystem : public FileSystem {
|
||||
}
|
||||
assert(cqe != nullptr);
|
||||
|
||||
// Returns cqe->user_data.
|
||||
Posix_IOHandle* posix_handle =
|
||||
static_cast<Posix_IOHandle*>(io_uring_cqe_get_data(cqe));
|
||||
assert(posix_handle->iu == iu);
|
||||
|
||||
Vendored
+3
-1
@@ -899,8 +899,10 @@ IOStatus PosixRandomAccessFile::ReadAsync(
|
||||
struct io_uring_sqe* sqe;
|
||||
sqe = io_uring_get_sqe(iu);
|
||||
|
||||
io_uring_prep_readv(sqe, fd_, &posix_handle->iov, 1, posix_handle->offset);
|
||||
io_uring_prep_readv(sqe, fd_, /*sqe->addr=*/&posix_handle->iov,
|
||||
/*sqe->len=*/1, /*sqe->offset=*/posix_handle->offset);
|
||||
|
||||
// Sets sqe->user_data to posix_handle.
|
||||
io_uring_sqe_set_data(sqe, posix_handle);
|
||||
|
||||
// Step 4: io_uring_submit
|
||||
|
||||
@@ -271,7 +271,10 @@ struct AdvancedColumnFamilyOptions {
|
||||
// read amplification because a get request has to check in all of these
|
||||
// files. Also, an in-memory merge may result in writing lesser
|
||||
// data to storage if there are duplicate records in each of these
|
||||
// individual write buffers. Default: 1
|
||||
// individual write buffers.
|
||||
// If atomic flush is enabled (options.atomic_flush == true), then this
|
||||
// option will be sanitized to 1.
|
||||
// Default: 1
|
||||
int min_write_buffer_number_to_merge = 1;
|
||||
|
||||
// DEPRECATED
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// minor or major version number planned for release.
|
||||
#define ROCKSDB_MAJOR 7
|
||||
#define ROCKSDB_MINOR 7
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_PATCH 2
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
+17
-12
@@ -241,6 +241,23 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key,
|
||||
if (*seq_ == kMaxSequenceNumber) {
|
||||
*seq_ = parsed_key.sequence;
|
||||
}
|
||||
if (max_covering_tombstone_seq_) {
|
||||
*seq_ = std::max(*seq_, *max_covering_tombstone_seq_);
|
||||
}
|
||||
}
|
||||
|
||||
size_t ts_sz = ucmp_->timestamp_size();
|
||||
if (ts_sz > 0 && timestamp_ != nullptr) {
|
||||
if (!timestamp_->empty()) {
|
||||
assert(ts_sz == timestamp_->size());
|
||||
}
|
||||
// TODO optimize for small size ts
|
||||
const std::string kMaxTs(ts_sz, '\xff');
|
||||
if (timestamp_->empty() ||
|
||||
ucmp_->CompareTimestamp(*timestamp_, kMaxTs) == 0) {
|
||||
Slice ts = ExtractTimestampFromUserKey(parsed_key.user_key, ts_sz);
|
||||
timestamp_->assign(ts.data(), ts.size());
|
||||
}
|
||||
}
|
||||
|
||||
auto type = parsed_key.type;
|
||||
@@ -359,13 +376,6 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state_ == kFound) {
|
||||
size_t ts_sz = ucmp_->timestamp_size();
|
||||
if (ts_sz > 0 && timestamp_ != nullptr) {
|
||||
Slice ts = ExtractTimestampFromUserKey(parsed_key.user_key, ts_sz);
|
||||
timestamp_->assign(ts.data(), ts.size());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case kTypeDeletion:
|
||||
@@ -377,11 +387,6 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key,
|
||||
assert(state_ == kNotFound || state_ == kMerge);
|
||||
if (kNotFound == state_) {
|
||||
state_ = kDeleted;
|
||||
size_t ts_sz = ucmp_->timestamp_size();
|
||||
if (ts_sz > 0 && timestamp_ != nullptr) {
|
||||
Slice ts = ExtractTimestampFromUserKey(parsed_key.user_key, ts_sz);
|
||||
timestamp_->assign(ts.data(), ts.size());
|
||||
}
|
||||
} else if (kMerge == state_) {
|
||||
state_ = kFound;
|
||||
Merge(nullptr);
|
||||
|
||||
+29
-12
@@ -1295,22 +1295,40 @@ void MergeIteratorBuilder::AddIterator(InternalIterator* iter) {
|
||||
}
|
||||
}
|
||||
|
||||
void MergeIteratorBuilder::AddRangeTombstoneIterator(
|
||||
TruncatedRangeDelIterator* iter, TruncatedRangeDelIterator*** iter_ptr) {
|
||||
if (!use_merging_iter) {
|
||||
void MergeIteratorBuilder::AddPointAndTombstoneIterator(
|
||||
InternalIterator* point_iter, TruncatedRangeDelIterator* tombstone_iter,
|
||||
TruncatedRangeDelIterator*** tombstone_iter_ptr) {
|
||||
// tombstone_iter_ptr != nullptr means point_iter is a LevelIterator.
|
||||
bool add_range_tombstone = tombstone_iter ||
|
||||
!merge_iter->range_tombstone_iters_.empty() ||
|
||||
tombstone_iter_ptr;
|
||||
if (!use_merging_iter && (add_range_tombstone || first_iter)) {
|
||||
use_merging_iter = true;
|
||||
if (first_iter) {
|
||||
merge_iter->AddIterator(first_iter);
|
||||
first_iter = nullptr;
|
||||
}
|
||||
}
|
||||
merge_iter->AddRangeTombstoneIterator(iter);
|
||||
if (iter_ptr) {
|
||||
// This is needed instead of setting to &range_tombstone_iters_[i] directly
|
||||
// here since the memory address of range_tombstone_iters_[i] might change
|
||||
// during vector resizing.
|
||||
range_del_iter_ptrs_.emplace_back(
|
||||
merge_iter->range_tombstone_iters_.size() - 1, iter_ptr);
|
||||
if (use_merging_iter) {
|
||||
merge_iter->AddIterator(point_iter);
|
||||
if (add_range_tombstone) {
|
||||
// If there was a gap, fill in nullptr as empty range tombstone iterators.
|
||||
while (merge_iter->range_tombstone_iters_.size() <
|
||||
merge_iter->children_.size() - 1) {
|
||||
merge_iter->AddRangeTombstoneIterator(nullptr);
|
||||
}
|
||||
merge_iter->AddRangeTombstoneIterator(tombstone_iter);
|
||||
}
|
||||
|
||||
if (tombstone_iter_ptr) {
|
||||
// This is needed instead of setting to &range_tombstone_iters_[i]
|
||||
// directly here since the memory address of range_tombstone_iters_[i]
|
||||
// might change during vector resizing.
|
||||
range_del_iter_ptrs_.emplace_back(
|
||||
merge_iter->range_tombstone_iters_.size() - 1, tombstone_iter_ptr);
|
||||
}
|
||||
} else {
|
||||
first_iter = point_iter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,8 +1341,7 @@ InternalIterator* MergeIteratorBuilder::Finish(ArenaWrappedDBIter* db_iter) {
|
||||
for (auto& p : range_del_iter_ptrs_) {
|
||||
*(p.second) = &(merge_iter->range_tombstone_iters_[p.first]);
|
||||
}
|
||||
if (db_iter) {
|
||||
assert(!merge_iter->range_tombstone_iters_.empty());
|
||||
if (db_iter && !merge_iter->range_tombstone_iters_.empty()) {
|
||||
// memtable is always the first level
|
||||
db_iter->SetMemtableRangetombstoneIter(
|
||||
&merge_iter->range_tombstone_iters_.front());
|
||||
|
||||
+16
-10
@@ -38,6 +38,8 @@ extern InternalIterator* NewMergingIterator(
|
||||
class MergingIterator;
|
||||
|
||||
// A builder class to build a merging iterator by adding iterators one by one.
|
||||
// User should call only one of AddIterator() or AddPointAndTombstoneIterator()
|
||||
// exclusively for the same builder.
|
||||
class MergeIteratorBuilder {
|
||||
public:
|
||||
// comparator: the comparator used in merging comparator
|
||||
@@ -49,16 +51,20 @@ class MergeIteratorBuilder {
|
||||
// Add iter to the merging iterator.
|
||||
void AddIterator(InternalIterator* iter);
|
||||
|
||||
// Add a range tombstone iterator to underlying merge iterator.
|
||||
// See MergingIterator::AddRangeTombstoneIterator() for more detail.
|
||||
//
|
||||
// If `iter_ptr` is not nullptr, *iter_ptr will be set to where the merging
|
||||
// iterator stores `iter` when MergeIteratorBuilder::Finish() is called. This
|
||||
// is used by level iterator to update range tombstone iters when switching to
|
||||
// a different SST file.
|
||||
void AddRangeTombstoneIterator(
|
||||
TruncatedRangeDelIterator* iter,
|
||||
TruncatedRangeDelIterator*** iter_ptr = nullptr);
|
||||
// Add a point key iterator and a range tombstone iterator.
|
||||
// `tombstone_iter_ptr` should and only be set by LevelIterator.
|
||||
// *tombstone_iter_ptr will be set to where the merging iterator stores
|
||||
// `tombstone_iter` when MergeIteratorBuilder::Finish() is called. This is
|
||||
// used by LevelIterator to update range tombstone iters when switching to a
|
||||
// different SST file. If a single point iterator with a nullptr range
|
||||
// tombstone iterator is provided, and the point iterator is not a level
|
||||
// iterator, then this builder will return the point iterator directly,
|
||||
// instead of creating a merging iterator on top of it. Internally, if all
|
||||
// point iterators are not LevelIterator, then range tombstone iterator is
|
||||
// only added to the merging iter if there is a non-null `tombstone_iter`.
|
||||
void AddPointAndTombstoneIterator(
|
||||
InternalIterator* point_iter, TruncatedRangeDelIterator* tombstone_iter,
|
||||
TruncatedRangeDelIterator*** tombstone_iter_ptr = nullptr);
|
||||
|
||||
// Get arena used to build the merging iterator. It is called one a child
|
||||
// iterator needs to be allocated.
|
||||
|
||||
@@ -166,6 +166,65 @@ TEST_P(OptimisticTransactionTest, WriteConflictTest2) {
|
||||
delete txn;
|
||||
}
|
||||
|
||||
TEST_P(OptimisticTransactionTest, WriteConflictTest3) {
|
||||
ASSERT_OK(txn_db->Put(WriteOptions(), "foo", "bar"));
|
||||
|
||||
Transaction* txn = txn_db->BeginTransaction(WriteOptions());
|
||||
ASSERT_NE(txn, nullptr);
|
||||
|
||||
std::string value;
|
||||
ASSERT_OK(txn->GetForUpdate(ReadOptions(), "foo", &value));
|
||||
ASSERT_EQ(value, "bar");
|
||||
ASSERT_OK(txn->Merge("foo", "bar3"));
|
||||
|
||||
// Merge outside of a transaction should conflict with the previous merge
|
||||
ASSERT_OK(txn_db->Merge(WriteOptions(), "foo", "bar2"));
|
||||
ASSERT_OK(txn_db->Get(ReadOptions(), "foo", &value));
|
||||
ASSERT_EQ(value, "bar2");
|
||||
|
||||
ASSERT_EQ(1, txn->GetNumKeys());
|
||||
|
||||
Status s = txn->Commit();
|
||||
EXPECT_TRUE(s.IsBusy()); // Txn should not commit
|
||||
|
||||
// Verify that transaction did not write anything
|
||||
ASSERT_OK(txn_db->Get(ReadOptions(), "foo", &value));
|
||||
ASSERT_EQ(value, "bar2");
|
||||
|
||||
delete txn;
|
||||
}
|
||||
|
||||
TEST_P(OptimisticTransactionTest, WriteConflict4) {
|
||||
ASSERT_OK(txn_db->Put(WriteOptions(), "foo", "bar"));
|
||||
|
||||
Transaction* txn = txn_db->BeginTransaction(WriteOptions());
|
||||
ASSERT_NE(txn, nullptr);
|
||||
|
||||
std::string value;
|
||||
ASSERT_OK(txn->GetForUpdate(ReadOptions(), "foo", &value));
|
||||
ASSERT_EQ(value, "bar");
|
||||
ASSERT_OK(txn->Merge("foo", "bar3"));
|
||||
|
||||
// Range delete outside of a transaction should conflict with the previous
|
||||
// merge inside txn
|
||||
auto* dbimpl = static_cast_with_check<DBImpl>(txn_db->GetRootDB());
|
||||
ColumnFamilyHandle* default_cf = dbimpl->DefaultColumnFamily();
|
||||
ASSERT_OK(dbimpl->DeleteRange(WriteOptions(), default_cf, "foo", "foo1"));
|
||||
Status s = txn_db->Get(ReadOptions(), "foo", &value);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
|
||||
ASSERT_EQ(1, txn->GetNumKeys());
|
||||
|
||||
s = txn->Commit();
|
||||
EXPECT_TRUE(s.IsBusy()); // Txn should not commit
|
||||
|
||||
// Verify that transaction did not write anything
|
||||
s = txn_db->Get(ReadOptions(), "foo", &value);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
|
||||
delete txn;
|
||||
}
|
||||
|
||||
TEST_P(OptimisticTransactionTest, ReadConflictTest) {
|
||||
WriteOptions write_options;
|
||||
ReadOptions read_options, snapshot_read_options;
|
||||
|
||||
Reference in New Issue
Block a user