Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FilterPolicy API changes for 7.0 #9501

Closed
wants to merge 6 commits into from

Conversation

pdillinger
Copy link
Contributor

@pdillinger pdillinger commented Feb 4, 2022

Summary:

  • Inefficient block-based filter is no longer customizable in the public
    API, though (for now) can still be enabled.
    • Removed deprecated FilterPolicy::CreateFilter() and
      FilterPolicy::KeyMayMatch()
    • Removed rocksdb_filterpolicy_create() from C API
  • Change meaning of nullptr return from GetBuilderWithContext() from "use
    block-based filter" to "generate no filter in this case." This is a
    cleaner solution to the proposal in Support skipping filters in custom FilterPolicy, near-zero bpk #8250.
    • Also, when user specifies bits_per_key < 0.5, we now round this down
      to "no filter" because we expect a filter with >= 80% FP rate is
      unlikely to be worth the CPU cost of accessing it (esp with
      cache_index_and_filter_blocks=1 or partition_filters=1).
    • bits_per_key >= 0.5 and < 1.0 is still rounded up to 1.0 (for 62% FP
      rate)
    • This also gives us some support for configuring filters from OPTIONS
      file as currently saved: filter_policy=rocksdb.BuiltinBloomFilter.
      Opening from such an options file will enable reading filters (an
      improvement) but not writing new ones. (See Customizable follow-up
      below.)
  • Also removed deprecated functions
    • FilterBitsBuilder::CalculateNumEntry()
    • FilterPolicy::GetFilterBitsBuilder()
    • NewExperimentalRibbonFilterPolicy()
  • Remove default implementations of
    • FilterBitsBuilder::EstimateEntriesAdded()
    • FilterBitsBuilder::ApproximateNumEntries()
    • FilterPolicy::GetBuilderWithContext()
  • Remove support for "filter_policy=experimental_ribbon" configuration
    string.
  • Allow "filter_policy=bloomfilter:n" without bool to discourage use of
    block-based filter.

Some pieces for #9389

Likely follow-up (later PRs):

  • Refactoring toward FilterPolicy Customizable, so that we can generate
    filters with same configuration as before when configuring from options
    file.
  • Remove support for user enabling block-based filter (ignore bool use_block_based_builder)
    • Some months after this change, we could even remove read support for
      block-based filter, because it is not critical to DB data
      preservation.
  • Make FilterBitsBuilder::FinishV2 to avoid using FilterBitsBuilder::Finish mess and add support for specifying a
    MemoryAllocator (for cache warming)

Test Plan: A number of obsolete tests deleted and new tests or test
cases added or updated.

Summary:
* Inefficient block-based filter is no longer customizable in the public
API, though (for now) can still be enabled.
  * Removed deprecated FilterPolicy::CreateFilter() and
  FilterPolicy::KeyMayMatch()
  * Removed `rocksdb_filterpolicy_create()` from C API
* Change meaning of nullptr return from GetBuilderWithContext() from "use
block-based filter" to "generate no filter in this case." This is a
cleaner solution to the proposal in facebook#8250.
  * Also, when user specifies bits_per_key < 0.5, we now round this down
  to "no filter" because we expect a filter with >= 80% FP rate is
  unlikely to be worth the CPU cost of accessing it (esp with
  cache_index_and_filter_blocks=1 or partition_filters=1).
  * bits_per_key >= 0.5 and < 1.0 is still rounded up to 1.0 (for 62% FP
  rate)
  * This also gives us some support for configuring filters from OPTIONS
  file as currently saved: `filter_policy=rocksdb.BuiltinBloomFilter`.
  Opening from such an options file will enable reading filters (an
  improvement) but not writing new ones. (See Customizable follow-up
  below.)
* Also removed deprecated functions
  * FilterBitsBuilder::CalculateNumEntry()
  * FilterPolicy::GetFilterBitsBuilder()
  * NewExperimentalRibbonFilterPolicy()
* Remove default implementations of
  * FilterBitsBuilder::EstimateEntriesAdded()
  * FilterBitsBuilder::ApproximateNumEntries()
  * FilterPolicy::GetBuilderWithContext()
* Remove support for "filter_policy=experimental_ribbon" configuration
string.
* Allow "filter_policy=bloomfilter:n" without bool to discourage use of
block-based filter.

