Compare commits

...

22 Commits

Author SHA1 Message Date
akankshamahajan bf2c335184 Update HISTORY.md and version to 7.8.3
Summary:

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:
2022-11-29 06:51:03 -08:00
Peter Dillinger 7aa573eb45 Update HISTORY for reverts 2022-11-28 09:37:05 -08:00
Peter Dillinger e0cf5cd5f3 Revert "Improve / refactor anonymous mmap capabilities (#10810)"
This reverts commit 8367f0d2d7.
2022-11-28 09:34:16 -08:00
Peter Dillinger 7ad900e1fb Revert "Fix include of windows.h in mmap.h (#10885)"
This reverts commit 49b7f219de.
2022-11-28 09:33:56 -08:00
Andrew Kryczka 491615ccaa update version.h for 7.8.2 2022-11-27 23:14:56 -08:00
Andrew Kryczka 1cf453943d batch latest fixes into 7.8.2 2022-11-27 23:12:09 -08:00
Andrew Kryczka 442b6b8048 Fix CompactionIterator flag for penultimate level output (#10967)
Summary:
We were not resetting it in non-debug mode so it could be true once and then stay true for future keys where it should be false. This PR adds the reset logic.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10967

Test Plan:
- built `db_bench` with DEBUG_LEVEL=0
- ran benchmark: `TEST_TMPDIR=/dev/shm/prefix ./db_bench -benchmarks=fillrandom -compaction_style=1 -preserve_internal_time_seconds=100 -preclude_last_level_data_seconds=10 -write_buffer_size=1048576 -target_file_size_base=1048576 -subcompactions=8 -duration=120`
- compared "output_to_penultimate_level: X bytes + last: Y bytes" lines in LOG output
  - Before this fix, Y was always zero
  - After this fix, Y gradually increased throughout the benchmark

Reviewed By: riversand963

Differential Revision: D41417726

Pulled By: ajkr

fbshipit-source-id: ace1e9a289e751a5b0c2fbaa8addd4eda5525329
2022-11-24 13:44:34 -08:00
Andrew Kryczka 8c06988313 Fix flush picking non-consecutive memtables (#10921)
Summary:
Prevents `MemTableList::PickMemtablesToFlush()` from picking non-consecutive memtables. It leads to wrong ordering in L0 if the files are committed, or an error like below if force_consistency_checks=true catches it:

```
Corruption: force_consistency_checks: VersionBuilder: L0 file https://github.com/facebook/rocksdb/issues/25 with seqno 320416 368066 vs. file https://github.com/facebook/rocksdb/issues/24 with seqno 336037 352068
```

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10921

Test Plan: fix the expectation in the existing test of this behavior

Reviewed By: riversand963

Differential Revision: D41046935

Pulled By: ajkr

fbshipit-source-id: 783696bff56115063d5dc5856dfaed6a9881d1ab
2022-11-24 13:43:36 -08:00
Changyu Bi 8d5edb693c Prevent iterating over range tombstones beyond iterate_upper_bound (#10966) (#10985)
Summary:
Currently, `iterate_upper_bound` is not checked for range tombstone keys in MergingIterator. This may impact performance when there is a large number of range tombstones right after `iterate_upper_bound`. This PR fixes this issue by checking `iterate_upper_bound` in MergingIterator for range tombstone keys.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10966

Test Plan:
- added unit test
- stress test: `python3 tools/db_crashtest.py whitebox --simple --verify_iterator_with_expected_state_one_in=5 --delrangepercent=5 --prefixpercent=18 --writepercent=48 --readpercen=15 --duration=36000 --range_deletion_width=100`
- ran different stress tests over sandcastle
- Falcon team ran some test traffic and saw reduced CPU usage on processing range tombstones.
2022-11-23 17:29:25 -08:00
Yanqin Jin 32d853f91c Make best-efforts recovery verify SST unique ID before Version construction (#10962)
Summary:
The check for SST unique IDs added to best-efforts recovery (`Options::best_efforts_recovery` is true).

With best_efforts_recovery being true, RocksDB will recover to the latest point in
MANIFEST such that all valid SST files included up to this point pass unique ID checks as well.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10962

Test Plan: make check

Reviewed By: pdillinger

Differential Revision: D41378241

Pulled By: riversand963

fbshipit-source-id: a036064e2c17dec13d080a24ef2a9f85d607b16c
2022-11-23 16:03:15 -08:00
anand76 e0b1793c2f Add some async read stats (#10947)
Summary:
Add stats for time spent in the ReadAsync call, and async read errors.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10947

Test Plan: Run db_bench and look at stats

Reviewed By: akankshamahajan15

Differential Revision: D41236637

Pulled By: anand1976

fbshipit-source-id: 70539b69a28491d57acead449436a761f7108acf
2022-11-21 10:37:00 -08:00
Akanksha Mahajan f690f2449a Fix db_stress failure in async_io in FilePrefetchBuffer (#10949)
Summary:
Fix db_stress failure in async_io in FilePrefetchBuffer.

From the logs, assertion was caused when
- prev_offset_ = offset but somehow prev_len != 0 and explicit_prefetch_submitted_ = true. That scenario is when we send async request to prefetch buffer during seek but in second seek that data is found in cache. prev_offset_ and prev_len_ get updated but we were not setting explicit_prefetch_submitted_ = false because of which buffers were getting out of sync.
It's possible a read by another thread might have loaded the block into the cache in the meantime.

Particular assertion example:
```
prev_offset: 0, prev_len_: 8097 , offset: 0, length: 8097, actual_length: 8097 , actual_offset: 0 ,
curr_: 0, bufs_[curr_].offset_: 4096 ,bufs_[curr_].CurrentSize(): 48541 , async_len_to_read: 278528, bufs_[curr_].async_in_progress_: false
second: 1, bufs_[second].offset_: 282624 ,bufs_[second].CurrentSize(): 0, async_len_to_read: 262144 ,bufs_[second].async_in_progress_: true ,
explicit_prefetch_submitted_: true , copy_to_third_buffer: false
```
As we can see curr_ was expected to read 278528 but it read 48541. Also buffers are out of sync.
Also `explicit_prefetch_submitted_` is set true but prev_len not 0.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10949

Test Plan:
- Ran db_bench for regression to make sure there is no regression;
- Ran db_stress failing without this fix,
- Ran build-linux-mini-crashtest 7- 8 times locally + CircleCI

Reviewed By: anand1976

Differential Revision: D41257786

Pulled By: akankshamahajan15

fbshipit-source-id: 1d100f94f8c06bbbe4cc76ca27f1bbc820c2494f
2022-11-21 10:36:43 -08:00
akankshamahajan c097b01467 Fix async_io regression in scans (#10939)
Summary:
Fix async_io regression in scans due to incorrect check which was causing the valid data in buffer to be cleared during seek.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10939

Test Plan:
- stress tests  export CRASH_TEST_EXT_ARGS="--async_io=1"
    make crash_test -j32
- Ran db_bench command which was caught the regression:
./db_bench --db=/rocksdb_async_io_testing/prefix_scan --disable_wal=1 --use_existing_db=true --benchmarks="seekrandom" -key_size=32 -value_size=512 -num=50000000 -use_direct_reads=false -seek_nexts=963 -duration=30 -ops_between_duration_checks=1 --async_io=true --compaction_readahead_size=4194304 --log_readahead_size=0 --blob_compaction_readahead_size=0 --initial_auto_readahead_size=65536 --num_file_reads_for_auto_readahead=0 --max_auto_readahead_size=524288

seekrandom   :    3777.415 micros/op 264 ops/sec 30.000 seconds 7942 operations;  132.3 MB/s (7942 of 7942 found)

Reviewed By: anand1976

Differential Revision: D41173899

Pulled By: akankshamahajan15

fbshipit-source-id: 2d75b06457d65b1851c92382565d9c3fac329dfe
2022-11-21 10:36:09 -08:00
akankshamahajan 6b2f41f223 Update HISTORY.md and version.h for 7.8.1
Summary:

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:
2022-11-02 09:56:28 -07:00
akankshamahajan d53da91ec2 Fix async_io failures in case there is error in reading data (#10890)
Summary:
Fix memory corruption error in scans if async_io is enabled. Memory corruption happened if data is overlapping between two buffers. If there is IOError while reading the data, it leads to empty buffer and other buffer already in progress of async read goes again for reading causing the error.
Fix: Added check to abort IO in second buffer if curr_ got empty.

This PR also fixes db_stress failures which happened when buffers are not aligned.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10890

Test Plan:
- Ran make crash_test -j32 with async_io enabled.
-  Ran benchmarks to make sure there is no regression.

Reviewed By: anand1976

Differential Revision: D40881731

Pulled By: akankshamahajan15

fbshipit-source-id: 39fcf2134c7b1bbb08415ede3e1ef261ac2dbc58
2022-11-01 16:26:20 -07:00
Changyu Bi 0ffd94d615 Reduce heap operations for range tombstone keys in iterator (#10877)
Summary:
Right now in MergingIterator, for each range tombstone start and end key, we pop one end from heap and push the other end into the heap. This involves extra downheap and upheap cost. In the likely cases when a range tombstone iterator emits relatively adjacent keys, these keys should have similar order within all keys in the heap. This can happen when there is a burst of consecutive range tombstones, and most of the keys covered by them are dropped already. This PR uses `replace_top()` when inserting new range tombstone keys, which is more efficient in these common cases.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10877

Test Plan:
- existing UT
- ran all flavors of stress test through sandcastle
- benchmark:
```

TEST_TMPDIR=/tmp/rocksdb-rangedel-test-all-tombstone ./db_bench --benchmarks=fillseq,levelstats --writes_per_range_tombstone=1 --max_num_range_tombstones=1000000 --range_tombstone_width=2 --num=100000000 --writes=800000 --max_bytes_for_level_base=4194304 --disable_auto_compactions --write_buffer_size=33554432 --key_size=64

Level Files Size(MB)
--------------------
  0        8      152
  1        0        0
  2        0        0
  3        0        0
  4        0        0
  5        0        0
  6        0        0

TEST_TMPDIR=/tmp/rocksdb-rangedel-test-all-tombstone/ ./db_bench --benchmarks=readseq[-W1][-X5],levelstats --use_existing_db=true --cache_size=3221225472 --num=100000000 --reads=1000000 --disable_auto_compactions=true --avoid_flush_during_recovery=true

readseq [AVG    5 runs] : 1432116 (± 59664) ops/sec;  224.0 (± 9.3) MB/sec
readseq [MEDIAN 5 runs] : 1454886 ops/sec;  227.5 MB/sec

readseq [AVG    5 runs] : 1944425 (± 29521) ops/sec;  304.1 (± 4.6) MB/sec
readseq [MEDIAN 5 runs] : 1959430 ops/sec;  306.5 MB/sec
```

Reviewed By: ajkr

Differential Revision: D40710936

Pulled By: cbi42

fbshipit-source-id: cb782fb9cdcd26c0c3eb9443215a4ef4d2f79022
2022-10-31 09:41:30 -07:00
Levi Tamasi fb7b420443 Use malloc/free for LRUHandle instead of new[]/delete[] (#10884)
Summary:
It's unsafe to call `malloc_usable_size` with an address not returned by a function from the `malloc` family (see https://github.com/facebook/rocksdb/issues/10798). The patch switches from using `new[]` / `delete[]` for `LRUHandle` to `malloc` / `free`.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10884

Test Plan: `make check`

Reviewed By: pdillinger

Differential Revision: D40738089

Pulled By: ltamasi

fbshipit-source-id: ac5583f88125fee49c314639be6b6df85937fbee
2022-10-28 15:35:11 -07:00
anand76 3453a88b57 Fix a potential std::vector use after move bug (#10845)
Summary:
The call to `folly::coro::collectAllRange()` should move the input `mget_tasks`. But just in case, assert and clear the std::vector before reusing.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10845

Reviewed By: akankshamahajan15

Differential Revision: D40611719

Pulled By: anand1976

fbshipit-source-id: 0f32b387cf5a2894b13389016c020b01ab479b5e
2022-10-26 22:41:35 -07:00
Peter Dillinger 49b7f219de Fix include of windows.h in mmap.h (#10885)
Summary:
If windows.h is not included in a particular way, it can conflict with other code including it. I don't know all the details, but having just one standard place where we include windows.h in header files seems best and seems to fix the internal issue we hit.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10885

Test Plan: CI and internal validation

Reviewed By: anand1976

Differential Revision: D40738945

Pulled By: pdillinger

fbshipit-source-id: 88f635e895b1c7b810baad159e6dbb8351344cac
2022-10-26 19:34:46 -07:00
akankshamahajan 0d82c62664 Fix override error in system_clock.h (#10858)
Summary:
Fix error
```
 rocksdb/system_clock.h:30:11: error: '~SystemClock' overrides a destructor but is not marked 'override' [-Werror,-Wsuggest-destructor-override]
virtual ~SystemClock() {}
```

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10858

Test Plan: Ran internally

Reviewed By: siying

Differential Revision: D40652374

Pulled By: akankshamahajan15

fbshipit-source-id: 5dda8ca03ea57d709442c87e23e5fe097d7db672
2022-10-24 17:38:41 -07:00
akankshamahajan 3ecef275be Update header file to include right copyright (#10854)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/10854

Reviewed By: siying

Differential Revision: D40651483

Pulled By: akankshamahajan15

fbshipit-source-id: 95ce53297e9699a34cc80439bc7553f6cc3ac957
2022-10-24 17:38:19 -07:00
Changyu Bi 9e0c4a0aa7 Remove range tombstone test code from sst_file_reader (#10847)
Summary:
`#include "db/range_tombstone_fragmenter.h"` seems to break some internal test for 7.8 release. I'm removing it from sst_file_reader.h for now to unblock release. This should be fine as it is only used in a unit test for DeleteRange with timestamp. In addition, it does not seem to be useful to support delete range for sst file writer, since the range tombstone won't cover any key (its sequence number is 0). So maybe we can remove it in the future.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10847

Test Plan: CI.

Reviewed By: akankshamahajan15

Differential Revision: D40620865

Pulled By: cbi42

fbshipit-source-id: be44b2f31e062bff87ed1b8d94482c3f7eaa370c
2022-10-24 09:17:32 -07:00
39 changed files with 672 additions and 478 deletions
+1 -1
View File
@@ -584,7 +584,7 @@ jobs:
name: "Test RocksDB"
shell: powershell.exe
command: |
build_tools\run_ci_db_test.ps1 -SuiteRun arena_test,db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test -Concurrency 16
build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test -Concurrency 16
build-linux-java:
executor: linux-docker
-1
View File
@@ -796,7 +796,6 @@ set(SOURCES
options/options.cc
options/options_helper.cc
options/options_parser.cc
port/mmap.cc
port/stack_trace.cc
table/adaptive/adaptive_table_factory.cc
table/block_based/binary_search_index_reader.cc
+18
View File
@@ -1,4 +1,21 @@
# Rocksdb Change Log
## 7.8.3 (11/29/2022)
* Revert an internal change in 7.8.0 associated with some memory usage churn.
## 7.8.2 (11/27/2022)
### Behavior changes
* Make best-efforts recovery verify SST unique ID before Version construction (#10962)
* Fix failed memtable flush retry bug that could cause wrongly ordered updates, which would surface to writers as `Status::Corruption` in case of `force_consistency_checks=true` (default). It affects use cases that enable both parallel flush (`max_background_flushes > 1` or `max_background_jobs >= 8`) and non-default memtable count (`max_write_buffer_number > 2`).
* Tiered Storage: fixed excessive keys written to penultimate level in non-debug builds.
### Bug Fixes
* Fixed a regression in scan for async_io. During seek, valid buffers were getting cleared causing a regression.
* Fixed a performance regression in iterator where range tombstones after `iterate_upper_bound` is processed.
## 7.8.1 (11/2/2022)
### Bug Fixes
* Fix memory corruption error in scans if async_io is enabled. Memory corruption happened if there is IOError while reading the data leading to empty buffer and other buffer already in progress of async read goes again for reading.
## 7.8.0 (10/22/2022)
### New Features
* `DeleteRange()` now supports user-defined timestamp.
@@ -28,6 +45,7 @@
* Try to align the compaction output file boundaries to the next level ones, which can reduce more than 10% compaction load for the default level compaction. The feature is enabled by default, to disable, set `AdvancedColumnFamilyOptions.level_compaction_dynamic_file_size` to false. As a side effect, it can create SSTs larger than the target_file_size (capped at 2x target_file_size) or smaller files.
* Improve RoundRobin TTL compaction, which is going to be the same as normal RoundRobin compaction to move the compaction cursor.
* Fix a small CPU regression caused by a change that UserComparatorWrapper was made Customizable, because Customizable itself has small CPU overhead for initialization.
* Fixed an iterator performance regression for delete range users when scanning through a consecutive sequence of range tombstones (#10877).
### 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).
-2
View File
@@ -163,7 +163,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"options/options.cc",
"options/options_helper.cc",
"options/options_parser.cc",
"port/mmap.cc",
"port/port_posix.cc",
"port/stack_trace.cc",
"port/win/env_default.cc",
@@ -503,7 +502,6 @@ cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[
"options/options.cc",
"options/options_helper.cc",
"options/options_parser.cc",
"port/mmap.cc",
"port/port_posix.cc",
"port/stack_trace.cc",
"port/win/env_default.cc",
+4 -5
View File
@@ -386,7 +386,7 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, LRUHandle** handle,
last_reference_list.push_back(e);
} else {
if (free_handle_on_fail) {
delete[] reinterpret_cast<char*>(e);
free(e);
*handle = nullptr;
}
s = Status::MemoryLimit("Insert failed due to LRU cache being full.");
@@ -559,8 +559,7 @@ LRUHandle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash,
secondary_cache_->Lookup(key, create_cb, wait, found_dummy_entry,
is_in_sec_cache);
if (secondary_handle != nullptr) {
e = reinterpret_cast<LRUHandle*>(
new char[sizeof(LRUHandle) - 1 + key.size()]);
e = static_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
e->m_flags = 0;
e->im_flags = 0;
@@ -683,8 +682,8 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
// Allocate the memory here outside of the mutex.
// If the cache is full, we'll have to release it.
// It shouldn't happen very often though.
LRUHandle* e = reinterpret_cast<LRUHandle*>(
new char[sizeof(LRUHandle) - 1 + key.size()]);
LRUHandle* e =
static_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
e->value = value;
e->m_flags = 0;
+3 -1
View File
@@ -212,6 +212,7 @@ struct LRUHandle {
void Free() {
assert(refs == 0);
if (!IsSecondaryCacheCompatible() && info_.deleter) {
(*info_.deleter)(key(), value);
} else if (IsSecondaryCacheCompatible()) {
@@ -226,7 +227,8 @@ struct LRUHandle {
(*info_.helper->del_cb)(key(), value);
}
}
delete[] reinterpret_cast<char*>(this);
free(this);
}
inline size_t CalcuMetaCharge(
+3 -1
View File
@@ -1100,7 +1100,9 @@ void CompactionIterator::DecideOutputLevel() {
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context",
&context);
output_to_penultimate_level_ = context.output_to_penultimate_level;
#endif /* !NDEBUG */
#else
output_to_penultimate_level_ = false;
#endif // NDEBUG
// if the key is newer than the cutoff sequence or within the earliest
// snapshot, it should output to the penultimate level.
+2 -1
View File
@@ -1819,7 +1819,8 @@ InternalIterator* DBImpl::NewInternalIterator(
MergeIteratorBuilder merge_iter_builder(
&cfd->internal_comparator(), arena,
!read_options.total_order_seek &&
super_version->mutable_cf_options.prefix_extractor != nullptr);
super_version->mutable_cf_options.prefix_extractor != nullptr,
read_options.iterate_upper_bound);
// Collect iterator for mutable memtable
auto mem_iter = super_version->mem->NewIterator(read_options, arena);
Status s;
+40
View File
@@ -2754,6 +2754,46 @@ TEST_F(DBRangeDelTest, RefreshMemtableIter) {
ASSERT_OK(iter->Refresh());
}
TEST_F(DBRangeDelTest, RangeTombstoneRespectIterateUpperBound) {
// Memtable: a, [b, bz)
// Do a Seek on `a` with iterate_upper_bound being az
// range tombstone [b, bz) should not be processed (added to and
// popped from the min_heap in MergingIterator).
Options options = CurrentOptions();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
ASSERT_OK(Put("a", "bar"));
ASSERT_OK(
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "b", "bz"));
// I could not find a cleaner way to test this without relying on
// implementation detail. Tried to test the value of
// `internal_range_del_reseek_count` but that did not work
// since BlockBasedTable iterator becomes !Valid() when point key
// is out of bound and that reseek only happens when a point key
// is covered by some range tombstone.
SyncPoint::GetInstance()->SetCallBack("MergeIterator::PopDeleteRangeStart",
[](void*) {
// there should not be any range
// tombstone in the heap.
FAIL();
});
SyncPoint::GetInstance()->EnableProcessing();
ReadOptions read_opts;
std::string upper_bound = "az";
Slice upper_bound_slice = upper_bound;
read_opts.iterate_upper_bound = &upper_bound_slice;
std::unique_ptr<Iterator> iter{db_->NewIterator(read_opts)};
iter->Seek("a");
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->key(), "a");
iter->Next();
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
}
#endif // ROCKSDB_LITE
} // namespace ROCKSDB_NAMESPACE
+74
View File
@@ -7506,6 +7506,80 @@ TEST_F(DBTest2, SstUniqueIdVerifyMultiCFs) {
ASSERT_TRUE(s.IsCorruption());
}
TEST_F(DBTest2, BestEffortsRecoveryWithSstUniqueIdVerification) {
const auto tamper_with_uniq_id = [&](void* arg) {
auto props = static_cast<TableProperties*>(arg);
assert(props);
// update table property session_id to a different one
props->db_session_id = DBImpl::GenerateDbSessionId(nullptr);
};
const auto assert_db = [&](size_t expected_count,
const std::string& expected_v) {
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
size_t cnt = 0;
for (it->SeekToFirst(); it->Valid(); it->Next(), ++cnt) {
ASSERT_EQ(std::to_string(cnt), it->key());
ASSERT_EQ(expected_v, it->value());
}
ASSERT_EQ(expected_count, cnt);
};
const int num_l0_compaction_trigger = 8;
const int num_l0 = num_l0_compaction_trigger - 1;
Options options = CurrentOptions();
options.level0_file_num_compaction_trigger = num_l0_compaction_trigger;
for (int k = 0; k < num_l0; ++k) {
// Allow mismatch for now
options.verify_sst_unique_id_in_manifest = false;
DestroyAndReopen(options);
constexpr size_t num_keys_per_file = 10;
for (int i = 0; i < num_l0; ++i) {
for (size_t j = 0; j < num_keys_per_file; ++j) {
ASSERT_OK(Put(std::to_string(j), "v" + std::to_string(i)));
}
if (i == k) {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->SetCallBack(
"PropertyBlockBuilder::AddTableProperty:Start",
tamper_with_uniq_id);
SyncPoint::GetInstance()->EnableProcessing();
}
ASSERT_OK(Flush());
}
options.verify_sst_unique_id_in_manifest = true;
Status s = TryReopen(options);
ASSERT_TRUE(s.IsCorruption());
options.best_efforts_recovery = true;
Reopen(options);
assert_db(k == 0 ? 0 : num_keys_per_file, "v" + std::to_string(k - 1));
// Reopen with regular recovery
options.best_efforts_recovery = false;
Reopen(options);
assert_db(k == 0 ? 0 : num_keys_per_file, "v" + std::to_string(k - 1));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
for (size_t i = 0; i < num_keys_per_file; ++i) {
ASSERT_OK(Put(std::to_string(i), "v"));
}
ASSERT_OK(Flush());
Reopen(options);
{
for (size_t i = 0; i < num_keys_per_file; ++i) {
ASSERT_EQ("v", Get(std::to_string(i)));
}
}
}
}
#ifndef ROCKSDB_LITE
TEST_F(DBTest2, GetLatestSeqAndTsForKey) {
Destroy(last_options_);
-3
View File
@@ -49,9 +49,6 @@
#include "util/string_util.h"
#include "utilities/merge_operators.h"
// In case defined by Windows headers
#undef small
namespace ROCKSDB_NAMESPACE {
class MockEnv;
+1 -1
View File
@@ -76,7 +76,7 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
: comparator_(cmp),
moptions_(ioptions, mutable_cf_options),
refs_(0),
kArenaBlockSize(Arena::OptimizeBlockSize(moptions_.arena_block_size)),
kArenaBlockSize(OptimizeBlockSize(moptions_.arena_block_size)),
mem_tracker_(write_buffer_manager),
arena_(moptions_.arena_block_size,
(write_buffer_manager != nullptr &&
+7
View File
@@ -419,6 +419,13 @@ void MemTableList::PickMemtablesToFlush(uint64_t max_memtable_id,
std::max(m->GetNextLogNumber(), *max_next_log_number);
}
ret->push_back(m);
} else if (!ret->empty()) {
// This `break` is necessary to prevent picking non-consecutive memtables
// in case `memlist` has one or more entries with
// `flush_in_progress_ == true` sandwiched between entries with
// `flush_in_progress_ == false`. This could happen after parallel flushes
// are picked and the one flushing older memtables is rolled back.
break;
}
}
if (!atomic_flush || num_flush_not_started_ == 0) {
+35 -13
View File
@@ -742,28 +742,40 @@ TEST_F(MemTableListTest, FlushPendingTest) {
// Pick tables to flush
list.PickMemtablesToFlush(
std::numeric_limits<uint64_t>::max() /* memtable_id */, &to_flush);
// Should pick 4 of 5 since 1 table has been picked in to_flush2
ASSERT_EQ(4, to_flush.size());
// Picks three oldest memtables. The fourth oldest is picked in `to_flush2` so
// must be excluded. The newest (fifth oldest) is non-consecutive with the
// three oldest due to omitting the fourth oldest so must not be picked.
ASSERT_EQ(3, to_flush.size());
ASSERT_EQ(5, list.NumNotFlushed());
ASSERT_FALSE(list.IsFlushPending());
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire));
// Pick tables to flush again
autovector<MemTable*> to_flush3;
list.PickMemtablesToFlush(
std::numeric_limits<uint64_t>::max() /* memtable_id */, &to_flush3);
ASSERT_EQ(0, to_flush3.size()); // nothing not in progress of being flushed
// Picks newest (fifth oldest)
ASSERT_EQ(1, to_flush3.size());
ASSERT_EQ(5, list.NumNotFlushed());
ASSERT_FALSE(list.IsFlushPending());
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
// Flush the 4 memtables that were picked in to_flush
// Nothing left to flush
autovector<MemTable*> to_flush4;
list.PickMemtablesToFlush(
std::numeric_limits<uint64_t>::max() /* memtable_id */, &to_flush4);
ASSERT_EQ(0, to_flush4.size());
ASSERT_EQ(5, list.NumNotFlushed());
ASSERT_FALSE(list.IsFlushPending());
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
// Flush the 3 memtables that were picked in to_flush
s = Mock_InstallMemtableFlushResults(&list, mutable_cf_options, to_flush,
&to_delete);
ASSERT_OK(s);
// Note: now to_flush contains tables[0,1,2,4]. to_flush2 contains
// tables[3].
// Note: now to_flush contains tables[0,1,2]. to_flush2 contains
// tables[3]. to_flush3 contains tables[4].
// Current implementation will only commit memtables in the order they were
// created. So TryInstallMemtableFlushResults will install the first 3 tables
// in to_flush and stop when it encounters a table not yet flushed.
@@ -779,7 +791,17 @@ TEST_F(MemTableListTest, FlushPendingTest) {
ASSERT_FALSE(list.IsFlushPending());
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
// Flush the 1 memtable that was picked in to_flush2
// Flush the 1 memtable (tables[4]) that was picked in to_flush3
s = MemTableListTest::Mock_InstallMemtableFlushResults(
&list, mutable_cf_options, to_flush3, &to_delete);
ASSERT_OK(s);
// This will install 0 tables since tables[4] flushed while tables[3] has not
// yet flushed.
ASSERT_EQ(2, list.NumNotFlushed());
ASSERT_EQ(0, to_delete.size());
// Flush the 1 memtable (tables[3]) that was picked in to_flush2
s = MemTableListTest::Mock_InstallMemtableFlushResults(
&list, mutable_cf_options, to_flush2, &to_delete);
ASSERT_OK(s);
@@ -809,11 +831,11 @@ TEST_F(MemTableListTest, FlushPendingTest) {
memtable_id = 4;
// Pick tables to flush. The tables to pick must have ID smaller than or
// equal to 4. Therefore, no table will be selected in this case.
autovector<MemTable*> to_flush4;
autovector<MemTable*> to_flush5;
list.FlushRequested();
ASSERT_TRUE(list.HasFlushRequested());
list.PickMemtablesToFlush(memtable_id, &to_flush4);
ASSERT_TRUE(to_flush4.empty());
list.PickMemtablesToFlush(memtable_id, &to_flush5);
ASSERT_TRUE(to_flush5.empty());
ASSERT_EQ(1, list.NumNotFlushed());
ASSERT_TRUE(list.imm_flush_needed.load(std::memory_order_acquire));
ASSERT_FALSE(list.IsFlushPending());
@@ -823,8 +845,8 @@ TEST_F(MemTableListTest, FlushPendingTest) {
// equal to 5. Therefore, only tables[5] will be selected.
memtable_id = 5;
list.FlushRequested();
list.PickMemtablesToFlush(memtable_id, &to_flush4);
ASSERT_EQ(1, static_cast<int>(to_flush4.size()));
list.PickMemtablesToFlush(memtable_id, &to_flush5);
ASSERT_EQ(1, static_cast<int>(to_flush5.size()));
ASSERT_EQ(1, list.NumNotFlushed());
ASSERT_FALSE(list.imm_flush_needed.load(std::memory_order_acquire));
ASSERT_FALSE(list.IsFlushPending());
+28 -5
View File
@@ -734,12 +734,13 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
assert(!cfd->ioptions()->cf_paths.empty());
Status s;
for (const auto& elem : edit.GetNewFiles()) {
int level = elem.first;
const FileMetaData& meta = elem.second;
const FileDescriptor& fd = meta.fd;
uint64_t file_num = fd.GetNumber();
const std::string fpath =
MakeTableFileName(cfd->ioptions()->cf_paths[0].path, file_num);
s = VerifyFile(fpath, meta);
s = VerifyFile(cfd, fpath, level, meta);
if (s.IsPathNotFound() || s.IsNotFound() || s.IsCorruption()) {
missing_files.insert(file_num);
s = Status::OK();
@@ -804,6 +805,18 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
auto* version = new Version(cfd, version_set_, version_set_->file_options_,
*cfd->GetLatestMutableCFOptions(), io_tracer_,
version_set_->current_version_number_++);
s = builder->LoadTableHandlers(
cfd->internal_stats(),
version_set_->db_options_->max_file_opening_threads, false, true,
cfd->GetLatestMutableCFOptions()->prefix_extractor,
MaxFileSizeForL0MetaPin(*cfd->GetLatestMutableCFOptions()));
if (!s.ok()) {
delete version;
if (s.IsCorruption()) {
s = Status::OK();
}
return s;
}
s = builder->SaveTo(version->storage_info());
if (s.ok()) {
version->PrepareAppend(
@@ -823,9 +836,11 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
return s;
}
Status VersionEditHandlerPointInTime::VerifyFile(const std::string& fpath,
Status VersionEditHandlerPointInTime::VerifyFile(ColumnFamilyData* cfd,
const std::string& fpath,
int level,
const FileMetaData& fmeta) {
return version_set_->VerifyFileMetadata(fpath, fmeta);
return version_set_->VerifyFileMetadata(cfd, fpath, level, fmeta);
}
Status VersionEditHandlerPointInTime::VerifyBlobFile(
@@ -843,6 +858,12 @@ Status VersionEditHandlerPointInTime::VerifyBlobFile(
return s;
}
Status VersionEditHandlerPointInTime::LoadTables(
ColumnFamilyData* /*cfd*/, bool /*prefetch_index_and_filter_in_cache*/,
bool /*is_initial_load*/) {
return Status::OK();
}
Status ManifestTailer::Initialize() {
if (Mode::kRecovery == mode_) {
return VersionEditHandler::Initialize();
@@ -930,9 +951,11 @@ void ManifestTailer::CheckIterationResult(const log::Reader& reader,
}
}
Status ManifestTailer::VerifyFile(const std::string& fpath,
Status ManifestTailer::VerifyFile(ColumnFamilyData* cfd,
const std::string& fpath, int level,
const FileMetaData& fmeta) {
Status s = VersionEditHandlerPointInTime::VerifyFile(fpath, fmeta);
Status s =
VersionEditHandlerPointInTime::VerifyFile(cfd, fpath, level, fmeta);
// TODO: Open file or create hard link to prevent the file from being
// deleted.
return s;
+10 -6
View File
@@ -164,9 +164,9 @@ class VersionEditHandler : public VersionEditHandlerBase {
ColumnFamilyData* cfd,
bool force_create_version);
Status LoadTables(ColumnFamilyData* cfd,
bool prefetch_index_and_filter_in_cache,
bool is_initial_load);
virtual Status LoadTables(ColumnFamilyData* cfd,
bool prefetch_index_and_filter_in_cache,
bool is_initial_load);
virtual bool MustOpenAllColumnFamilies() const { return !read_only_; }
@@ -213,11 +213,15 @@ class VersionEditHandlerPointInTime : public VersionEditHandler {
ColumnFamilyData* DestroyCfAndCleanup(const VersionEdit& edit) override;
Status MaybeCreateVersion(const VersionEdit& edit, ColumnFamilyData* cfd,
bool force_create_version) override;
virtual Status VerifyFile(const std::string& fpath,
const FileMetaData& fmeta);
virtual Status VerifyFile(ColumnFamilyData* cfd, const std::string& fpath,
int level, const FileMetaData& fmeta);
virtual Status VerifyBlobFile(ColumnFamilyData* cfd, uint64_t blob_file_num,
const BlobFileAddition& blob_addition);
Status LoadTables(ColumnFamilyData* cfd,
bool prefetch_index_and_filter_in_cache,
bool is_initial_load) override;
std::unordered_map<uint32_t, Version*> versions_;
};
@@ -250,7 +254,7 @@ class ManifestTailer : public VersionEditHandlerPointInTime {
void CheckIterationResult(const log::Reader& reader, Status* s) override;
Status VerifyFile(const std::string& fpath,
Status VerifyFile(ColumnFamilyData* cfd, const std::string& fpath, int level,
const FileMetaData& fmeta) override;
enum Mode : uint8_t {
+36 -2
View File
@@ -2833,6 +2833,7 @@ Status Version::MultiGetAsync(
std::vector<Status> statuses = folly::coro::blockingWait(
folly::coro::collectAllRange(std::move(mget_tasks))
.scheduleOn(&range->context()->executor()));
mget_tasks.clear();
for (Status stat : statuses) {
if (!stat.ok()) {
s = stat;
@@ -6723,8 +6724,9 @@ uint64_t VersionSet::GetTotalBlobFileSize(Version* dummy_versions) {
return all_versions_blob_file_size;
}
Status VersionSet::VerifyFileMetadata(const std::string& fpath,
const FileMetaData& meta) const {
Status VersionSet::VerifyFileMetadata(ColumnFamilyData* cfd,
const std::string& fpath, int level,
const FileMetaData& meta) {
uint64_t fsize = 0;
Status status = fs_->GetFileSize(fpath, IOOptions(), &fsize, nullptr);
if (status.ok()) {
@@ -6732,6 +6734,38 @@ Status VersionSet::VerifyFileMetadata(const std::string& fpath,
status = Status::Corruption("File size mismatch: " + fpath);
}
}
if (status.ok() && db_options_->verify_sst_unique_id_in_manifest) {
assert(cfd);
TableCache* table_cache = cfd->table_cache();
assert(table_cache);
const MutableCFOptions* const cf_opts = cfd->GetLatestMutableCFOptions();
assert(cf_opts);
std::shared_ptr<const SliceTransform> pe = cf_opts->prefix_extractor;
size_t max_sz_for_l0_meta_pin = MaxFileSizeForL0MetaPin(*cf_opts);
const FileOptions& file_opts = file_options();
Version* version = cfd->current();
assert(version);
VersionStorageInfo& storage_info = version->storage_info_;
const InternalKeyComparator* icmp = storage_info.InternalComparator();
assert(icmp);
InternalStats* internal_stats = cfd->internal_stats();
FileMetaData meta_copy = meta;
status = table_cache->FindTable(
ReadOptions(), file_opts, *icmp, meta_copy,
&(meta_copy.table_reader_handle), pe,
/*no_io=*/false, /*record_read_stats=*/true,
internal_stats->GetFileReadHist(level), false, level,
/*prefetch_index_and_filter_in_cache*/ false, max_sz_for_l0_meta_pin,
meta_copy.temperature);
if (meta_copy.table_reader_handle) {
table_cache->ReleaseHandle(meta_copy.table_reader_handle);
}
}
return status;
}
+2 -2
View File
@@ -1503,8 +1503,8 @@ class VersionSet {
ColumnFamilyData* CreateColumnFamily(const ColumnFamilyOptions& cf_options,
const VersionEdit* edit);
Status VerifyFileMetadata(const std::string& fpath,
const FileMetaData& meta) const;
Status VerifyFileMetadata(ColumnFamilyData* cfd, const std::string& fpath,
int level, const FileMetaData& meta);
// Protected by DB mutex.
WalSet wals_;
+134 -65
View File
@@ -225,10 +225,8 @@ void FilePrefetchBuffer::AbortIOIfNeeded(uint64_t offset) {
bufs_[second].async_read_in_progress_ = false;
}
if (bufs_[curr_].io_handle_ == nullptr &&
bufs_[curr_].async_read_in_progress_) {
if (bufs_[curr_].io_handle_ == nullptr) {
bufs_[curr_].async_read_in_progress_ = false;
curr_ = curr_ ^ 1;
}
}
@@ -268,16 +266,36 @@ void FilePrefetchBuffer::UpdateBuffersIfNeeded(uint64_t offset) {
bufs_[second].buffer_.Clear();
}
// If data starts from second buffer, make it curr_. Second buffer can be
// either partial filled or full.
if (!bufs_[second].async_read_in_progress_ && DoesBufferContainData(second) &&
IsOffsetInBuffer(offset, second)) {
// Clear the curr_ as buffers have been swapped and curr_ contains the
// outdated data and switch the buffers.
if (!bufs_[curr_].async_read_in_progress_) {
bufs_[curr_].buffer_.Clear();
{
// In case buffers do not align, reset second buffer. This can happen in
// case readahead_size is set.
if (!bufs_[second].async_read_in_progress_ &&
!bufs_[curr_].async_read_in_progress_) {
if (DoesBufferContainData(curr_)) {
if (bufs_[curr_].offset_ + bufs_[curr_].buffer_.CurrentSize() !=
bufs_[second].offset_) {
bufs_[second].buffer_.Clear();
}
} else {
if (!IsOffsetInBuffer(offset, second)) {
bufs_[second].buffer_.Clear();
}
}
}
}
// If data starts from second buffer, make it curr_. Second buffer can be
// either partial filled, full or async read is in progress.
if (bufs_[second].async_read_in_progress_) {
if (IsOffsetInBufferWithAsyncProgress(offset, second)) {
curr_ = curr_ ^ 1;
}
} else {
if (DoesBufferContainData(second) && IsOffsetInBuffer(offset, second)) {
assert(bufs_[curr_].async_read_in_progress_ ||
bufs_[curr_].buffer_.CurrentSize() == 0);
curr_ = curr_ ^ 1;
}
curr_ = curr_ ^ 1;
}
}
@@ -300,53 +318,16 @@ void FilePrefetchBuffer::PollAndUpdateBuffersIfNeeded(uint64_t offset) {
UpdateBuffersIfNeeded(offset);
}
// If async_io is enabled in case of sequential reads, PrefetchAsyncInternal is
// called. When buffers are switched, we clear the curr_ buffer as we assume the
// data has been consumed because of sequential reads.
// Data in buffers will always be sequential with curr_ following second and
// not vice versa.
//
// Scenarios for prefetching asynchronously:
// Case1: If both buffers are empty, prefetch n + readahead_size_/2 bytes
// synchronously in curr_ and prefetch readahead_size_/2 async in second
// buffer.
// Case2: If second buffer has partial or full data, make it current and
// prefetch readahead_size_/2 async in second buffer. In case of
// partial data, prefetch remaining bytes from size n synchronously to
// fulfill the requested bytes request.
// Case3: If curr_ has partial data, prefetch remaining bytes from size n
// synchronously in curr_ to fulfill the requested bytes request and
// prefetch readahead_size_/2 bytes async in second buffer.
// Case4: (Special case) If data is in both buffers, copy requested data from
// curr_, send async request on curr_, wait for poll to fill second
// buffer (if any), and copy remaining data from second buffer to third
// buffer.
Status FilePrefetchBuffer::PrefetchAsyncInternal(
Status FilePrefetchBuffer::HandleOverlappingData(
const IOOptions& opts, RandomAccessFileReader* reader, uint64_t offset,
size_t length, size_t readahead_size, Env::IOPriority rate_limiter_priority,
bool& copy_to_third_buffer) {
if (!enable_) {
return Status::OK();
}
TEST_SYNC_POINT("FilePrefetchBuffer::PrefetchAsyncInternal:Start");
size_t alignment = reader->file()->GetRequiredBufferAlignment();
size_t length, size_t readahead_size,
Env::IOPriority /*rate_limiter_priority*/, bool& copy_to_third_buffer,
uint64_t& tmp_offset, size_t& tmp_length) {
Status s;
uint64_t tmp_offset = offset;
size_t tmp_length = length;
// 1. Abort IO and swap buffers if needed to point curr_ to first buffer with
// data.
{
if (!explicit_prefetch_submitted_) {
AbortIOIfNeeded(offset);
}
UpdateBuffersIfNeeded(offset);
}
size_t alignment = reader->file()->GetRequiredBufferAlignment();
uint32_t second = curr_ ^ 1;
// 2. If data is overlapping over two buffers, copy the data from curr_ and
// If data is overlapping over two buffers, copy the data from curr_ and
// call ReadAsync on curr_.
if (!bufs_[curr_].async_read_in_progress_ && DoesBufferContainData(curr_) &&
IsOffsetInBuffer(offset, curr_) &&
@@ -391,21 +372,80 @@ Status FilePrefetchBuffer::PrefetchAsyncInternal(
}
curr_ = curr_ ^ 1;
}
return s;
}
// If async_io is enabled in case of sequential reads, PrefetchAsyncInternal is
// called. When buffers are switched, we clear the curr_ buffer as we assume the
// data has been consumed because of sequential reads.
// Data in buffers will always be sequential with curr_ following second and
// not vice versa.
//
// Scenarios for prefetching asynchronously:
// Case1: If both buffers are empty, prefetch n + readahead_size_/2 bytes
// synchronously in curr_ and prefetch readahead_size_/2 async in second
// buffer.
// Case2: If second buffer has partial or full data, make it current and
// prefetch readahead_size_/2 async in second buffer. In case of
// partial data, prefetch remaining bytes from size n synchronously to
// fulfill the requested bytes request.
// Case3: If curr_ has partial data, prefetch remaining bytes from size n
// synchronously in curr_ to fulfill the requested bytes request and
// prefetch readahead_size_/2 bytes async in second buffer.
// Case4: (Special case) If data is in both buffers, copy requested data from
// curr_, send async request on curr_, wait for poll to fill second
// buffer (if any), and copy remaining data from second buffer to third
// buffer.
Status FilePrefetchBuffer::PrefetchAsyncInternal(
const IOOptions& opts, RandomAccessFileReader* reader, uint64_t offset,
size_t length, size_t readahead_size, Env::IOPriority rate_limiter_priority,
bool& copy_to_third_buffer) {
if (!enable_) {
return Status::OK();
}
TEST_SYNC_POINT("FilePrefetchBuffer::PrefetchAsyncInternal:Start");
size_t alignment = reader->file()->GetRequiredBufferAlignment();
Status s;
uint64_t tmp_offset = offset;
size_t tmp_length = length;
// 1. Abort IO and swap buffers if needed to point curr_ to first buffer with
// data.
if (!explicit_prefetch_submitted_) {
AbortIOIfNeeded(offset);
}
UpdateBuffersIfNeeded(offset);
// 2. Handle overlapping data over two buffers. If data is overlapping then
// during this call:
// - data from curr_ is copied into third buffer,
// - curr_ is send for async prefetching of further data if second buffer
// contains remaining requested data or in progress for async prefetch,
// - switch buffers and curr_ now points to second buffer to copy remaining
// data.
s = HandleOverlappingData(opts, reader, offset, length, readahead_size,
rate_limiter_priority, copy_to_third_buffer,
tmp_offset, tmp_length);
if (!s.ok()) {
return s;
}
// 3. Call Poll only if data is needed for the second buffer.
// - Return if whole data is in curr_ and second buffer in progress.
// - Return if whole data is in curr_ and second buffer is in progress or
// already full.
// - If second buffer is empty, it will go for ReadAsync for second buffer.
if (!bufs_[curr_].async_read_in_progress_ && DoesBufferContainData(curr_) &&
IsDataBlockInBuffer(offset, length, curr_)) {
// Whole data is in curr_.
UpdateBuffersIfNeeded(offset);
second = curr_ ^ 1;
if (bufs_[second].async_read_in_progress_) {
if (!IsSecondBuffEligibleForPrefetching()) {
return s;
}
} else {
// After poll request, curr_ might be empty because of IOError in
// callback while reading or may contain required data.
PollAndUpdateBuffersIfNeeded(offset);
second = curr_ ^ 1;
}
if (copy_to_third_buffer) {
@@ -427,19 +467,42 @@ Status FilePrefetchBuffer::PrefetchAsyncInternal(
if (explicit_prefetch_submitted_) {
return s;
}
if (!IsSecondBuffEligibleForPrefetching()) {
return s;
}
}
uint32_t second = curr_ ^ 1;
assert(!bufs_[curr_].async_read_in_progress_);
// In case because of some IOError curr_ got empty, abort IO for second as
// well. Otherwise data might not align if more data needs to be read in curr_
// which might overlap with second buffer.
if (!DoesBufferContainData(curr_) && bufs_[second].async_read_in_progress_) {
if (bufs_[second].io_handle_ != nullptr) {
std::vector<void*> handles;
handles.emplace_back(bufs_[second].io_handle_);
{
StopWatch sw(clock_, stats_, ASYNC_PREFETCH_ABORT_MICROS);
Status status = fs_->AbortIO(handles);
assert(status.ok());
}
}
DestroyAndClearIOHandle(second);
bufs_[second].buffer_.Clear();
}
// 5. Data is overlapping i.e. some of the data has been copied to third
// buffer
// and remaining will be updated below.
if (copy_to_third_buffer) {
// buffer and remaining will be updated below.
if (copy_to_third_buffer && DoesBufferContainData(curr_)) {
CopyDataToBuffer(curr_, offset, length);
// Length == 0: All the requested data has been copied to third buffer and
// it has already gone for async prefetching. It can return without doing
// anything further.
// Length > 0: More data needs to be consumed so it will continue async and
// sync prefetching and copy the remaining data to third buffer in the end.
// Length > 0: More data needs to be consumed so it will continue async
// and sync prefetching and copy the remaining data to third buffer in the
// end.
if (length == 0) {
return s;
}
@@ -458,6 +521,9 @@ Status FilePrefetchBuffer::PrefetchAsyncInternal(
uint64_t chunk_len1 = 0;
uint64_t read_len1 = 0;
assert(!bufs_[second].async_read_in_progress_ &&
!DoesBufferContainData(second));
// For length == 0, skip the synchronous prefetching. read_len1 will be 0.
if (length > 0) {
CalculateOffsetAndLen(alignment, offset, roundup_len1, curr_,
@@ -594,8 +660,11 @@ bool FilePrefetchBuffer::TryReadFromCacheAsync(
}
if (explicit_prefetch_submitted_) {
// explicit_prefetch_submitted_ is special case where it expects request
// submitted in PrefetchAsync should match with this request. Otherwise
// buffers will be outdated.
// Random offset called. So abort the IOs.
if (prev_offset_ != offset) {
// Random offset called. So abort the IOs.
AbortAllIOs();
bufs_[curr_].buffer_.Clear();
bufs_[curr_ ^ 1].buffer_.Clear();
+30
View File
@@ -8,6 +8,7 @@
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#pragma once
#include <algorithm>
#include <atomic>
#include <sstream>
@@ -236,6 +237,7 @@ class FilePrefetchBuffer {
}
prev_offset_ = offset;
prev_len_ = len;
explicit_prefetch_submitted_ = false;
}
void GetReadaheadState(ReadaheadFileInfo::ReadaheadInfo* readahead_info) {
@@ -363,6 +365,27 @@ class FilePrefetchBuffer {
bufs_[index].io_handle_ != nullptr &&
offset >= bufs_[index].offset_ + bufs_[index].async_req_len_);
}
bool IsOffsetInBufferWithAsyncProgress(uint64_t offset, uint32_t index) {
return (bufs_[index].async_read_in_progress_ &&
offset >= bufs_[index].offset_ &&
offset < bufs_[index].offset_ + bufs_[index].async_req_len_);
}
bool IsSecondBuffEligibleForPrefetching() {
uint32_t second = curr_ ^ 1;
if (bufs_[second].async_read_in_progress_) {
return false;
}
assert(!bufs_[curr_].async_read_in_progress_);
if (DoesBufferContainData(curr_) && DoesBufferContainData(second) &&
(bufs_[curr_].offset_ + bufs_[curr_].buffer_.CurrentSize() ==
bufs_[second].offset_)) {
return false;
}
bufs_[second].buffer_.Clear();
return true;
}
void DestroyAndClearIOHandle(uint32_t index) {
if (bufs_[index].io_handle_ != nullptr && bufs_[index].del_fn_ != nullptr) {
@@ -373,6 +396,13 @@ class FilePrefetchBuffer {
bufs_[index].async_read_in_progress_ = false;
}
Status HandleOverlappingData(const IOOptions& opts,
RandomAccessFileReader* reader, uint64_t offset,
size_t length, size_t readahead_size,
Env::IOPriority rate_limiter_priority,
bool& copy_to_third_buffer, uint64_t& tmp_offset,
size_t& tmp_length);
std::vector<BufferInfo> bufs_;
// curr_ represents the index for bufs_ indicating which buffer is being
// consumed currently.
+8
View File
@@ -471,6 +471,7 @@ IOStatus RandomAccessFileReader::ReadAsync(
(uintptr_t(req.scratch) & (alignment - 1)) == 0;
read_async_info->is_aligned_ = is_aligned;
uint64_t elapsed = 0;
if (use_direct_io() && is_aligned == false) {
FSReadRequest aligned_req = Align(req, alignment);
aligned_req.status.PermitUncheckedError();
@@ -491,12 +492,17 @@ IOStatus RandomAccessFileReader::ReadAsync(
assert(read_async_info->buf_.CurrentSize() == 0);
StopWatch sw(clock_, nullptr /*stats*/, 0 /*hist_type*/, &elapsed,
true /*overwrite*/, true /*delay_enabled*/);
s = file_->ReadAsync(aligned_req, opts, read_async_callback,
read_async_info, io_handle, del_fn, nullptr /*dbg*/);
} else {
StopWatch sw(clock_, nullptr /*stats*/, 0 /*hist_type*/, &elapsed,
true /*overwrite*/, true /*delay_enabled*/);
s = file_->ReadAsync(req, opts, read_async_callback, read_async_info,
io_handle, del_fn, nullptr /*dbg*/);
}
RecordTick(stats_, READ_ASYNC_MICROS, elapsed);
// Suppress false positive clang analyzer warnings.
// Memory is not released if file_->ReadAsync returns !s.ok(), because
@@ -575,6 +581,8 @@ void RandomAccessFileReader::ReadAsyncCallback(const FSReadRequest& req,
}
if (req.status.ok()) {
RecordInHistogram(stats_, ASYNC_READ_BYTES, req.result.size());
} else if (!req.status.IsAborted()) {
RecordTick(stats_, ASYNC_READ_ERROR_COUNT, 1);
}
#ifndef ROCKSDB_LITE
if (ShouldNotifyListeners()) {
+4 -4
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2022, Meta, 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) 2022, Meta Platforms, Inc. and affiliates. All rights
// reserved. This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#pragma once
+25 -18
View File
@@ -1285,30 +1285,37 @@ struct DBOptions {
// Default: nullptr
std::shared_ptr<FileChecksumGenFactory> file_checksum_gen_factory = nullptr;
// By default, RocksDB recovery fails if any table/blob file referenced in
// By default, RocksDB recovery fails if any table/blob file referenced in the
// final version reconstructed from the
// MANIFEST are missing after scanning the MANIFEST pointed to by the
// CURRENT file.
// Best-efforts recovery is another recovery mode that tolerates missing or
// corrupted table or blob files.
// CURRENT file. It can also fail if verification of unique SST id fails.
// Best-efforts recovery is another recovery mode that does not necessarily
// fail when certain table/blob files are missing/corrupted or have mismatched
// unique id table property. Instead, best-efforts recovery recovers each
// column family to a point in the MANIFEST that corresponds to a version. In
// such a version, all valid table/blob files referenced have the expected
// file size. For table files, their unique id table property match the
// MANIFEST.
//
// Best-efforts recovery does not need a valid CURRENT file, and tries to
// recover the database using one of the available MANIFEST files in the db
// directory.
// Best-efforts recovery recovers database to a state in which the database
// includes only table and blob files whose actual sizes match the
// information in the chosen MANIFEST without holes in the history.
// Best-efforts recovery tries the available MANIFEST files from high file
// numbers (newer) to low file numbers (older), and stops after finding the
// first MANIFEST file from which the db can be recovered to a state without
// invalid (missing/file-mismatch) table and blob files.
// It is possible that the database can be restored to an empty state with no
// table or blob files.
// Regardless of this option, the IDENTITY file is updated if needed during
// recovery to match the DB ID in the MANIFEST (if previously using
// write_dbid_to_manifest) or to be in some valid state (non-empty DB ID).
// Currently, not compatible with atomic flush. Furthermore, WAL files will
// not be used for recovery if best_efforts_recovery is true.
// Also requires either 1) LOCK file exists or 2) underlying env's LockFile()
// call returns ok even for non-existing LOCK file.
// invalid (missing/filesize-mismatch/unique-id-mismatch) table and blob
// files. It is possible that the database can be restored to an empty state
// with no table or blob files.
//
// Regardless of this option, the IDENTITY file
// is updated if needed during recovery to match the DB ID in the MANIFEST (if
// previously using write_dbid_to_manifest) or to be in some valid state
// (non-empty DB ID). Currently, not compatible with atomic flush.
// Furthermore, WAL files will not be used for recovery if
// best_efforts_recovery is true. Also requires either 1) LOCK file exists or
// 2) underlying env's LockFile() call returns ok even for non-existing LOCK
// file.
//
// Default: false
bool best_efforts_recovery = false;
@@ -1480,7 +1487,7 @@ struct ReadOptions {
const Slice* iterate_lower_bound;
// "iterate_upper_bound" defines the extent up to which the forward iterator
// can returns entries. Once the bound is reached, Valid() will be false.
// can return entries. Once the bound is reached, Valid() will be false.
// "iterate_upper_bound" is exclusive ie the bound value is
// not a valid entry. If prefix_extractor is not null:
// 1. If options.auto_prefix_mode = true, iterate_upper_bound will be used
-4
View File
@@ -7,7 +7,6 @@
#ifndef ROCKSDB_LITE
#include "db/range_tombstone_fragmenter.h"
#include "rocksdb/iterator.h"
#include "rocksdb/options.h"
#include "rocksdb/slice.h"
@@ -31,9 +30,6 @@ class SstFileReader {
// If "snapshot" is nullptr, the iterator returns only the latest keys.
Iterator* NewIterator(const ReadOptions& options);
FragmentedRangeTombstoneIterator* NewRangeTombstoneIterator(
const ReadOptions& options);
std::shared_ptr<const TableProperties> GetTableProperties() const;
// Verifies whether there is corruption in this table.
+5
View File
@@ -448,6 +448,11 @@ enum Tickers : uint32_t {
// # of bytes written into blob cache.
BLOB_DB_CACHE_BYTES_WRITE,
// Time spent in the ReadAsync file system call
READ_ASYNC_MICROS,
// Number of errors returned to the async read callback
ASYNC_READ_ERROR_COUNT,
TICKER_ENUM_MAX
};
+1 -1
View File
@@ -27,7 +27,7 @@ struct ConfigOptions;
// operating system time-related functionality.
class SystemClock : public Customizable {
public:
virtual ~SystemClock() {}
~SystemClock() override {}
static const char* Type() { return "SystemClock"; }
static Status CreateFromString(const ConfigOptions& options,
+1 -1
View File
@@ -13,7 +13,7 @@
// minor or major version number planned for release.
#define ROCKSDB_MAJOR 7
#define ROCKSDB_MINOR 8
#define ROCKSDB_PATCH 0
#define ROCKSDB_PATCH 3
// 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
+8
View File
@@ -5104,6 +5104,10 @@ class TickerTypeJni {
return -0x33;
case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_BYTES_WRITE:
return -0x34;
case ROCKSDB_NAMESPACE::Tickers::READ_ASYNC_MICROS:
return -0x35;
case ROCKSDB_NAMESPACE::Tickers::ASYNC_READ_ERROR_COUNT:
return -0x36;
case ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX:
// 0x5F was the max value in the initial copy of tickers to Java.
// Since these values are exposed directly to Java clients, we keep
@@ -5487,6 +5491,10 @@ class TickerTypeJni {
return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_BYTES_READ;
case -0x34:
return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_CACHE_BYTES_WRITE;
case -0x35:
return ROCKSDB_NAMESPACE::Tickers::READ_ASYNC_MICROS;
case -0x36:
return ROCKSDB_NAMESPACE::Tickers::ASYNC_READ_ERROR_COUNT;
case 0x5F:
// 0x5F was the max value in the initial copy of tickers to Java.
// Since these values are exposed directly to Java clients, we keep
+87 -22
View File
@@ -8,7 +8,9 @@
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#include "memory/arena.h"
#ifndef OS_WIN
#include <sys/mman.h>
#endif
#include <algorithm>
#include "logging/logging.h"
@@ -20,7 +22,16 @@
namespace ROCKSDB_NAMESPACE {
size_t Arena::OptimizeBlockSize(size_t block_size) {
// MSVC complains that it is already defined since it is static in the header.
#ifndef _MSC_VER
const size_t Arena::kInlineSize;
#endif
const size_t Arena::kMinBlockSize = 4096;
const size_t Arena::kMaxBlockSize = 2u << 30;
static const int kAlignUnit = alignof(max_align_t);
size_t OptimizeBlockSize(size_t block_size) {
// Make sure block_size is in optimal range
block_size = std::max(Arena::kMinBlockSize, block_size);
block_size = std::min(Arena::kMaxBlockSize, block_size);
@@ -42,12 +53,14 @@ Arena::Arena(size_t block_size, AllocTracker* tracker, size_t huge_page_size)
blocks_memory_ += alloc_bytes_remaining_;
aligned_alloc_ptr_ = inline_block_;
unaligned_alloc_ptr_ = inline_block_ + alloc_bytes_remaining_;
if (MemMapping::kHugePageSupported) {
hugetlb_size_ = huge_page_size;
if (hugetlb_size_ && kBlockSize > hugetlb_size_) {
hugetlb_size_ = ((kBlockSize - 1U) / hugetlb_size_ + 1U) * hugetlb_size_;
}
#ifdef MAP_HUGETLB
hugetlb_size_ = huge_page_size;
if (hugetlb_size_ && kBlockSize > hugetlb_size_) {
hugetlb_size_ = ((kBlockSize - 1U) / hugetlb_size_ + 1U) * hugetlb_size_;
}
#else
(void)huge_page_size;
#endif
if (tracker_ != nullptr) {
tracker_->Allocate(kInlineSize);
}
@@ -58,6 +71,21 @@ Arena::~Arena() {
assert(tracker_->is_freed());
tracker_->FreeMem();
}
for (const auto& block : blocks_) {
delete[] block;
}
#ifdef MAP_HUGETLB
for (const auto& mmap_info : huge_blocks_) {
if (mmap_info.addr_ == nullptr) {
continue;
}
auto ret = munmap(mmap_info.addr_, mmap_info.length_);
if (ret != 0) {
// TODO(sdong): Better handling
}
}
#endif
}
char* Arena::AllocateFallback(size_t bytes, bool aligned) {
@@ -71,10 +99,12 @@ char* Arena::AllocateFallback(size_t bytes, bool aligned) {
// We waste the remaining space in the current block.
size_t size = 0;
char* block_head = nullptr;
if (MemMapping::kHugePageSupported && hugetlb_size_ > 0) {
#ifdef MAP_HUGETLB
if (hugetlb_size_) {
size = hugetlb_size_;
block_head = AllocateFromHugePage(size);
}
#endif
if (!block_head) {
size = kBlockSize;
block_head = AllocateNewBlock(size);
@@ -93,22 +123,45 @@ char* Arena::AllocateFallback(size_t bytes, bool aligned) {
}
char* Arena::AllocateFromHugePage(size_t bytes) {
MemMapping mm = MemMapping::AllocateHuge(bytes);
auto addr = static_cast<char*>(mm.Get());
if (addr) {
huge_blocks_.push_back(std::move(mm));
blocks_memory_ += bytes;
if (tracker_ != nullptr) {
tracker_->Allocate(bytes);
}
#ifdef MAP_HUGETLB
if (hugetlb_size_ == 0) {
return nullptr;
}
return addr;
// Reserve space in `huge_blocks_` before calling `mmap`.
// Use `emplace_back()` instead of `reserve()` to let std::vector manage its
// own memory and do fewer reallocations.
//
// - If `emplace_back` throws, no memory leaks because we haven't called
// `mmap` yet.
// - If `mmap` throws, no memory leaks because the vector will be cleaned up
// via RAII.
huge_blocks_.emplace_back(nullptr /* addr */, 0 /* length */);
void* addr = mmap(nullptr, bytes, (PROT_READ | PROT_WRITE),
(MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB), -1, 0);
if (addr == MAP_FAILED) {
return nullptr;
}
huge_blocks_.back() = MmapInfo(addr, bytes);
blocks_memory_ += bytes;
if (tracker_ != nullptr) {
tracker_->Allocate(bytes);
}
return reinterpret_cast<char*>(addr);
#else
(void)bytes;
return nullptr;
#endif
}
char* Arena::AllocateAligned(size_t bytes, size_t huge_page_size,
Logger* logger) {
if (MemMapping::kHugePageSupported && hugetlb_size_ > 0 &&
huge_page_size > 0 && bytes > 0) {
assert((kAlignUnit & (kAlignUnit - 1)) ==
0); // Pointer size should be a power of 2
#ifdef MAP_HUGETLB
if (huge_page_size > 0 && bytes > 0) {
// Allocate from a huge page TLB table.
size_t reserved_size =
((bytes - 1U) / huge_page_size + 1U) * huge_page_size;
@@ -124,6 +177,10 @@ char* Arena::AllocateAligned(size_t bytes, size_t huge_page_size,
return addr;
}
}
#else
(void)huge_page_size;
(void)logger;
#endif
size_t current_mod =
reinterpret_cast<uintptr_t>(aligned_alloc_ptr_) & (kAlignUnit - 1);
@@ -143,10 +200,17 @@ char* Arena::AllocateAligned(size_t bytes, size_t huge_page_size,
}
char* Arena::AllocateNewBlock(size_t block_bytes) {
auto uniq = std::make_unique<char[]>(block_bytes);
char* block = uniq.get();
blocks_.push_back(std::move(uniq));
// Reserve space in `blocks_` before allocating memory via new.
// Use `emplace_back()` instead of `reserve()` to let std::vector manage its
// own memory and do fewer reallocations.
//
// - If `emplace_back` throws, no memory leaks because we haven't called `new`
// yet.
// - If `new` throws, no memory leaks because the vector will be cleaned up
// via RAII.
blocks_.emplace_back(nullptr);
char* block = new char[block_bytes];
size_t allocated_size;
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
allocated_size = malloc_usable_size(block);
@@ -163,6 +227,7 @@ char* Arena::AllocateNewBlock(size_t block_bytes) {
if (tracker_ != nullptr) {
tracker_->Allocate(allocated_size);
}
blocks_.back() = block;
return block;
}
+31 -25
View File
@@ -12,13 +12,16 @@
// size, it uses malloc to directly get the requested size.
#pragma once
#ifndef OS_WIN
#include <sys/mman.h>
#endif
#include <assert.h>
#include <stdint.h>
#include <cerrno>
#include <cstddef>
#include <deque>
#include <vector>
#include "memory/allocator.h"
#include "port/mmap.h"
#include "rocksdb/env.h"
#include "util/mutexlock.h"
namespace ROCKSDB_NAMESPACE {
@@ -28,13 +31,9 @@ class Arena : public Allocator {
Arena(const Arena&) = delete;
void operator=(const Arena&) = delete;
static constexpr size_t kInlineSize = 2048;
static constexpr size_t kMinBlockSize = 4096;
static constexpr size_t kMaxBlockSize = 2u << 30;
static constexpr unsigned kAlignUnit = alignof(std::max_align_t);
static_assert((kAlignUnit & (kAlignUnit - 1)) == 0,
"Pointer size should be power of 2");
static const size_t kInlineSize = 2048;
static const size_t kMinBlockSize;
static const size_t kMaxBlockSize;
// huge_page_size: if 0, don't use huge page TLB. If > 0 (should set to the
// supported hugepage size of the system), block allocation will try huge
@@ -64,7 +63,7 @@ class Arena : public Allocator {
// by the arena (exclude the space allocated but not yet used for future
// allocations).
size_t ApproximateMemoryUsage() const {
return blocks_memory_ + blocks_.size() * sizeof(char*) -
return blocks_memory_ + blocks_.capacity() * sizeof(char*) -
alloc_bytes_remaining_;
}
@@ -82,19 +81,21 @@ class Arena : public Allocator {
return blocks_.empty() && huge_blocks_.empty();
}
// check and adjust the block_size so that the return value is
// 1. in the range of [kMinBlockSize, kMaxBlockSize].
// 2. the multiple of align unit.
static size_t OptimizeBlockSize(size_t block_size);
private:
alignas(std::max_align_t) char inline_block_[kInlineSize];
char inline_block_[kInlineSize] __attribute__((__aligned__(alignof(max_align_t))));
// Number of bytes allocated in one block
const size_t kBlockSize;
// Allocated memory blocks
std::deque<std::unique_ptr<char[]>> blocks_;
// Huge page allocations
std::deque<MemMapping> huge_blocks_;
// Array of new[] allocated memory blocks
using Blocks = std::vector<char*>;
Blocks blocks_;
struct MmapInfo {
void* addr_;
size_t length_;
MmapInfo(void* addr, size_t length) : addr_(addr), length_(length) {}
};
std::vector<MmapInfo> huge_blocks_;
size_t irregular_block_num = 0;
// Stats for current active block.
@@ -107,15 +108,15 @@ class Arena : public Allocator {
// How many bytes left in currently active block?
size_t alloc_bytes_remaining_ = 0;
#ifdef MAP_HUGETLB
size_t hugetlb_size_ = 0;
#endif // MAP_HUGETLB
char* AllocateFromHugePage(size_t bytes);
char* AllocateFallback(size_t bytes, bool aligned);
char* AllocateNewBlock(size_t block_bytes);
// Bytes of memory in blocks allocated so far
size_t blocks_memory_ = 0;
// Non-owned
AllocTracker* tracker_;
};
@@ -132,4 +133,9 @@ inline char* Arena::Allocate(size_t bytes) {
return AllocateFallback(bytes, false /* unaligned */);
}
// check and adjust the block_size so that the return value is
// 1. in the range of [kMinBlockSize, kMaxBlockSize].
// 2. the multiple of align unit.
extern size_t OptimizeBlockSize(size_t block_size);
} // namespace ROCKSDB_NAMESPACE
-60
View File
@@ -8,11 +8,6 @@
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#include "memory/arena.h"
#ifndef OS_WIN
#include <sys/resource.h>
#endif
#include "port/port.h"
#include "test_util/testharness.h"
#include "util/random.h"
@@ -201,61 +196,6 @@ TEST_F(ArenaTest, Simple) {
SimpleTest(0);
SimpleTest(kHugePageSize);
}
// Number of minor page faults since last call
size_t PopMinorPageFaultCount() {
#ifdef RUSAGE_SELF
static long prev = 0;
struct rusage usage;
EXPECT_EQ(getrusage(RUSAGE_SELF, &usage), 0);
size_t rv = usage.ru_minflt - prev;
prev = usage.ru_minflt;
return rv;
#else
// Conservative
return SIZE_MAX;
#endif // RUSAGE_SELF
}
TEST(MmapTest, AllocateLazyZeroed) {
// Doesn't have to be page aligned
constexpr size_t len = 1234567;
MemMapping m = MemMapping::AllocateLazyZeroed(len);
auto arr = static_cast<char*>(m.Get());
// Should generally work
ASSERT_NE(arr, nullptr);
// Start counting page faults
PopMinorPageFaultCount();
// Access half of the allocation
size_t i = 0;
for (; i < len / 2; ++i) {
ASSERT_EQ(arr[i], 0);
arr[i] = static_cast<char>(i & 255);
}
// Appropriate page faults (maybe more)
size_t faults = PopMinorPageFaultCount();
ASSERT_GE(faults, len / 2 / port::kPageSize);
// Access rest of the allocation
for (; i < len; ++i) {
ASSERT_EQ(arr[i], 0);
arr[i] = static_cast<char>(i & 255);
}
// Appropriate page faults (maybe more)
faults = PopMinorPageFaultCount();
ASSERT_GE(faults, len / 2 / port::kPageSize);
// Verify data
for (i = 0; i < len; ++i) {
ASSERT_EQ(arr[i], static_cast<char>(i & 255));
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+3 -1
View File
@@ -233,7 +233,9 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
{BLOB_DB_CACHE_ADD, "rocksdb.blobdb.cache.add"},
{BLOB_DB_CACHE_ADD_FAILURES, "rocksdb.blobdb.cache.add.failures"},
{BLOB_DB_CACHE_BYTES_READ, "rocksdb.blobdb.cache.bytes.read"},
{BLOB_DB_CACHE_BYTES_WRITE, "rocksdb.blobdb.cache.bytes.write"}};
{BLOB_DB_CACHE_BYTES_WRITE, "rocksdb.blobdb.cache.bytes.write"},
{READ_ASYNC_MICROS, "rocksdb.read.async.micros"},
{ASYNC_READ_ERROR_COUNT, "rocksdb.async.read.error.count"}};
const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
{DB_GET, "rocksdb.db.get.micros"},
-98
View File
@@ -1,98 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "port/mmap.h"
#include <cassert>
#include <cstdio>
#include <cstring>
#include <new>
#include <utility>
#include "util/hash.h"
namespace ROCKSDB_NAMESPACE {
MemMapping::~MemMapping() {
#ifdef OS_WIN
if (addr_ != nullptr) {
(void)::UnmapViewOfFile(addr_);
}
if (page_file_handle_ != NULL) {
(void)::CloseHandle(page_file_handle_);
}
#else // OS_WIN -> !OS_WIN
if (addr_ != nullptr) {
auto status = munmap(addr_, length_);
assert(status == 0);
if (status != 0) {
// TODO: handle error?
}
}
#endif // OS_WIN
}
MemMapping::MemMapping(MemMapping&& other) noexcept {
*this = std::move(other);
}
MemMapping& MemMapping::operator=(MemMapping&& other) noexcept {
if (&other == this) {
return *this;
}
this->~MemMapping();
std::memcpy(this, &other, sizeof(*this));
new (&other) MemMapping();
return *this;
}
MemMapping MemMapping::AllocateAnonymous(size_t length, bool huge) {
MemMapping mm;
mm.length_ = length;
assert(mm.addr_ == nullptr);
if (length == 0) {
// OK to leave addr as nullptr
return mm;
}
int huge_flag = 0;
#ifdef OS_WIN
if (huge) {
#ifdef FILE_MAP_LARGE_PAGES
huge_flag = FILE_MAP_LARGE_PAGES;
#endif // FILE_MAP_LARGE_PAGES
}
mm.page_file_handle_ = ::CreateFileMapping(
INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE | SEC_COMMIT,
Upper32of64(length), Lower32of64(length), nullptr);
if (mm.page_file_handle_ == NULL) {
// Failure
return mm;
}
mm.addr_ = ::MapViewOfFile(mm.page_file_handle_, FILE_MAP_WRITE | huge_flag,
0, 0, length);
#else // OS_WIN -> !OS_WIN
if (huge) {
#ifdef MAP_HUGETLB
huge_flag = MAP_HUGETLB;
#endif // MAP_HUGE_TLB
}
mm.addr_ = mmap(nullptr, length, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | huge_flag, -1, 0);
if (mm.addr_ == MAP_FAILED) {
mm.addr_ = nullptr;
}
#endif // OS_WIN
return mm;
}
MemMapping MemMapping::AllocateHuge(size_t length) {
return AllocateAnonymous(length, /*huge*/ true);
}
MemMapping MemMapping::AllocateLazyZeroed(size_t length) {
return AllocateAnonymous(length, /*huge*/ false);
}
} // namespace ROCKSDB_NAMESPACE
-70
View File
@@ -1,70 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#pragma once
#ifdef OS_WIN
#include <windows.h>
// ^^^ Must come first
#include <memoryapi.h>
#else
#include <sys/mman.h>
#endif // OS_WIN
#include <cstdint>
#include "rocksdb/rocksdb_namespace.h"
namespace ROCKSDB_NAMESPACE {
// An RAII wrapper for mmaped memory
class MemMapping {
public:
static constexpr bool kHugePageSupported =
#if defined(MAP_HUGETLB) || defined(FILE_MAP_LARGE_PAGES)
true;
#else
false;
#endif
// Allocate memory requesting to be backed by huge pages
static MemMapping AllocateHuge(size_t length);
// Allocate memory that is only lazily mapped to resident memory and
// guaranteed to be zero-initialized. Note that some platforms like
// Linux allow memory over-commit, where only the used portion of memory
// matters, while other platforms require enough swap space (page file) to
// back the full mapping.
static MemMapping AllocateLazyZeroed(size_t length);
// No copies
MemMapping(const MemMapping&) = delete;
MemMapping& operator=(const MemMapping&) = delete;
// Move
MemMapping(MemMapping&&) noexcept;
MemMapping& operator=(MemMapping&&) noexcept;
// Releases the mapping
~MemMapping();
inline void* Get() const { return addr_; }
inline size_t Length() const { return length_; }
private:
MemMapping() {}
// The mapped memory, or nullptr on failure / not supported
void* addr_ = nullptr;
// The known usable number of bytes starting at that address
size_t length_ = 0;
#ifdef OS_WIN
HANDLE page_file_handle_ = NULL;
#endif // OS_WIN
static MemMapping AllocateAnonymous(size_t length, bool huge);
};
} // namespace ROCKSDB_NAMESPACE
-1
View File
@@ -154,7 +154,6 @@ LIB_SOURCES = \
options/options.cc \
options/options_helper.cc \
options/options_parser.cc \
port/mmap.cc \
port/port_posix.cc \
port/win/env_default.cc \
port/win/env_win.cc \
+69 -24
View File
@@ -117,14 +117,16 @@ class MergingIterator : public InternalIterator {
public:
MergingIterator(const InternalKeyComparator* comparator,
InternalIterator** children, int n, bool is_arena_mode,
bool prefix_seek_mode)
bool prefix_seek_mode,
const Slice* iterate_upper_bound = nullptr)
: is_arena_mode_(is_arena_mode),
prefix_seek_mode_(prefix_seek_mode),
direction_(kForward),
comparator_(comparator),
current_(nullptr),
minHeap_(comparator_),
pinned_iters_mgr_(nullptr) {
pinned_iters_mgr_(nullptr),
iterate_upper_bound_(iterate_upper_bound) {
children_.resize(n);
for (int i = 0; i < n; i++) {
children_[i].level = i;
@@ -197,27 +199,48 @@ class MergingIterator : public InternalIterator {
// Add range_tombstone_iters_[level] into min heap.
// Updates active_ if the end key of a range tombstone is inserted.
// @param start_key specifies which end point of the range tombstone to add.
void InsertRangeTombstoneToMinHeap(size_t level, bool start_key = true) {
void InsertRangeTombstoneToMinHeap(size_t level, bool start_key = true,
bool replace_top = false) {
assert(!range_tombstone_iters_.empty() &&
range_tombstone_iters_[level]->Valid());
if (start_key) {
pinned_heap_item_[level].SetTombstoneKey(
range_tombstone_iters_[level]->start_key());
ParsedInternalKey pik = range_tombstone_iters_[level]->start_key();
// iterate_upper_bound does not have timestamp
if (iterate_upper_bound_ &&
comparator_->user_comparator()->CompareWithoutTimestamp(
pik.user_key, true /* a_has_ts */, *iterate_upper_bound_,
false /* b_has_ts */) >= 0) {
if (replace_top) {
// replace_top implies this range tombstone iterator is still in
// minHeap_ and at the top.
minHeap_.pop();
}
return;
}
pinned_heap_item_[level].SetTombstoneKey(std::move(pik));
pinned_heap_item_[level].type = HeapItem::DELETE_RANGE_START;
assert(active_.count(level) == 0);
} else {
// allow end key to go over upper bound (if present) since start key is
// before upper bound and the range tombstone could still cover a
// range before upper bound.
pinned_heap_item_[level].SetTombstoneKey(
range_tombstone_iters_[level]->end_key());
pinned_heap_item_[level].type = HeapItem::DELETE_RANGE_END;
active_.insert(level);
}
minHeap_.push(&pinned_heap_item_[level]);
if (replace_top) {
minHeap_.replace_top(&pinned_heap_item_[level]);
} else {
minHeap_.push(&pinned_heap_item_[level]);
}
}
// Add range_tombstone_iters_[level] into max heap.
// Updates active_ if the start key of a range tombstone is inserted.
// @param end_key specifies which end point of the range tombstone to add.
void InsertRangeTombstoneToMaxHeap(size_t level, bool end_key = true) {
void InsertRangeTombstoneToMaxHeap(size_t level, bool end_key = true,
bool replace_top = false) {
assert(!range_tombstone_iters_.empty() &&
range_tombstone_iters_[level]->Valid());
if (end_key) {
@@ -231,7 +254,11 @@ class MergingIterator : public InternalIterator {
pinned_heap_item_[level].type = HeapItem::DELETE_RANGE_START;
active_.insert(level);
}
maxHeap_->push(&pinned_heap_item_[level]);
if (replace_top) {
maxHeap_->replace_top(&pinned_heap_item_[level]);
} else {
maxHeap_->push(&pinned_heap_item_[level]);
}
}
// Remove HeapItems from top of minHeap_ that are of type DELETE_RANGE_START
@@ -241,10 +268,10 @@ class MergingIterator : public InternalIterator {
void PopDeleteRangeStart() {
while (!minHeap_.empty() &&
minHeap_.top()->type == HeapItem::DELETE_RANGE_START) {
auto level = minHeap_.top()->level;
minHeap_.pop();
TEST_SYNC_POINT_CALLBACK("MergeIterator::PopDeleteRangeStart", nullptr);
// insert end key of this range tombstone and updates active_
InsertRangeTombstoneToMinHeap(level, false /* start_key */);
InsertRangeTombstoneToMinHeap(
minHeap_.top()->level, false /* start_key */, true /* replace_top */);
}
}
@@ -255,10 +282,9 @@ class MergingIterator : public InternalIterator {
void PopDeleteRangeEnd() {
while (!maxHeap_->empty() &&
maxHeap_->top()->type == HeapItem::DELETE_RANGE_END) {
auto level = maxHeap_->top()->level;
maxHeap_->pop();
// insert start key of this range tombstone and updates active_
InsertRangeTombstoneToMaxHeap(level, false /* end_key */);
InsertRangeTombstoneToMaxHeap(maxHeap_->top()->level, false /* end_key */,
true /* replace_top */);
}
}
@@ -565,6 +591,10 @@ class MergingIterator : public InternalIterator {
std::unique_ptr<MergerMaxIterHeap> maxHeap_;
PinnedIteratorsManager* pinned_iters_mgr_;
// Used to bound range tombstones. For point keys, DBIter and SSTable iterator
// take care of boundary checking.
const Slice* iterate_upper_bound_;
// In forward direction, process a child that is not in the min heap.
// If valid, add to the min heap. Otherwise, check status.
void AddToMinHeapOrCheckStatus(HeapItem*);
@@ -626,9 +656,19 @@ void MergingIterator::SeekImpl(const Slice& target, size_t starting_level,
for (size_t level = 0; level < starting_level; ++level) {
if (range_tombstone_iters_[level] &&
range_tombstone_iters_[level]->Valid()) {
assert(static_cast<bool>(active_.count(level)) ==
(pinned_heap_item_[level].type == HeapItem::DELETE_RANGE_END));
minHeap_.push(&pinned_heap_item_[level]);
// use an iterator on active_ if performance becomes an issue here
if (active_.count(level) > 0) {
assert(pinned_heap_item_[level].type == HeapItem::DELETE_RANGE_END);
// if it was active, then start key must be within upper_bound,
// so we can add to minHeap_ directly.
minHeap_.push(&pinned_heap_item_[level]);
} else {
// this takes care of checking iterate_upper_bound, but with an extra
// key comparison if range_tombstone_iters_[level] was already out of
// bound. Consider using a new HeapItem type or some flag to remember
// boundary checking result.
InsertRangeTombstoneToMinHeap(level);
}
} else {
assert(!active_.count(level));
}
@@ -766,13 +806,15 @@ bool MergingIterator::SkipNextDeleted() {
// - range deletion end key
auto current = minHeap_.top();
if (current->type == HeapItem::DELETE_RANGE_END) {
minHeap_.pop();
active_.erase(current->level);
assert(range_tombstone_iters_[current->level] &&
range_tombstone_iters_[current->level]->Valid());
range_tombstone_iters_[current->level]->Next();
if (range_tombstone_iters_[current->level]->Valid()) {
InsertRangeTombstoneToMinHeap(current->level);
InsertRangeTombstoneToMinHeap(current->level, true /* start_key */,
true /* replace_top */);
} else {
minHeap_.pop();
}
return true /* current key deleted */;
}
@@ -981,13 +1023,15 @@ bool MergingIterator::SkipPrevDeleted() {
// - range deletion start key
auto current = maxHeap_->top();
if (current->type == HeapItem::DELETE_RANGE_START) {
maxHeap_->pop();
active_.erase(current->level);
assert(range_tombstone_iters_[current->level] &&
range_tombstone_iters_[current->level]->Valid());
range_tombstone_iters_[current->level]->Prev();
if (range_tombstone_iters_[current->level]->Valid()) {
InsertRangeTombstoneToMaxHeap(current->level);
InsertRangeTombstoneToMaxHeap(current->level, true /* end_key */,
true /* replace_top */);
} else {
maxHeap_->pop();
}
return true /* current key deleted */;
}
@@ -1265,11 +1309,12 @@ InternalIterator* NewMergingIterator(const InternalKeyComparator* cmp,
}
MergeIteratorBuilder::MergeIteratorBuilder(
const InternalKeyComparator* comparator, Arena* a, bool prefix_seek_mode)
const InternalKeyComparator* comparator, Arena* a, bool prefix_seek_mode,
const Slice* iterate_upper_bound)
: first_iter(nullptr), use_merging_iter(false), arena(a) {
auto mem = arena->AllocateAligned(sizeof(MergingIterator));
merge_iter =
new (mem) MergingIterator(comparator, nullptr, 0, true, prefix_seek_mode);
merge_iter = new (mem) MergingIterator(comparator, nullptr, 0, true,
prefix_seek_mode, iterate_upper_bound);
}
MergeIteratorBuilder::~MergeIteratorBuilder() {
+2 -1
View File
@@ -45,7 +45,8 @@ class MergeIteratorBuilder {
// comparator: the comparator used in merging comparator
// arena: where the merging iterator needs to be allocated from.
explicit MergeIteratorBuilder(const InternalKeyComparator* comparator,
Arena* arena, bool prefix_seek_mode = false);
Arena* arena, bool prefix_seek_mode = false,
const Slice* iterate_upper_bound = nullptr);
~MergeIteratorBuilder();
// Add iter to the merging iterator.
-6
View File
@@ -86,12 +86,6 @@ Iterator* SstFileReader::NewIterator(const ReadOptions& roptions) {
return res;
}
FragmentedRangeTombstoneIterator* SstFileReader::NewRangeTombstoneIterator(
const ReadOptions& options) {
auto r = rep_.get();
return r->table_reader->NewRangeTombstoneIterator(options);
}
std::shared_ptr<const TableProperties> SstFileReader::GetTableProperties()
const {
return rep_->table_reader->GetTableProperties();
-33
View File
@@ -385,39 +385,6 @@ TEST_F(SstFileReaderTimestampTest, Basic) {
}
}
TEST_F(SstFileReaderTimestampTest, BasicDeleteRange) {
SstFileWriter writer(soptions_, options_);
ASSERT_OK(writer.Open(sst_name_));
ASSERT_OK(writer.DeleteRange("key1", "key2", EncodeAsUint64(1)));
ASSERT_OK(writer.Finish());
SstFileReader reader(options_);
ASSERT_OK(reader.Open(sst_name_));
ASSERT_OK(reader.VerifyChecksum());
ReadOptions read_options;
std::string ts = EncodeAsUint64(2);
Slice ts_slice = ts;
read_options.timestamp = &ts_slice;
FragmentedRangeTombstoneIterator* iter =
reader.NewRangeTombstoneIterator(read_options);
iter->SeekToFirst();
ASSERT_TRUE(iter->Valid());
ASSERT_OK(iter->status());
ASSERT_EQ(
StripTimestampFromUserKey(iter->start_key(), EncodeAsUint64(1).size()),
"key1");
ASSERT_EQ(
StripTimestampFromUserKey(iter->end_key(), EncodeAsUint64(1).size()),
"key2");
ASSERT_EQ(iter->timestamp(), EncodeAsUint64(1));
iter->Next();
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
delete iter;
}
TEST_F(SstFileReaderTimestampTest, TimestampsOutOfOrder) {
SstFileWriter writer(soptions_, options_);