Skip to content

Fix SstQueryFilter decoding of SelectKeySegmentRange - #15523

Open
Marti2203 wants to merge 1 commit into
meta:mainfrom
Marti2203:sqfc-segment-range-decode
Open

Marti2203 wants to merge 1 commit into
meta:mainfrom
Marti2203:sqfc-segment-range-decode

Conversation

@Marti2203

Copy link
Copy Markdown

Summary

DeserializeFilterInput() in db/experimental.cc read back every
serialized SelectKeySegmentRange as SelectKeySegmentRange(0, 6). This PR
decodes the range from the low 4 bits of the selector byte, as
SerializeFilterInput() encodes it and as the SelectKeySegment case just
above already does.

Root cause

SerializeFilterInput() writes a key segment range as one byte,
(2 << 4) | index, where index is the range's position in the table in the
comment (0 = segments 0-1, 1 = 0-2, 2 = 1-2, ..., 15 = 0-6). Case 2 of
DeserializeFilterInput() compared the whole byte b (0x20 to 0x2f) against
6, 10 and 15 instead of b & 0xf. Since b >= 0x20, it always took the last
branch and returned SelectKeySegmentRange{0, 6}. Only a filter configured
with segments 0-6 round-tripped, and only by chance.

The fix masks b with BitwiseAnd(b, 0xf) before the table lookup. I checked
all 16 encodable ranges against the encoder: start = (to - 1) * to / 2 gives
indices 0 for to = 1, 1-2 for to = 2, 3-5 for to = 3, 6-9 for to = 4,
10-14 for to = 5 and 15 for (0, 6). The existing decode branches invert that
table once they get the low nibble.

Impact

The bug affects the experimental SST query filter API (SstQueryFilterConfigs,
MakeSharedBytewiseMinMaxSQFC / MakeSharedReverseBytewiseMinMaxSQFC) when it
is configured with any SelectKeySegmentRange other than (0, 6). The table
properties collector computes the min/max over the configured segments. But
RangeMayMatch() extracts the query bounds over segments 0-6, so it compares
values from different parts of the key. GetTableFilterForRangeQuery() can
then report that a file cannot match when it holds keys in the range. The
iterator skips that file, and the range query silently returns too few keys.
No error is reported.

Example: one-byte segments, filter on SelectKeySegmentRange(1, 2), and an SST
with keys a1Z and a2Y. The stored min/max over segments 1-2 are 1Z and
2Y. A range query ["a2Y", "a2Z") with the table filter returns nothing, but
the same query without the filter returns a2Y.

Files already written are not affected: the serialized selector byte was
correct, only the reader was wrong. So after this fix, existing filters are
read correctly.

Test plan

New test DBBloomFilterTest.SstQueryFilterKeySegmentRange in
db/db_bloom_filter_test.cc. For each of the 16 key segment ranges with a
compact encoding, it builds a DB with seven one-byte segments, flushes the keys
0000000 and 1111111, and checks two things:

  • a range query whose selected segments fall inside the file's min/max finds
    the key and seeks the file
  • a query above the max is filtered out (no data seek)

Before the fix, the test fails for the 15 ranges other than (0, 6): the
matching key is missing and the file is never seeked. After the fix, it
passes. The existing DBBloomFilterTest.SstQueryFilter and
DBBloomFilterTest.FixedWidthSegments still pass.

cmake --build <build> --target db_bloom_filter_test
./db_bloom_filter_test --gtest_filter='DBBloomFilterTest.SstQueryFilter*:DBBloomFilterTest.FixedWidthSegments'

This issue was found by an LLM-Enhanced Static Analysis Engine and verified manually.

DeserializeFilterInput() compared the whole selector byte (0x20-0x2f)
instead of its low 4 bits when decoding a SelectKeySegmentRange, so every
serialized range was read back as segments 0 to 6. SerializeFilterInput()
stores the range index in the low 4 bits, as the SelectKeySegment case
already decodes. With a range other than 0 to 6, RangeMayMatch() compared
query bounds and stored min/max taken from different key segments, and
GetTableFilterForRangeQuery() could skip SST files containing keys in the
range.

Mask the byte to the low 4 bits before decoding, and add a test that runs a
range query for every key segment range with a compact encoding.
@meta-cla

meta-cla Bot commented Oct 30, 2026

Copy link
Copy Markdown

Hi @Marti2203!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@meta-cla

meta-cla Bot commented Oct 30, 2026

Copy link
Copy Markdown

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@meta-cla meta-cla Bot added the CLA Signed label Oct 30, 2026

This branch has not been deployed

No deployments
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.

1 participant