|
|
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
|
|
//
|
|
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
#include "options/cf_options.h"
|
|
|
|
#include "options/db_options.h"
|
|
|
|
#include "options/options_helper.h"
|
|
|
|
#include "rocksdb/convenience.h"
|
|
|
|
#include "test_util/testharness.h"
|
|
|
|
|
|
|
|
#ifndef GFLAGS
|
|
|
|
bool FLAGS_enable_print = false;
|
|
|
|
#else
|
|
|
|
#include "util/gflags_compat.h"
|
|
|
|
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
|
|
|
DEFINE_bool(enable_print, false, "Print options generated to console.");
|
|
|
|
#endif // GFLAGS
|
|
|
|
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
|
|
|
|
// Verify options are settable from options strings.
|
|
|
|
// We take the approach that depends on compiler behavior that copy constructor
|
|
|
|
// won't touch implicit padding bytes, so that the test is fragile.
|
|
|
|
// As a result, we only run the tests to verify new fields in options are
|
|
|
|
// settable through string on limited platforms as it depends on behavior of
|
|
|
|
// compilers.
|
|
|
|
#ifndef ROCKSDB_LITE
|
|
|
|
#if defined OS_LINUX || defined OS_WIN
|
|
|
|
#ifndef __clang__
|
|
|
|
|
|
|
|
class OptionsSettableTest : public testing::Test {
|
|
|
|
public:
|
|
|
|
OptionsSettableTest() {}
|
|
|
|
};
|
|
|
|
|
|
|
|
const char kSpecialChar = 'z';
|
|
|
|
using OffsetGap = std::vector<std::pair<size_t, size_t>>;
|
|
|
|
|
|
|
|
void FillWithSpecialChar(char* start_ptr, size_t total_size,
|
|
|
|
const OffsetGap& excluded,
|
|
|
|
char special_char = kSpecialChar) {
|
|
|
|
size_t offset = 0;
|
|
|
|
for (auto& pair : excluded) {
|
|
|
|
std::memset(start_ptr + offset, special_char, pair.first - offset);
|
|
|
|
offset = pair.first + pair.second;
|
|
|
|
}
|
|
|
|
std::memset(start_ptr + offset, special_char, total_size - offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
int NumUnsetBytes(char* start_ptr, size_t total_size,
|
|
|
|
const OffsetGap& excluded) {
|
|
|
|
int total_unset_bytes_base = 0;
|
|
|
|
size_t offset = 0;
|
|
|
|
for (auto& pair : excluded) {
|
|
|
|
for (char* ptr = start_ptr + offset; ptr < start_ptr + pair.first; ptr++) {
|
|
|
|
if (*ptr == kSpecialChar) {
|
|
|
|
total_unset_bytes_base++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
offset = pair.first + pair.second;
|
|
|
|
}
|
|
|
|
for (char* ptr = start_ptr + offset; ptr < start_ptr + total_size; ptr++) {
|
|
|
|
if (*ptr == kSpecialChar) {
|
|
|
|
total_unset_bytes_base++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return total_unset_bytes_base;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true iff two structs are the same except excluded fields.
|
|
|
|
bool CompareBytes(char* start_ptr1, char* start_ptr2, size_t total_size,
|
|
|
|
const OffsetGap& excluded) {
|
|
|
|
size_t offset = 0;
|
|
|
|
for (auto& pair : excluded) {
|
|
|
|
for (; offset < pair.first; offset++) {
|
|
|
|
if (*(start_ptr1 + offset) != *(start_ptr2 + offset)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
offset = pair.first + pair.second;
|
|
|
|
}
|
|
|
|
for (; offset < total_size; offset++) {
|
|
|
|
if (*(start_ptr1 + offset) != *(start_ptr2 + offset)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the test fails, likely a new option is added to BlockBasedTableOptions
|
|
|
|
// but it cannot be set through GetBlockBasedTableOptionsFromString(), or the
|
|
|
|
// test is not updated accordingly.
|
|
|
|
// After adding an option, we need to make sure it is settable by
|
|
|
|
// GetBlockBasedTableOptionsFromString() and add the option to the input string
|
|
|
|
// passed to the GetBlockBasedTableOptionsFromString() in this test.
|
|
|
|
// If it is a complicated type, you also need to add the field to
|
|
|
|
// kBbtoExcluded, and maybe add customized verification for it.
|
|
|
|
TEST_F(OptionsSettableTest, BlockBasedTableOptionsAllFieldsSettable) {
|
|
|
|
// Items in the form of <offset, size>. Need to be in ascending order
|
|
|
|
// and not overlapping. Need to updated if new pointer-option is added.
|
|
|
|
const OffsetGap kBbtoExcluded = {
|
|
|
|
{offsetof(struct BlockBasedTableOptions, flush_block_policy_factory),
|
|
|
|
sizeof(std::shared_ptr<FlushBlockPolicyFactory>)},
|
|
|
|
{offsetof(struct BlockBasedTableOptions, block_cache),
|
|
|
|
sizeof(std::shared_ptr<Cache>)},
|
|
|
|
{offsetof(struct BlockBasedTableOptions, persistent_cache),
|
|
|
|
sizeof(std::shared_ptr<PersistentCache>)},
|
|
|
|
{offsetof(struct BlockBasedTableOptions, block_cache_compressed),
|
|
|
|
sizeof(std::shared_ptr<Cache>)},
|
|
|
|
{offsetof(struct BlockBasedTableOptions, filter_policy),
|
|
|
|
sizeof(std::shared_ptr<const FilterPolicy>)},
|
|
|
|
};
|
|
|
|
|
|
|
|
// In this test, we catch a new option of BlockBasedTableOptions that is not
|
|
|
|
// settable through GetBlockBasedTableOptionsFromString().
|
|
|
|
// We count padding bytes of the option struct, and assert it to be the same
|
|
|
|
// as unset bytes of an option struct initialized by
|
|
|
|
// GetBlockBasedTableOptionsFromString().
|
|
|
|
|
|
|
|
char* bbto_ptr = new char[sizeof(BlockBasedTableOptions)];
|
|
|
|
|
|
|
|
// Count padding bytes by setting all bytes in the memory to a special char,
|
|
|
|
// copy a well constructed struct to this memory and see how many special
|
|
|
|
// bytes left.
|
|
|
|
BlockBasedTableOptions* bbto = new (bbto_ptr) BlockBasedTableOptions();
|
|
|
|
FillWithSpecialChar(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoExcluded);
|
|
|
|
// It based on the behavior of compiler that padding bytes are not changed
|
|
|
|
// when copying the struct. It's prone to failure when compiler behavior
|
|
|
|
// changes. We verify there is unset bytes to detect the case.
|
|
|
|
*bbto = BlockBasedTableOptions();
|
|
|
|
int unset_bytes_base =
|
|
|
|
NumUnsetBytes(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoExcluded);
|
|
|
|
ASSERT_GT(unset_bytes_base, 0);
|
|
|
|
bbto->~BlockBasedTableOptions();
|
|
|
|
|
|
|
|
// Construct the base option passed into
|
|
|
|
// GetBlockBasedTableOptionsFromString().
|
|
|
|
bbto = new (bbto_ptr) BlockBasedTableOptions();
|
|
|
|
FillWithSpecialChar(bbto_ptr, sizeof(BlockBasedTableOptions), kBbtoExcluded);
|
|
|
|
// This option is not setable:
|
|
|
|
bbto->use_delta_encoding = true;
|
|
|
|
|
|
|
|
char* new_bbto_ptr = new char[sizeof(BlockBasedTableOptions)];
|
|
|
|
BlockBasedTableOptions* new_bbto =
|
|
|
|
new (new_bbto_ptr) BlockBasedTableOptions();
|
|
|
|
FillWithSpecialChar(new_bbto_ptr, sizeof(BlockBasedTableOptions),
|
|
|
|
kBbtoExcluded);
|
|
|
|
|
|
|
|
// Need to update the option string if a new option is added.
|
|
|
|
ASSERT_OK(GetBlockBasedTableOptionsFromString(
|
|
|
|
*bbto,
|
|
|
|
"cache_index_and_filter_blocks=1;"
|
|
|
|
"cache_index_and_filter_blocks_with_high_priority=true;"
|
|
|
|
"metadata_cache_options={top_level_index_pinning=kFallback;"
|
|
|
|
"partition_pinning=kAll;"
|
|
|
|
"unpartitioned_pinning=kFlushedAndSimilar;};"
|
|
|
|
"pin_l0_filter_and_index_blocks_in_cache=1;"
|
|
|
|
"pin_top_level_index_and_filter=1;"
|
|
|
|
"index_type=kHashSearch;"
|
|
|
|
"data_block_index_type=kDataBlockBinaryAndHash;"
|
|
|
|
"index_shortening=kNoShortening;"
|
|
|
|
"data_block_hash_table_util_ratio=0.75;"
|
|
|
|
"checksum=kxxHash;hash_index_allow_collision=1;no_block_cache=1;"
|
|
|
|
"block_cache=1M;block_cache_compressed=1k;block_size=1024;"
|
|
|
|
"block_size_deviation=8;block_restart_interval=4; "
|
|
|
|
"metadata_block_size=1024;"
|
|
|
|
"partition_filters=false;"
|
Minimize memory internal fragmentation for Bloom filters (#6427)
Summary:
New experimental option BBTO::optimize_filters_for_memory builds
filters that maximize their use of "usable size" from malloc_usable_size,
which is also used to compute block cache charges.
Rather than always "rounding up," we track state in the
BloomFilterPolicy object to mix essentially "rounding down" and
"rounding up" so that the average FP rate of all generated filters is
the same as without the option. (YMMV as heavily accessed filters might
be unluckily lower accuracy.)
Thus, the option near-minimizes what the block cache considers as
"memory used" for a given target Bloom filter false positive rate and
Bloom filter implementation. There are no forward or backward
compatibility issues with this change, though it only works on the
format_version=5 Bloom filter.
With Jemalloc, we see about 10% reduction in memory footprint (and block
cache charge) for Bloom filters, but 1-2% increase in storage footprint,
due to encoding efficiency losses (FP rate is non-linear with bits/key).
Why not weighted random round up/down rather than state tracking? By
only requiring malloc_usable_size, we don't actually know what the next
larger and next smaller usable sizes for the allocator are. We pick a
requested size, accept and use whatever usable size it has, and use the
difference to inform our next choice. This allows us to narrow in on the
right balance without tracking/predicting usable sizes.
Why not weight history of generated filter false positive rates by
number of keys? This could lead to excess skew in small filters after
generating a large filter.
Results from filter_bench with jemalloc (irrelevant details omitted):
(normal keys/filter, but high variance)
$ ./filter_bench -quick -impl=2 -average_keys_per_filter=30000 -vary_key_count_ratio=0.9
Build avg ns/key: 29.6278
Number of filters: 5516
Total size (MB): 200.046
Reported total allocated memory (MB): 220.597
Reported internal fragmentation: 10.2732%
Bits/key stored: 10.0097
Average FP rate %: 0.965228
$ ./filter_bench -quick -impl=2 -average_keys_per_filter=30000 -vary_key_count_ratio=0.9 -optimize_filters_for_memory
Build avg ns/key: 30.5104
Number of filters: 5464
Total size (MB): 200.015
Reported total allocated memory (MB): 200.322
Reported internal fragmentation: 0.153709%
Bits/key stored: 10.1011
Average FP rate %: 0.966313
(very few keys / filter, optimization not as effective due to ~59 byte
internal fragmentation in blocked Bloom filter representation)
$ ./filter_bench -quick -impl=2 -average_keys_per_filter=1000 -vary_key_count_ratio=0.9
Build avg ns/key: 29.5649
Number of filters: 162950
Total size (MB): 200.001
Reported total allocated memory (MB): 224.624
Reported internal fragmentation: 12.3117%
Bits/key stored: 10.2951
Average FP rate %: 0.821534
$ ./filter_bench -quick -impl=2 -average_keys_per_filter=1000 -vary_key_count_ratio=0.9 -optimize_filters_for_memory
Build avg ns/key: 31.8057
Number of filters: 159849
Total size (MB): 200
Reported total allocated memory (MB): 208.846
Reported internal fragmentation: 4.42297%
Bits/key stored: 10.4948
Average FP rate %: 0.811006
(high keys/filter)
$ ./filter_bench -quick -impl=2 -average_keys_per_filter=1000000 -vary_key_count_ratio=0.9
Build avg ns/key: 29.7017
Number of filters: 164
Total size (MB): 200.352
Reported total allocated memory (MB): 221.5
Reported internal fragmentation: 10.5552%
Bits/key stored: 10.0003
Average FP rate %: 0.969358
$ ./filter_bench -quick -impl=2 -average_keys_per_filter=1000000 -vary_key_count_ratio=0.9 -optimize_filters_for_memory
Build avg ns/key: 30.7131
Number of filters: 160
Total size (MB): 200.928
Reported total allocated memory (MB): 200.938
Reported internal fragmentation: 0.00448054%
Bits/key stored: 10.1852
Average FP rate %: 0.963387
And from db_bench (block cache) with jemalloc:
$ ./db_bench -db=/dev/shm/dbbench.no_optimize -benchmarks=fillrandom -format_version=5 -value_size=90 -bloom_bits=10 -num=2000000 -threads=8 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=10000 -fifo_compaction_allow_compaction=false
$ ./db_bench -db=/dev/shm/dbbench -benchmarks=fillrandom -format_version=5 -value_size=90 -bloom_bits=10 -num=2000000 -threads=8 -optimize_filters_for_memory -compaction_style=2 -fifo_compaction_max_table_files_size_mb=10000 -fifo_compaction_allow_compaction=false
$ (for FILE in /dev/shm/dbbench.no_optimize/*.sst; do ./sst_dump --file=$FILE --show_properties | grep 'filter block' ; done) | awk '{ t += $4; } END { print t; }'
17063835
$ (for FILE in /dev/shm/dbbench/*.sst; do ./sst_dump --file=$FILE --show_properties | grep 'filter block' ; done) | awk '{ t += $4; } END { print t; }'
17430747
$ #^ 2.1% additional filter storage
$ ./db_bench -db=/dev/shm/dbbench.no_optimize -use_existing_db -benchmarks=readrandom,stats -statistics -bloom_bits=10 -num=2000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=10000 -fifo_compaction_allow_compaction=false -duration=10 -cache_index_and_filter_blocks -cache_size=1000000000
rocksdb.block.cache.index.add COUNT : 33
rocksdb.block.cache.index.bytes.insert COUNT : 8440400
rocksdb.block.cache.filter.add COUNT : 33
rocksdb.block.cache.filter.bytes.insert COUNT : 21087528
rocksdb.bloom.filter.useful COUNT : 4963889
rocksdb.bloom.filter.full.positive COUNT : 1214081
rocksdb.bloom.filter.full.true.positive COUNT : 1161999
$ #^ 1.04 % observed FP rate
$ ./db_bench -db=/dev/shm/dbbench -use_existing_db -benchmarks=readrandom,stats -statistics -bloom_bits=10 -num=2000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=10000 -fifo_compaction_allow_compaction=false -optimize_filters_for_memory -duration=10 -cache_index_and_filter_blocks -cache_size=1000000000
rocksdb.block.cache.index.add COUNT : 33
rocksdb.block.cache.index.bytes.insert COUNT : 8448592
rocksdb.block.cache.filter.add COUNT : 33
rocksdb.block.cache.filter.bytes.insert COUNT : 18220328
rocksdb.bloom.filter.useful COUNT : 5360933
rocksdb.bloom.filter.full.positive COUNT : 1321315
rocksdb.bloom.filter.full.true.positive COUNT : 1262999
$ #^ 1.08 % observed FP rate, 13.6% less memory usage for filters
(Due to specific key density, this example tends to generate filters that are "worse than average" for internal fragmentation. "Better than average" cases can show little or no improvement.)
Pull Request resolved: https://github.com/facebook/rocksdb/pull/6427
Test Plan: unit test added, 'make check' with gcc, clang and valgrind
Reviewed By: siying
Differential Revision: D22124374
Pulled By: pdillinger
fbshipit-source-id: f3e3aa152f9043ddf4fae25799e76341d0d8714e
5 years ago
|
|
|
"optimize_filters_for_memory=true;"
|
|
|
|
"index_block_restart_interval=4;"
|
|
|
|
"filter_policy=bloomfilter:4:true;whole_key_filtering=1;"
|
Account Bloom/Ribbon filter construction memory in global memory limit (#9073)
Summary:
Note: This PR is the 4th part of a bigger PR stack (https://github.com/facebook/rocksdb/pull/9073) and will rebase/merge only after the first three PRs (https://github.com/facebook/rocksdb/pull/9070, https://github.com/facebook/rocksdb/pull/9071, https://github.com/facebook/rocksdb/pull/9130) merge.
**Context:**
Similar to https://github.com/facebook/rocksdb/pull/8428, this PR is to track memory usage during (new) Bloom Filter (i.e,FastLocalBloom) and Ribbon Filter (i.e, Ribbon128) construction, moving toward the goal of [single global memory limit using block cache capacity](https://github.com/facebook/rocksdb/wiki/Projects-Being-Developed#improving-memory-efficiency). It also constrains the size of the banding portion of Ribbon Filter during construction by falling back to Bloom Filter if that banding is, at some point, larger than the available space in the cache under `LRUCacheOptions::strict_capacity_limit=true`.
The option to turn on this feature is `BlockBasedTableOptions::reserve_table_builder_memory = true` which by default is set to `false`. We [decided](https://github.com/facebook/rocksdb/pull/9073#discussion_r741548409) not to have separate option for separate memory user in table building therefore their memory accounting are all bundled under one general option.
**Summary:**
- Reserved/released cache for creation/destruction of three main memory users with the passed-in `FilterBuildingContext::cache_res_mgr` during filter construction:
- hash entries (i.e`hash_entries`.size(), we bucket-charge hash entries during insertion for performance),
- banding (Ribbon Filter only, `bytes_coeff_rows` +`bytes_result_rows` + `bytes_backtrack`),
- final filter (i.e, `mutable_buf`'s size).
- Implementation details: in order to use `CacheReservationManager::CacheReservationHandle` to account final filter's memory, we have to store the `CacheReservationManager` object and `CacheReservationHandle` for final filter in `XXPH3BitsFilterBuilder` as well as explicitly delete the filter bits builder when done with the final filter in block based table.
- Added option fo run `filter_bench` with this memory reservation feature
Pull Request resolved: https://github.com/facebook/rocksdb/pull/9073
Test Plan:
- Added new tests in `db_bloom_filter_test` to verify filter construction peak cache reservation under combination of `BlockBasedTable::Rep::FilterType` (e.g, `kFullFilter`, `kPartitionedFilter`), `BloomFilterPolicy::Mode`(e.g, `kFastLocalBloom`, `kStandard128Ribbon`, `kDeprecatedBlock`) and `BlockBasedTableOptions::reserve_table_builder_memory`
- To address the concern for slow test: tests with memory reservation under `kFullFilter` + `kStandard128Ribbon` and `kPartitionedFilter` take around **3000 - 6000 ms** and others take around **1500 - 2000 ms**, in total adding **20000 - 25000 ms** to the test suit running locally
- Added new test in `bloom_test` to verify Ribbon Filter fallback on large banding in FullFilter
- Added test in `filter_bench` to verify that this feature does not significantly slow down Bloom/Ribbon Filter construction speed. Local result averaged over **20** run as below:
- FastLocalBloom
- baseline `./filter_bench -impl=2 -quick -runs 20 | grep 'Build avg'`:
- **Build avg ns/key: 29.56295** (DEBUG_LEVEL=1), **29.98153** (DEBUG_LEVEL=0)
- new feature (expected to be similar as above)`./filter_bench -impl=2 -quick -runs 20 -reserve_table_builder_memory=true | grep 'Build avg'`:
- **Build avg ns/key: 30.99046** (DEBUG_LEVEL=1), **30.48867** (DEBUG_LEVEL=0)
- new feature of RibbonFilter with fallback (expected to be similar as above) `./filter_bench -impl=2 -quick -runs 20 -reserve_table_builder_memory=true -strict_capacity_limit=true | grep 'Build avg'` :
- **Build avg ns/key: 31.146975** (DEBUG_LEVEL=1), **30.08165** (DEBUG_LEVEL=0)
- Ribbon128
- baseline `./filter_bench -impl=3 -quick -runs 20 | grep 'Build avg'`:
- **Build avg ns/key: 129.17585** (DEBUG_LEVEL=1), **130.5225** (DEBUG_LEVEL=0)
- new feature (expected to be similar as above) `./filter_bench -impl=3 -quick -runs 20 -reserve_table_builder_memory=true | grep 'Build avg' `:
- **Build avg ns/key: 131.61645** (DEBUG_LEVEL=1), **132.98075** (DEBUG_LEVEL=0)
- new feature of RibbonFilter with fallback (expected to be a lot faster than above due to fallback) `./filter_bench -impl=3 -quick -runs 20 -reserve_table_builder_memory=true -strict_capacity_limit=true | grep 'Build avg'` :
- **Build avg ns/key: 52.032965** (DEBUG_LEVEL=1), **52.597825** (DEBUG_LEVEL=0)
- And the warning message of `"Cache reservation for Ribbon filter banding failed due to cache full"` is indeed logged to console.
Reviewed By: pdillinger
Differential Revision: D31991348
Pulled By: hx235
fbshipit-source-id: 9336b2c60f44d530063da518ceaf56dac5f9df8e
3 years ago
|
|
|
"reserve_table_builder_memory=false;"
|
|
|
|
"format_version=1;"
|
|
|
|
"hash_index_allow_collision=false;"
|
|
|
|
"verify_compression=true;read_amp_bytes_per_bit=0;"
|
|
|
|
"enable_index_compression=false;"
|
|
|
|
"block_align=true;"
|
|
|
|
"max_auto_readahead_size=0;"
|
|
|
|
"prepopulate_block_cache=kDisable",
|
|
|
|
new_bbto));
|
|
|
|
|
|
|
|
ASSERT_EQ(unset_bytes_base,
|
|
|
|
NumUnsetBytes(new_bbto_ptr, sizeof(BlockBasedTableOptions),
|
|
|
|
kBbtoExcluded));
|
|
|
|
|
|
|
|
ASSERT_TRUE(new_bbto->block_cache.get() != nullptr);
|
|
|
|
ASSERT_TRUE(new_bbto->block_cache_compressed.get() != nullptr);
|
|
|
|
ASSERT_TRUE(new_bbto->filter_policy.get() != nullptr);
|
|
|
|
|
|
|
|
bbto->~BlockBasedTableOptions();
|
|
|
|
new_bbto->~BlockBasedTableOptions();
|
|
|
|
|
|
|
|
delete[] bbto_ptr;
|
|
|
|
delete[] new_bbto_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the test fails, likely a new option is added to DBOptions
|
|
|
|
// but it cannot be set through GetDBOptionsFromString(), or the test is not
|
|
|
|
// updated accordingly.
|
|
|
|
// After adding an option, we need to make sure it is settable by
|
|
|
|
// GetDBOptionsFromString() and add the option to the input string passed to
|
|
|
|
// DBOptionsFromString()in this test.
|
|
|
|
// If it is a complicated type, you also need to add the field to
|
|
|
|
// kDBOptionsExcluded, and maybe add customized verification for it.
|
|
|
|
TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
|
|
|
|
const OffsetGap kDBOptionsExcluded = {
|
|
|
|
{offsetof(struct DBOptions, env), sizeof(Env*)},
|
|
|
|
{offsetof(struct DBOptions, rate_limiter),
|
|
|
|
sizeof(std::shared_ptr<RateLimiter>)},
|
|
|
|
{offsetof(struct DBOptions, sst_file_manager),
|
|
|
|
sizeof(std::shared_ptr<SstFileManager>)},
|
|
|
|
{offsetof(struct DBOptions, info_log), sizeof(std::shared_ptr<Logger>)},
|
|
|
|
{offsetof(struct DBOptions, statistics),
|
|
|
|
sizeof(std::shared_ptr<Statistics>)},
|
|
|
|
{offsetof(struct DBOptions, db_paths), sizeof(std::vector<DbPath>)},
|
|
|
|
{offsetof(struct DBOptions, db_log_dir), sizeof(std::string)},
|
|
|
|
{offsetof(struct DBOptions, wal_dir), sizeof(std::string)},
|
|
|
|
{offsetof(struct DBOptions, write_buffer_manager),
|
|
|
|
sizeof(std::shared_ptr<WriteBufferManager>)},
|
|
|
|
{offsetof(struct DBOptions, listeners),
|
|
|
|
sizeof(std::vector<std::shared_ptr<EventListener>>)},
|
|
|
|
{offsetof(struct DBOptions, row_cache), sizeof(std::shared_ptr<Cache>)},
|
|
|
|
{offsetof(struct DBOptions, wal_filter), sizeof(const WalFilter*)},
|
|
|
|
{offsetof(struct DBOptions, file_checksum_gen_factory),
|
|
|
|
sizeof(std::shared_ptr<FileChecksumGenFactory>)},
|
|
|
|
{offsetof(struct DBOptions, db_host_id), sizeof(std::string)},
|
|
|
|
{offsetof(struct DBOptions, checksum_handoff_file_types),
|
|
|
|
sizeof(FileTypeSet)},
|
|
|
|
{offsetof(struct DBOptions, compaction_service),
|
|
|
|
sizeof(std::shared_ptr<CompactionService>)},
|
|
|
|
};
|
|
|
|
|
|
|
|
char* options_ptr = new char[sizeof(DBOptions)];
|
|
|
|
|
|
|
|
// Count padding bytes by setting all bytes in the memory to a special char,
|
|
|
|
// copy a well constructed struct to this memory and see how many special
|
|
|
|
// bytes left.
|
|
|
|
DBOptions* options = new (options_ptr) DBOptions();
|
|
|
|
FillWithSpecialChar(options_ptr, sizeof(DBOptions), kDBOptionsExcluded);
|
|
|
|
// It based on the behavior of compiler that padding bytes are not changed
|
|
|
|
// when copying the struct. It's prone to failure when compiler behavior
|
|
|
|
// changes. We verify there is unset bytes to detect the case.
|
|
|
|
*options = DBOptions();
|
|
|
|
int unset_bytes_base =
|
|
|
|
NumUnsetBytes(options_ptr, sizeof(DBOptions), kDBOptionsExcluded);
|
|
|
|
ASSERT_GT(unset_bytes_base, 0);
|
|
|
|
options->~DBOptions();
|
|
|
|
|
|
|
|
options = new (options_ptr) DBOptions();
|
|
|
|
FillWithSpecialChar(options_ptr, sizeof(DBOptions), kDBOptionsExcluded);
|
|
|
|
|
|
|
|
char* new_options_ptr = new char[sizeof(DBOptions)];
|
|
|
|
DBOptions* new_options = new (new_options_ptr) DBOptions();
|
|
|
|
FillWithSpecialChar(new_options_ptr, sizeof(DBOptions), kDBOptionsExcluded);
|
|
|
|
|
|
|
|
// Need to update the option string if a new option is added.
|
|
|
|
ASSERT_OK(
|
|
|
|
GetDBOptionsFromString(*options,
|
|
|
|
"wal_bytes_per_sync=4295048118;"
|
|
|
|
"delete_obsolete_files_period_micros=4294967758;"
|
|
|
|
"WAL_ttl_seconds=4295008036;"
|
|
|
|
"WAL_size_limit_MB=4295036161;"
|
|
|
|
"max_write_batch_group_size_bytes=1048576;"
|
|
|
|
"wal_dir=path/to/wal_dir;"
|
|
|
|
"db_write_buffer_size=2587;"
|
|
|
|
"max_subcompactions=64330;"
|
|
|
|
"table_cache_numshardbits=28;"
|
|
|
|
"max_open_files=72;"
|
|
|
|
"max_file_opening_threads=35;"
|
|
|
|
"max_background_jobs=8;"
|
|
|
|
"base_background_compactions=3;"
|
|
|
|
"max_background_compactions=33;"
|
|
|
|
"use_fsync=true;"
|
|
|
|
"use_adaptive_mutex=false;"
|
|
|
|
"max_total_wal_size=4295005604;"
|
|
|
|
"compaction_readahead_size=0;"
|
|
|
|
"new_table_reader_for_compaction_inputs=false;"
|
|
|
|
"keep_log_file_num=4890;"
|
|
|
|
"skip_stats_update_on_db_open=false;"
|
Add an option to prevent DB::Open() from querying sizes of all sst files (#6353)
Summary:
When paranoid_checks is on, DBImpl::CheckConsistency() iterates over all sst files and calls Env::GetFileSize() for each of them. As far as I could understand, this is pretty arbitrary and doesn't affect correctness - if filesystem doesn't corrupt fsynced files, the file sizes will always match; if it does, it may as well corrupt contents as well as sizes, and rocksdb doesn't check contents on open.
If there are thousands of sst files, getting all their sizes takes a while. If, on top of that, Env is overridden to use some remote storage instead of local filesystem, it can be *really* slow and overload the remote storage service. This PR adds an option to not do GetFileSize(); instead it does GetChildren() for parent directory to check that all the expected sst files are at least present, but doesn't check their sizes.
We can't just disable paranoid_checks instead because paranoid_checks do a few other important things: make the DB read-only on write errors, print error messages on read errors, etc.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/6353
Test Plan: ran the added sanity check unit test. Will try it out in a LogDevice test cluster where the GetFileSize() calls are causing a lot of trouble.
Differential Revision: D19656425
Pulled By: al13n321
fbshipit-source-id: c2c421b367633033760d1f56747bad206d1fbf82
5 years ago
|
|
|
"skip_checking_sst_file_sizes_on_db_open=false;"
|
|
|
|
"max_manifest_file_size=4295009941;"
|
|
|
|
"db_log_dir=path/to/db_log_dir;"
|
|
|
|
"skip_log_error_on_recovery=true;"
|
|
|
|
"writable_file_max_buffer_size=1048576;"
|
|
|
|
"paranoid_checks=true;"
|
|
|
|
"flush_verify_memtable_count=true;"
|
|
|
|
"track_and_verify_wals_in_manifest=true;"
|
|
|
|
"is_fd_close_on_exec=false;"
|
|
|
|
"bytes_per_sync=4295013613;"
|
Optionally wait on bytes_per_sync to smooth I/O (#5183)
Summary:
The existing implementation does not guarantee bytes reach disk every `bytes_per_sync` when writing SST files, or every `wal_bytes_per_sync` when writing WALs. This can cause confusing behavior for users who enable this feature to avoid large syncs during flush and compaction, but then end up hitting them anyways.
My understanding of the existing behavior is we used `sync_file_range` with `SYNC_FILE_RANGE_WRITE` to submit ranges for async writeback, such that we could continue processing the next range of bytes while that I/O is happening. I believe we can preserve that benefit while also limiting how far the processing can get ahead of the I/O, which prevents huge syncs from happening when the file finishes.
Consider this `sync_file_range` usage: `sync_file_range(fd_, 0, static_cast<off_t>(offset + nbytes), SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE)`. Expanding the range to start at 0 and adding the `SYNC_FILE_RANGE_WAIT_BEFORE` flag causes any pending writeback (like from a previous call to `sync_file_range`) to finish before it proceeds to submit the latest `nbytes` for writeback. The latest `nbytes` are still written back asynchronously, unless processing exceeds I/O speed, in which case the following `sync_file_range` will need to wait on it.
There is a second change in this PR to use `fdatasync` when `sync_file_range` is unavailable (determined statically) or has some known problem with the underlying filesystem (determined dynamically).
The above two changes only apply when the user enables a new option, `strict_bytes_per_sync`.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/5183
Differential Revision: D14953553
Pulled By: siying
fbshipit-source-id: 445c3862e019fb7b470f9c7f314fc231b62706e9
6 years ago
|
|
|
"strict_bytes_per_sync=true;"
|
|
|
|
"enable_thread_tracking=false;"
|
|
|
|
"recycle_log_file_num=0;"
|
|
|
|
"create_missing_column_families=true;"
|
|
|
|
"log_file_time_to_roll=3097;"
|
|
|
|
"max_background_flushes=35;"
|
|
|
|
"create_if_missing=false;"
|
|
|
|
"error_if_exists=true;"
|
|
|
|
"delayed_write_rate=4294976214;"
|
|
|
|
"manifest_preallocation_size=1222;"
|
|
|
|
"allow_mmap_writes=false;"
|
|
|
|
"stats_dump_period_sec=70127;"
|
|
|
|
"stats_persist_period_sec=54321;"
|
|
|
|
"persist_stats_to_disk=true;"
|
|
|
|
"stats_history_buffer_size=14159;"
|
|
|
|
"allow_fallocate=true;"
|
|
|
|
"allow_mmap_reads=false;"
|
|
|
|
"use_direct_reads=false;"
|
|
|
|
"use_direct_io_for_flush_and_compaction=false;"
|
|
|
|
"max_log_file_size=4607;"
|
|
|
|
"random_access_max_buffer_size=1048576;"
|
|
|
|
"advise_random_on_open=true;"
|
|
|
|
"fail_if_options_file_error=false;"
|
|
|
|
"enable_pipelined_write=false;"
|
|
|
|
"unordered_write=false;"
|
|
|
|
"allow_concurrent_memtable_write=true;"
|
|
|
|
"wal_recovery_mode=kPointInTimeRecovery;"
|
|
|
|
"enable_write_thread_adaptive_yield=true;"
|
|
|
|
"write_thread_slow_yield_usec=5;"
|
|
|
|
"write_thread_max_yield_usec=1000;"
|
|
|
|
"access_hint_on_compaction_start=NONE;"
|
|
|
|
"info_log_level=DEBUG_LEVEL;"
|
|
|
|
"dump_malloc_stats=false;"
|
|
|
|
"allow_2pc=false;"
|
|
|
|
"avoid_flush_during_recovery=false;"
|
|
|
|
"avoid_flush_during_shutdown=false;"
|
|
|
|
"allow_ingest_behind=false;"
|
Added support for differential snapshots
Summary:
The motivation for this PR is to add to RocksDB support for differential (incremental) snapshots, as snapshot of the DB changes between two points in time (one can think of it as diff between to sequence numbers, or the diff D which can be thought of as an SST file or just set of KVs that can be applied to sequence number S1 to get the database to the state at sequence number S2).
This feature would be useful for various distributed storages layers built on top of RocksDB, as it should help reduce resources (time and network bandwidth) needed to recover and rebuilt DB instances as replicas in the context of distributed storages.
From the API standpoint that would like client app requesting iterator between (start seqnum) and current DB state, and reading the "diff".
This is a very draft PR for initial review in the discussion on the approach, i'm going to rework some parts and keep updating the PR.
For now, what's done here according to initial discussions:
Preserving deletes:
- We want to be able to optionally preserve recent deletes for some defined period of time, so that if a delete came in recently and might need to be included in the next incremental snapshot it would't get dropped by a compaction. This is done by adding new param to Options (preserve deletes flag) and new variable to DB Impl where we keep track of the sequence number after which we don't want to drop tombstones, even if they are otherwise eligible for deletion.
- I also added a new API call for clients to be able to advance this cutoff seqnum after which we drop deletes; i assume it's more flexible to let clients control this, since otherwise we'd need to keep some kind of timestamp < -- > seqnum mapping inside the DB, which sounds messy and painful to support. Clients could make use of it by periodically calling GetLatestSequenceNumber(), noting the timestamp, doing some calculation and figuring out by how much we need to advance the cutoff seqnum.
- Compaction codepath in compaction_iterator.cc has been modified to avoid dropping tombstones with seqnum > cutoff seqnum.
Iterator changes:
- couple params added to ReadOptions, to optionally allow client to request internal keys instead of user keys (so that client can get the latest value of a key, be it delete marker or a put), as well as min timestamp and min seqnum.
TableCache changes:
- I modified table_cache code to be able to quickly exclude SST files from iterators heep if creation_time on the file is less then iter_start_ts as passed in ReadOptions. That would help a lot in some DB settings (like reading very recent data only or using FIFO compactions), but not so much for universal compaction with more or less long iterator time span.
What's left:
- Still looking at how to best plug that inside DBIter codepath. So far it seems that FindNextUserKeyInternal only parses values as UserKeys, and iter->key() call generally returns user key. Can we add new API to DBIter as internal_key(), and modify this internal method to optionally set saved_key_ to point to the full internal key? I don't need to store actual seqnum there, but I do need to store type.
Closes https://github.com/facebook/rocksdb/pull/2999
Differential Revision: D6175602
Pulled By: mikhail-antonov
fbshipit-source-id: c779a6696ee2d574d86c69cec866a3ae095aa900
7 years ago
|
|
|
"preserve_deletes=false;"
|
|
|
|
"concurrent_prepare=false;"
|
|
|
|
"two_write_queues=false;"
|
|
|
|
"manual_wal_flush=false;"
|
|
|
|
"seq_per_batch=false;"
|
|
|
|
"atomic_flush=false;"
|
|
|
|
"avoid_unnecessary_blocking_io=false;"
|
|
|
|
"log_readahead_size=0;"
|
|
|
|
"write_dbid_to_manifest=false;"
|
|
|
|
"best_efforts_recovery=false;"
|
|
|
|
"max_bgerror_resume_count=2;"
|
|
|
|
"bgerror_resume_retry_interval=1000000"
|
|
|
|
"db_host_id=hostname;"
|
|
|
|
"lowest_used_cache_tier=kNonVolatileBlockTier;"
|
|
|
|
"allow_data_in_errors=false",
|
|
|
|
new_options));
|
|
|
|
|
|
|
|
ASSERT_EQ(unset_bytes_base, NumUnsetBytes(new_options_ptr, sizeof(DBOptions),
|
|
|
|
kDBOptionsExcluded));
|
|
|
|
|
|
|
|
options->~DBOptions();
|
|
|
|
new_options->~DBOptions();
|
|
|
|
|
|
|
|
delete[] options_ptr;
|
|
|
|
delete[] new_options_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T1, typename T2>
|
|
|
|
inline int offset_of(T1 T2::*member) {
|
|
|
|
static T2 obj;
|
|
|
|
return int(size_t(&(obj.*member)) - size_t(&obj));
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the test fails, likely a new option is added to ColumnFamilyOptions
|
|
|
|
// but it cannot be set through GetColumnFamilyOptionsFromString(), or the
|
|
|
|
// test is not updated accordingly.
|
|
|
|
// After adding an option, we need to make sure it is settable by
|
|
|
|
// GetColumnFamilyOptionsFromString() and add the option to the input
|
|
|
|
// string passed to GetColumnFamilyOptionsFromString()in this test.
|
|
|
|
// If it is a complicated type, you also need to add the field to
|
|
|
|
// kColumnFamilyOptionsExcluded, and maybe add customized verification
|
|
|
|
// for it.
|
|
|
|
TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
|
|
|
|
// options in the excluded set need to appear in the same order as in
|
|
|
|
// ColumnFamilyOptions.
|
|
|
|
const OffsetGap kColumnFamilyOptionsExcluded = {
|
|
|
|
{offset_of(&ColumnFamilyOptions::inplace_callback),
|
|
|
|
sizeof(UpdateStatus(*)(char*, uint32_t*, Slice, std::string*))},
|
|
|
|
{offset_of(
|
|
|
|
&ColumnFamilyOptions::memtable_insert_with_hint_prefix_extractor),
|
|
|
|
sizeof(std::shared_ptr<const SliceTransform>)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::compression_per_level),
|
|
|
|
sizeof(std::vector<CompressionType>)},
|
|
|
|
{offset_of(
|
|
|
|
&ColumnFamilyOptions::max_bytes_for_level_multiplier_additional),
|
|
|
|
sizeof(std::vector<int>)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::memtable_factory),
|
|
|
|
sizeof(std::shared_ptr<MemTableRepFactory>)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::table_properties_collector_factories),
|
|
|
|
sizeof(ColumnFamilyOptions::TablePropertiesCollectorFactories)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::comparator), sizeof(Comparator*)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::merge_operator),
|
|
|
|
sizeof(std::shared_ptr<MergeOperator>)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::compaction_filter),
|
|
|
|
sizeof(const CompactionFilter*)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::compaction_filter_factory),
|
|
|
|
sizeof(std::shared_ptr<CompactionFilterFactory>)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::prefix_extractor),
|
|
|
|
sizeof(std::shared_ptr<const SliceTransform>)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::snap_refresh_nanos), sizeof(uint64_t)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::table_factory),
|
|
|
|
sizeof(std::shared_ptr<TableFactory>)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::cf_paths), sizeof(std::vector<DbPath>)},
|
Concurrent task limiter for compaction thread control (#4332)
Summary:
The PR is targeting to resolve the issue of:
https://github.com/facebook/rocksdb/issues/3972#issue-330771918
We have a rocksdb created with leveled-compaction with multiple column families (CFs), some of CFs are using HDD to store big and less frequently accessed data and others are using SSD.
When there are continuously write traffics going on to all CFs, the compaction thread pool is mostly occupied by those slow HDD compactions, which blocks fully utilize SSD bandwidth.
Since atomic write and transaction is needed across CFs, so splitting it to multiple rocksdb instance is not an option for us.
With the compaction thread control, we got 30%+ HDD write throughput gain, and also a lot smooth SSD write since less write stall happening.
ConcurrentTaskLimiter can be shared with multi-CFs across rocksdb instances, so the feature does not only work for multi-CFs scenarios, but also for multi-rocksdbs scenarios, who need disk IO resource control per tenant.
The usage is straight forward:
e.g.:
//
// Enable compaction thread limiter thru ColumnFamilyOptions
//
std::shared_ptr<ConcurrentTaskLimiter> ctl(NewConcurrentTaskLimiter("foo_limiter", 4));
Options options;
ColumnFamilyOptions cf_opt(options);
cf_opt.compaction_thread_limiter = ctl;
...
//
// Compaction thread limiter can be tuned or disabled on-the-fly
//
ctl->SetMaxOutstandingTask(12); // enlarge to 12 tasks
...
ctl->ResetMaxOutstandingTask(); // disable (bypass) thread limiter
ctl->SetMaxOutstandingTask(-1); // Same as above
...
ctl->SetMaxOutstandingTask(0); // full throttle (0 task)
//
// Sharing compaction thread limiter among CFs (to resolve multiple storage perf issue)
//
std::shared_ptr<ConcurrentTaskLimiter> ctl_ssd(NewConcurrentTaskLimiter("ssd_limiter", 8));
std::shared_ptr<ConcurrentTaskLimiter> ctl_hdd(NewConcurrentTaskLimiter("hdd_limiter", 4));
Options options;
ColumnFamilyOptions cf_opt_ssd1(options);
ColumnFamilyOptions cf_opt_ssd2(options);
ColumnFamilyOptions cf_opt_hdd1(options);
ColumnFamilyOptions cf_opt_hdd2(options);
ColumnFamilyOptions cf_opt_hdd3(options);
// SSD CFs
cf_opt_ssd1.compaction_thread_limiter = ctl_ssd;
cf_opt_ssd2.compaction_thread_limiter = ctl_ssd;
// HDD CFs
cf_opt_hdd1.compaction_thread_limiter = ctl_hdd;
cf_opt_hdd2.compaction_thread_limiter = ctl_hdd;
cf_opt_hdd3.compaction_thread_limiter = ctl_hdd;
...
//
// The limiter is disabled by default (or set to nullptr explicitly)
//
Options options;
ColumnFamilyOptions cf_opt(options);
cf_opt.compaction_thread_limiter = nullptr;
Pull Request resolved: https://github.com/facebook/rocksdb/pull/4332
Differential Revision: D13226590
Pulled By: siying
fbshipit-source-id: 14307aec55b8bd59c8223d04aa6db3c03d1b0c1d
6 years ago
|
|
|
{offset_of(&ColumnFamilyOptions::compaction_thread_limiter),
|
|
|
|
sizeof(std::shared_ptr<ConcurrentTaskLimiter>)},
|
|
|
|
{offset_of(&ColumnFamilyOptions::sst_partitioner_factory),
|
|
|
|
sizeof(std::shared_ptr<SstPartitionerFactory>)},
|
|
|
|
};
|
|
|
|
|
|
|
|
char* options_ptr = new char[sizeof(ColumnFamilyOptions)];
|
|
|
|
|
|
|
|
// Count padding bytes by setting all bytes in the memory to a special char,
|
|
|
|
// copy a well constructed struct to this memory and see how many special
|
|
|
|
// bytes left.
|
|
|
|
FillWithSpecialChar(options_ptr, sizeof(ColumnFamilyOptions),
|
|
|
|
kColumnFamilyOptionsExcluded);
|
|
|
|
|
Limit buffering for collecting samples for compression dictionary (#7970)
Summary:
For dictionary compression, we need to collect some representative samples of the data to be compressed, which we use to either generate or train (when `CompressionOptions::zstd_max_train_bytes > 0`) a dictionary. Previously, the strategy was to buffer all the data blocks during flush, and up to the target file size during compaction. That strategy allowed us to randomly pick samples from as wide a range as possible that'd be guaranteed to land in a single output file.
However, some users try to make huge files in memory-constrained environments, where this strategy can cause OOM. This PR introduces an option, `CompressionOptions::max_dict_buffer_bytes`, that limits how much data blocks are buffered before we switch to unbuffered mode (which means creating the per-SST dictionary, writing out the buffered data, and compressing/writing new blocks as soon as they are built). It is not strict as we currently buffer more than just data blocks -- also keys are buffered. But it does make a step towards giving users predictable memory usage.
Related changes include:
- Changed sampling for dictionary compression to select unique data blocks when there is limited availability of data blocks
- Made use of `BlockBuilder::SwapAndReset()` to save an allocation+memcpy when buffering data blocks for building a dictionary
- Changed `ParseBoolean()` to accept an input containing characters after the boolean. This is necessary since, with this PR, a value for `CompressionOptions::enabled` is no longer necessarily the final component in the `CompressionOptions` string.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/7970
Test Plan:
- updated `CompressionOptions` unit tests to verify limit is respected (to the extent expected in the current implementation) in various scenarios of flush/compaction to bottommost/non-bottommost level
- looked at jemalloc heap profiles right before and after switching to unbuffered mode during flush/compaction. Verified memory usage in buffering is proportional to the limit set.
Reviewed By: pdillinger
Differential Revision: D26467994
Pulled By: ajkr
fbshipit-source-id: 3da4ef9fba59974e4ef40e40c01611002c861465
4 years ago
|
|
|
// Invoke a user-defined constructor in the hope that it does not overwrite
|
|
|
|
// padding bytes. Note that previously we relied on the implicitly-defined
|
|
|
|
// copy-assignment operator (i.e., `*options = ColumnFamilyOptions();`) here,
|
|
|
|
// which did in fact modify padding bytes.
|
|
|
|
ColumnFamilyOptions* options = new (options_ptr) ColumnFamilyOptions();
|
|
|
|
|
|
|
|
// Deprecatd option which is not initialized. Need to set it to avoid
|
|
|
|
// Valgrind error
|
|
|
|
options->max_mem_compaction_level = 0;
|
|
|
|
|
|
|
|
int unset_bytes_base = NumUnsetBytes(options_ptr, sizeof(ColumnFamilyOptions),
|
|
|
|
kColumnFamilyOptionsExcluded);
|
|
|
|
ASSERT_GT(unset_bytes_base, 0);
|
|
|
|
options->~ColumnFamilyOptions();
|
|
|
|
|
|
|
|
options = new (options_ptr) ColumnFamilyOptions();
|
|
|
|
FillWithSpecialChar(options_ptr, sizeof(ColumnFamilyOptions),
|
|
|
|
kColumnFamilyOptionsExcluded);
|
|
|
|
|
|
|
|
// Following options are not settable through
|
|
|
|
// GetColumnFamilyOptionsFromString():
|
|
|
|
options->rate_limit_delay_max_milliseconds = 33;
|
|
|
|
options->compaction_options_universal = CompactionOptionsUniversal();
|
|
|
|
options->hard_rate_limit = 0;
|
|
|
|
options->soft_rate_limit = 0;
|
|
|
|
options->num_levels = 42; // Initialize options for MutableCF
|
|
|
|
options->purge_redundant_kvs_while_flush = false;
|
|
|
|
options->max_mem_compaction_level = 0;
|
Concurrent task limiter for compaction thread control (#4332)
Summary:
The PR is targeting to resolve the issue of:
https://github.com/facebook/rocksdb/issues/3972#issue-330771918
We have a rocksdb created with leveled-compaction with multiple column families (CFs), some of CFs are using HDD to store big and less frequently accessed data and others are using SSD.
When there are continuously write traffics going on to all CFs, the compaction thread pool is mostly occupied by those slow HDD compactions, which blocks fully utilize SSD bandwidth.
Since atomic write and transaction is needed across CFs, so splitting it to multiple rocksdb instance is not an option for us.
With the compaction thread control, we got 30%+ HDD write throughput gain, and also a lot smooth SSD write since less write stall happening.
ConcurrentTaskLimiter can be shared with multi-CFs across rocksdb instances, so the feature does not only work for multi-CFs scenarios, but also for multi-rocksdbs scenarios, who need disk IO resource control per tenant.
The usage is straight forward:
e.g.:
//
// Enable compaction thread limiter thru ColumnFamilyOptions
//
std::shared_ptr<ConcurrentTaskLimiter> ctl(NewConcurrentTaskLimiter("foo_limiter", 4));
Options options;
ColumnFamilyOptions cf_opt(options);
cf_opt.compaction_thread_limiter = ctl;
...
//
// Compaction thread limiter can be tuned or disabled on-the-fly
//
ctl->SetMaxOutstandingTask(12); // enlarge to 12 tasks
...
ctl->ResetMaxOutstandingTask(); // disable (bypass) thread limiter
ctl->SetMaxOutstandingTask(-1); // Same as above
...
ctl->SetMaxOutstandingTask(0); // full throttle (0 task)
//
// Sharing compaction thread limiter among CFs (to resolve multiple storage perf issue)
//
std::shared_ptr<ConcurrentTaskLimiter> ctl_ssd(NewConcurrentTaskLimiter("ssd_limiter", 8));
std::shared_ptr<ConcurrentTaskLimiter> ctl_hdd(NewConcurrentTaskLimiter("hdd_limiter", 4));
Options options;
ColumnFamilyOptions cf_opt_ssd1(options);
ColumnFamilyOptions cf_opt_ssd2(options);
ColumnFamilyOptions cf_opt_hdd1(options);
ColumnFamilyOptions cf_opt_hdd2(options);
ColumnFamilyOptions cf_opt_hdd3(options);
// SSD CFs
cf_opt_ssd1.compaction_thread_limiter = ctl_ssd;
cf_opt_ssd2.compaction_thread_limiter = ctl_ssd;
// HDD CFs
cf_opt_hdd1.compaction_thread_limiter = ctl_hdd;
cf_opt_hdd2.compaction_thread_limiter = ctl_hdd;
cf_opt_hdd3.compaction_thread_limiter = ctl_hdd;
...
//
// The limiter is disabled by default (or set to nullptr explicitly)
//
Options options;
ColumnFamilyOptions cf_opt(options);
cf_opt.compaction_thread_limiter = nullptr;
Pull Request resolved: https://github.com/facebook/rocksdb/pull/4332
Differential Revision: D13226590
Pulled By: siying
fbshipit-source-id: 14307aec55b8bd59c8223d04aa6db3c03d1b0c1d
6 years ago
|
|
|
options->compaction_filter = nullptr;
|
|
|
|
options->sst_partitioner_factory = nullptr;
|
|
|
|
options->bottommost_temperature = Temperature::kUnknown;
|
|
|
|
|
|
|
|
char* new_options_ptr = new char[sizeof(ColumnFamilyOptions)];
|
|
|
|
ColumnFamilyOptions* new_options =
|
|
|
|
new (new_options_ptr) ColumnFamilyOptions();
|
|
|
|
FillWithSpecialChar(new_options_ptr, sizeof(ColumnFamilyOptions),
|
|
|
|
kColumnFamilyOptionsExcluded);
|
|
|
|
|
|
|
|
// Need to update the option string if a new option is added.
|
|
|
|
ASSERT_OK(GetColumnFamilyOptionsFromString(
|
|
|
|
*options,
|
|
|
|
"compaction_filter_factory=mpudlojcujCompactionFilterFactory;"
|
|
|
|
"table_factory=PlainTable;"
|
|
|
|
"prefix_extractor=rocksdb.CappedPrefix.13;"
|
|
|
|
"comparator=leveldb.BytewiseComparator;"
|
|
|
|
"compression_per_level=kBZip2Compression:kBZip2Compression:"
|
|
|
|
"kBZip2Compression:kNoCompression:kZlibCompression:kBZip2Compression:"
|
|
|
|
"kSnappyCompression;"
|
|
|
|
"max_bytes_for_level_base=986;"
|
|
|
|
"bloom_locality=8016;"
|
|
|
|
"target_file_size_base=4294976376;"
|
|
|
|
"memtable_huge_page_size=2557;"
|
|
|
|
"max_successive_merges=5497;"
|
|
|
|
"max_sequential_skip_in_iterations=4294971408;"
|
|
|
|
"arena_block_size=1893;"
|
|
|
|
"target_file_size_multiplier=35;"
|
|
|
|
"min_write_buffer_number_to_merge=9;"
|
|
|
|
"max_write_buffer_number=84;"
|
|
|
|
"write_buffer_size=1653;"
|
|
|
|
"max_compaction_bytes=64;"
|
|
|
|
"max_bytes_for_level_multiplier=60;"
|
|
|
|
"memtable_factory=SkipListFactory;"
|
|
|
|
"compression=kNoCompression;"
|
Limit buffering for collecting samples for compression dictionary (#7970)
Summary:
For dictionary compression, we need to collect some representative samples of the data to be compressed, which we use to either generate or train (when `CompressionOptions::zstd_max_train_bytes > 0`) a dictionary. Previously, the strategy was to buffer all the data blocks during flush, and up to the target file size during compaction. That strategy allowed us to randomly pick samples from as wide a range as possible that'd be guaranteed to land in a single output file.
However, some users try to make huge files in memory-constrained environments, where this strategy can cause OOM. This PR introduces an option, `CompressionOptions::max_dict_buffer_bytes`, that limits how much data blocks are buffered before we switch to unbuffered mode (which means creating the per-SST dictionary, writing out the buffered data, and compressing/writing new blocks as soon as they are built). It is not strict as we currently buffer more than just data blocks -- also keys are buffered. But it does make a step towards giving users predictable memory usage.
Related changes include:
- Changed sampling for dictionary compression to select unique data blocks when there is limited availability of data blocks
- Made use of `BlockBuilder::SwapAndReset()` to save an allocation+memcpy when buffering data blocks for building a dictionary
- Changed `ParseBoolean()` to accept an input containing characters after the boolean. This is necessary since, with this PR, a value for `CompressionOptions::enabled` is no longer necessarily the final component in the `CompressionOptions` string.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/7970
Test Plan:
- updated `CompressionOptions` unit tests to verify limit is respected (to the extent expected in the current implementation) in various scenarios of flush/compaction to bottommost/non-bottommost level
- looked at jemalloc heap profiles right before and after switching to unbuffered mode during flush/compaction. Verified memory usage in buffering is proportional to the limit set.
Reviewed By: pdillinger
Differential Revision: D26467994
Pulled By: ajkr
fbshipit-source-id: 3da4ef9fba59974e4ef40e40c01611002c861465
4 years ago
|
|
|
"compression_opts=5:6:7:8:9:10:true:11;"
|
|
|
|
"bottommost_compression_opts=4:5:6:7:8:9:true:10;"
|
|
|
|
"bottommost_compression=kDisableCompressionOption;"
|
|
|
|
"level0_stop_writes_trigger=33;"
|
|
|
|
"num_levels=99;"
|
|
|
|
"level0_slowdown_writes_trigger=22;"
|
|
|
|
"level0_file_num_compaction_trigger=14;"
|
|
|
|
"compaction_filter=urxcqstuwnCompactionFilter;"
|
|
|
|
"soft_rate_limit=530.615385;"
|
|
|
|
"soft_pending_compaction_bytes_limit=0;"
|
|
|
|
"max_write_buffer_number_to_maintain=84;"
|
Refactor trimming logic for immutable memtables (#5022)
Summary:
MyRocks currently sets `max_write_buffer_number_to_maintain` in order to maintain enough history for transaction conflict checking. The effectiveness of this approach depends on the size of memtables. When memtables are small, it may not keep enough history; when memtables are large, this may consume too much memory.
We are proposing a new way to configure memtable list history: by limiting the memory usage of immutable memtables. The new option is `max_write_buffer_size_to_maintain` and it will take precedence over the old `max_write_buffer_number_to_maintain` if they are both set to non-zero values. The new option accounts for the total memory usage of flushed immutable memtables and mutable memtable. When the total usage exceeds the limit, RocksDB may start dropping immutable memtables (which is also called trimming history), starting from the oldest one.
The semantics of the old option actually works both as an upper bound and lower bound. History trimming will start if number of immutable memtables exceeds the limit, but it will never go below (limit-1) due to history trimming.
In order the mimic the behavior with the new option, history trimming will stop if dropping the next immutable memtable causes the total memory usage go below the size limit. For example, assuming the size limit is set to 64MB, and there are 3 immutable memtables with sizes of 20, 30, 30. Although the total memory usage is 80MB > 64MB, dropping the oldest memtable will reduce the memory usage to 60MB < 64MB, so in this case no memtable will be dropped.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/5022
Differential Revision: D14394062
Pulled By: miasantreble
fbshipit-source-id: 60457a509c6af89d0993f988c9b5c2aa9e45f5c5
5 years ago
|
|
|
"max_write_buffer_size_to_maintain=2147483648;"
|
|
|
|
"merge_operator=aabcxehazrMergeOperator;"
|
|
|
|
"memtable_prefix_bloom_size_ratio=0.4642;"
|
|
|
|
"memtable_whole_key_filtering=true;"
|
|
|
|
"memtable_insert_with_hint_prefix_extractor=rocksdb.CappedPrefix.13;"
|
|
|
|
"check_flush_compaction_key_order=false;"
|
|
|
|
"paranoid_file_checks=true;"
|
|
|
|
"force_consistency_checks=true;"
|
|
|
|
"inplace_update_num_locks=7429;"
|
|
|
|
"optimize_filters_for_hits=false;"
|
|
|
|
"level_compaction_dynamic_level_bytes=false;"
|
|
|
|
"inplace_update_support=false;"
|
|
|
|
"compaction_style=kCompactionStyleFIFO;"
|
|
|
|
"compaction_pri=kMinOverlappingRatio;"
|
|
|
|
"hard_pending_compaction_bytes_limit=0;"
|
|
|
|
"disable_auto_compactions=false;"
|
|
|
|
"report_bg_io_stats=true;"
|
|
|
|
"ttl=60;"
|
Periodic Compactions (#5166)
Summary:
Introducing Periodic Compactions.
This feature allows all the files in a CF to be periodically compacted. It could help in catching any corruptions that could creep into the DB proactively as every file is constantly getting re-compacted. And also, of course, it helps to cleanup data older than certain threshold.
- Introduced a new option `periodic_compaction_time` to control how long a file can live without being compacted in a CF.
- This works across all levels.
- The files are put in the same level after going through the compaction. (Related files in the same level are picked up as `ExpandInputstoCleanCut` is used).
- Compaction filters, if any, are invoked as usual.
- A new table property, `file_creation_time`, is introduced to implement this feature. This property is set to the time at which the SST file was created (and that time is given by the underlying Env/OS).
This feature can be enabled on its own, or in conjunction with `ttl`. It is possible to set a different time threshold for the bottom level when used in conjunction with ttl. Since `ttl` works only on 0 to last but one levels, you could set `ttl` to, say, 1 day, and `periodic_compaction_time` to, say, 7 days. Since `ttl < periodic_compaction_time` all files in last but one levels keep getting picked up based on ttl, and almost never based on periodic_compaction_time. The files in the bottom level get picked up for compaction based on `periodic_compaction_time`.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/5166
Differential Revision: D14884441
Pulled By: sagar0
fbshipit-source-id: 408426cbacb409c06386a98632dcf90bfa1bda47
6 years ago
|
|
|
"periodic_compaction_seconds=3600;"
|
|
|
|
"sample_for_compression=0;"
|
|
|
|
"enable_blob_files=true;"
|
|
|
|
"min_blob_size=256;"
|
|
|
|
"blob_file_size=1000000;"
|
|
|
|
"blob_compression_type=kBZip2Compression;"
|
|
|
|
"enable_blob_garbage_collection=true;"
|
|
|
|
"blob_garbage_collection_age_cutoff=0.5;"
|
Make it possible to force the garbage collection of the oldest blob files (#8994)
Summary:
The current BlobDB garbage collection logic works by relocating the valid
blobs from the oldest blob files as they are encountered during compaction,
and cleaning up blob files once they contain nothing but garbage. However,
with sufficiently skewed workloads, it is theoretically possible to end up in a
situation when few or no compactions get scheduled for the SST files that contain
references to the oldest blob files, which can lead to increased space amp due
to the lack of GC.
In order to efficiently handle such workloads, the patch adds a new BlobDB
configuration option called `blob_garbage_collection_force_threshold`,
which signals to BlobDB to schedule targeted compactions for the SST files
that keep alive the oldest batch of blob files if the overall ratio of garbage in
the given blob files meets the threshold *and* all the given blob files are
eligible for GC based on `blob_garbage_collection_age_cutoff`. (For example,
if the new option is set to 0.9, targeted compactions will get scheduled if the
sum of garbage bytes meets or exceeds 90% of the sum of total bytes in the
oldest blob files, assuming all affected blob files are below the age-based cutoff.)
The net result of these targeted compactions is that the valid blobs in the oldest
blob files are relocated and the oldest blob files themselves cleaned up (since
*all* SST files that rely on them get compacted away).
These targeted compactions are similar to periodic compactions in the sense
that they force certain SST files that otherwise would not get picked up to undergo
compaction and also in the sense that instead of merging files from multiple levels,
they target a single file. (Note: such compactions might still include neighboring files
from the same level due to the need of having a "clean cut" boundary but they never
include any files from any other level.)
This functionality is currently only supported with the leveled compaction style
and is inactive by default (since the default value is set to 1.0, i.e. 100%).
Pull Request resolved: https://github.com/facebook/rocksdb/pull/8994
Test Plan: Ran `make check` and tested using `db_bench` and the stress/crash tests.
Reviewed By: riversand963
Differential Revision: D31489850
Pulled By: ltamasi
fbshipit-source-id: 44057d511726a0e2a03c5d9313d7511b3f0c4eab
3 years ago
|
|
|
"blob_garbage_collection_force_threshold=0.75;"
|
|
|
|
"blob_compaction_readahead_size=262144;"
|
|
|
|
"compaction_options_fifo={max_table_files_size=3;allow_"
|
|
|
|
"compaction=false;age_for_warm=1;};",
|
|
|
|
new_options));
|
|
|
|
|
|
|
|
ASSERT_EQ(unset_bytes_base,
|
|
|
|
NumUnsetBytes(new_options_ptr, sizeof(ColumnFamilyOptions),
|
|
|
|
kColumnFamilyOptionsExcluded));
|
|
|
|
|
|
|
|
ColumnFamilyOptions rnd_filled_options = *new_options;
|
|
|
|
|
|
|
|
options->~ColumnFamilyOptions();
|
|
|
|
new_options->~ColumnFamilyOptions();
|
|
|
|
|
|
|
|
delete[] options_ptr;
|
|
|
|
delete[] new_options_ptr;
|
|
|
|
|
|
|
|
// Test copying to mutabable and immutable options and copy back the mutable
|
|
|
|
// part.
|
|
|
|
const OffsetGap kMutableCFOptionsExcluded = {
|
|
|
|
{offset_of(&MutableCFOptions::prefix_extractor),
|
|
|
|
sizeof(std::shared_ptr<const SliceTransform>)},
|
|
|
|
{offset_of(&MutableCFOptions::max_bytes_for_level_multiplier_additional),
|
|
|
|
sizeof(std::vector<int>)},
|
|
|
|
{offset_of(&MutableCFOptions::max_file_size),
|
|
|
|
sizeof(std::vector<uint64_t>)},
|
|
|
|
};
|
|
|
|
|
|
|
|
// For all memory used for options, pre-fill every char. Otherwise, the
|
|
|
|
// padding bytes might be different so that byte-wise comparison doesn't
|
|
|
|
// general equal results even if objects are equal.
|
|
|
|
const char kMySpecialChar = 'x';
|
|
|
|
char* mcfo1_ptr = new char[sizeof(MutableCFOptions)];
|
|
|
|
FillWithSpecialChar(mcfo1_ptr, sizeof(MutableCFOptions),
|
|
|
|
kMutableCFOptionsExcluded, kMySpecialChar);
|
|
|
|
char* mcfo2_ptr = new char[sizeof(MutableCFOptions)];
|
|
|
|
FillWithSpecialChar(mcfo2_ptr, sizeof(MutableCFOptions),
|
|
|
|
kMutableCFOptionsExcluded, kMySpecialChar);
|
|
|
|
|
|
|
|
// A clean column family options is constructed after filling the same special
|
|
|
|
// char as the initial one. So that the padding bytes are the same.
|
|
|
|
char* cfo_clean_ptr = new char[sizeof(ColumnFamilyOptions)];
|
|
|
|
FillWithSpecialChar(cfo_clean_ptr, sizeof(ColumnFamilyOptions),
|
|
|
|
kColumnFamilyOptionsExcluded);
|
|
|
|
rnd_filled_options.num_levels = 66;
|
|
|
|
ColumnFamilyOptions* cfo_clean = new (cfo_clean_ptr) ColumnFamilyOptions();
|
|
|
|
|
|
|
|
MutableCFOptions* mcfo1 =
|
|
|
|
new (mcfo1_ptr) MutableCFOptions(rnd_filled_options);
|
|
|
|
ColumnFamilyOptions cfo_back = BuildColumnFamilyOptions(*cfo_clean, *mcfo1);
|
|
|
|
MutableCFOptions* mcfo2 = new (mcfo2_ptr) MutableCFOptions(cfo_back);
|
|
|
|
|
|
|
|
ASSERT_TRUE(CompareBytes(mcfo1_ptr, mcfo2_ptr, sizeof(MutableCFOptions),
|
|
|
|
kMutableCFOptionsExcluded));
|
|
|
|
|
|
|
|
cfo_clean->~ColumnFamilyOptions();
|
|
|
|
mcfo1->~MutableCFOptions();
|
|
|
|
mcfo2->~MutableCFOptions();
|
|
|
|
delete[] mcfo1_ptr;
|
|
|
|
delete[] mcfo2_ptr;
|
|
|
|
delete[] cfo_clean_ptr;
|
|
|
|
}
|
|
|
|
#endif // !__clang__
|
|
|
|
#endif // OS_LINUX || OS_WIN
|
|
|
|
#endif // !ROCKSDB_LITE
|
|
|
|
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
#ifdef GFLAGS
|
|
|
|
ParseCommandLineFlags(&argc, &argv, true);
|
|
|
|
#endif // GFLAGS
|
|
|
|
return RUN_ALL_TESTS();
|
|
|
|
}
|