Likely follow-up (later PRs):
* Refactoring toward FilterPolicy Customizable, so that we can generate
filters with same configuration as before when configuring from options
file.
* Remove support for user enabling block-based filter (ignore `bool
use_block_based_builder`)
  * Some months after this change, we could even remove read support for
  block-based filter, because it is not critical to DB data
  preservation.
* Make FilterBitsBuilder::FinishV2 to avoid `using
FilterBitsBuilder::Finish` mess and add support for specifying a
MemoryAllocator (for cache warming)

Test Plan: A number of obsolete tests deleted and new tests or test
cases added or updated.
@facebook-github-bot
Copy link
Contributor

@pdillinger has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@@ -399,21 +401,22 @@ TEST_P(FullBloomTest, FilterSize) {
bool some_computed_less_than_denoted = false;
// Note: enforced minimum is 1 bit per key (1000 millibits), and enforced
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seem like we also need to fix this comment as we now drop "the minimum is 1 bit per key (1000 millibits)"

// Abstract base class for RocksDB built-in filter policies.
// Base class for RocksDB built-in filter policies. This can read all
// kinds of built-in filters (for backward compatibility with old
// OPTIONS files) but does not write filters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "write filter" the same as "build filter"? I am more used to using the word "build" led by "FilterBitsBuilder". Is there a technical reason to use "write" instead of "build"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I'll use consistent wording.

Copy link
Contributor

@hx235 hx235 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round review: left 1 minor comment and 2 questions.

: ioptions_(options_),
env_options_(options_),
icomp_(options_.comparator) {
table_options_.filter_policy.reset(filter_policy);
table_options_.filter_policy = std::move(filter_policy);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any subtle reason why we are doing this trick?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

table_options_.filter_policy.reset(filter_policy.get()); would be wrong for shared_ptr, because it would be managed twice (double free). table_options_.filter_policy = filter_policy; would be OK but might unnecessarily increment+decrement the refcount on the shared_ptr (due to two potentially live local copies). std::move essentially guarantees the optimization by ensuring only one live local copy.

I know @mrambacher likes to use const std::shared_ptr& parameters, but in cases like this where the callee will surely (or almost surely) save a copy, I prefer std::shared_ptr because you can pass in a temporary like std::make_shared<X> and likely avoid unnecessary extra increment+decrement the refcount.

Copy link
Contributor

@hx235 hx235 Feb 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline:
For clarification: my original question was intended to ask why we do we add an "indirection" from raw pointer "const FilterPolicy* filter_policy" to the share pointer "table_options_.filter_policy" using a trick of adding another constructor (I realized I quoted the wrong line for my question so probably that's why it missed its point)

Update:
It's not needed in this code but might be needed for future refactoring. If that's the case, I suggest adding the "indirection" in that PR for better context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'll take it out of this one. I just noticed I need to fix some FB lints also.

@@ -930,16 +930,13 @@ TEST_F(OptionsTest, GetBlockBasedTableOptionsFromString) {
new_opt.cache_index_and_filter_blocks);
ASSERT_EQ(table_opt.filter_policy, new_opt.filter_policy);

// unrecognized filter policy config
s = GetBlockBasedTableOptionsFromString(config_options, table_opt,
"cache_index_and_filter_blocks=1;"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing "cache_index_and_filter_blocks=1;" in the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems irrelevant. The cases of

  ASSERT_EQ(table_opt.cache_index_and_filter_blocks,
            new_opt.cache_index_and_filter_blocks);

after

  ASSERT_NOK(GetBlockBasedTableOptionsFromString(...

seem especially useless because they only test that a bad assignment wasn't made. It does not distinguish no assignment from proper assignment before failure. I don't know what's intended.

@@ -1790,8 +1785,8 @@ Status FilterPolicy::CreateFromString(
} else {
bloom_before_level = ParseInt(trim(value.substr(pos + 1)));
}
double bloom_equivalent_bits_per_key =
ParseDouble(trim(value.substr(kRibbonName.size(), pos)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the previous parsing algorithm a bug which should have been ParseDouble( trim(value.substr(kRibbonName.size(), pos - kRibbonName.size())));?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but luckily it's just a code hygiene bug. If you go past the end with substr, it just gives you a string to the end. And ParseDouble seems to stop parsing and return what it has if it hits ':'.

@hx235
Copy link
Contributor

hx235 commented Feb 8, 2022

Second round: left 3 questions

@facebook-github-bot
Copy link
Contributor

@pdillinger has updated the pull request. You must reimport the pull request before landing.

@facebook-github-bot
Copy link
Contributor

@pdillinger has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

Copy link
Contributor

@hx235 hx235 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@facebook-github-bot
Copy link
Contributor

@pdillinger has updated the pull request. You must reimport the pull request before landing.

@facebook-github-bot
Copy link
Contributor

@pdillinger has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

pdillinger added a commit to pdillinger/rocksdb that referenced this pull request Feb 9, 2022
Summary: This change removes the ability to configure the deprecated,
inefficient block-based filter in the public API. Options that would
have enabled it now use "full" (and optionally partitioned) filters.
Existing block-based filters can still be read and used, and a "back
door" way to build them still exists, for testing and in case of trouble.

About the only way this removal would cause an issue for users is if
temporary memory for filter construction greatly increases. In
HISTORY.md we suggest a few possible mitigations: partitioned filters,
smaller SST files, or setting reserve_table_builder_memory=true.

Or users who have customized a FilterPolicy using the
CreateFilter/KeyMayMatch mechanism removed in facebook#9501 will have to upgrade
their code. (It's long past time for people to move to the new
builder/reader customization interface.)

This change also introduces some internal-use-only configuration strings
for testing specific filter implementations while bypassing some
compatibility / intelligence logic. This is intended to hint at a path
toward making FilterPolicy Customizable, but it also gives us a "back
door" way to configure block-based filter.

Test Plan: Unit tests updated. Specifically,

* BlockBasedTableTest.BlockReadCountTest is tweaked to validate the back
door configuration interface and ignoring of `use_block_based_builder`.
* BlockBasedTableTest.TracingGetTest is migrated from testing
block-based filter access pattern to full filter access patter, by
re-ordering some things.
* Options test (pretty self-explanatory)
facebook-github-bot pushed a commit that referenced this pull request Feb 12, 2022
Summary:
This change removes the ability to configure the deprecated,
inefficient block-based filter in the public API. Options that would
have enabled it now use "full" (and optionally partitioned) filters.
Existing block-based filters can still be read and used, and a "back
door" way to build them still exists, for testing and in case of trouble.

About the only way this removal would cause an issue for users is if
temporary memory for filter construction greatly increases. In
HISTORY.md we suggest a few possible mitigations: partitioned filters,
smaller SST files, or setting reserve_table_builder_memory=true.

Or users who have customized a FilterPolicy using the
CreateFilter/KeyMayMatch mechanism removed in #9501 will have to upgrade
their code. (It's long past time for people to move to the new
builder/reader customization interface.)

This change also introduces some internal-use-only configuration strings
for testing specific filter implementations while bypassing some
compatibility / intelligence logic. This is intended to hint at a path
toward making FilterPolicy Customizable, but it also gives us a "back
door" way to configure block-based filter.

Aside: updated db_bench so that -readonly implies -use_existing_db

Pull Request resolved: #9535

Test Plan:
Unit tests updated. Specifically,

* BlockBasedTableTest.BlockReadCountTest is tweaked to validate the back
door configuration interface and ignoring of `use_block_based_builder`.
* BlockBasedTableTest.TracingGetTest is migrated from testing
block-based filter access pattern to full filter access patter, by
re-ordering some things.
* Options test (pretty self-explanatory)

Performance test - create with `./db_bench -db=/dev/shm/rocksdb1 -bloom_bits=10 -cache_index_and_filter_blocks=1 -benchmarks=fillrandom -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=10000 -fifo_compaction_allow_compaction=0` with and without `-use_block_based_filter`, which creates a DB with 21 SST files in L0. Read with `./db_bench -db=/dev/shm/rocksdb1 -readonly -bloom_bits=10 -cache_index_and_filter_blocks=1 -benchmarks=readrandom -num=10000000 -compaction_style=2 -fifo_compaction_max_table_files_size_mb=10000 -fifo_compaction_allow_compaction=0 -duration=30`

Without -use_block_based_filter: readrandom 464 ops/sec, 689280 KB DB
With -use_block_based_filter: readrandom 169 ops/sec, 690996 KB DB
No consistent difference with fillrandom

Reviewed By: jay-zhuang

Differential Revision: D34153871

Pulled By: pdillinger

fbshipit-source-id: 31f4a933c542f8f09aca47fa64aec67832a69738
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants