-
Notifications
You must be signed in to change notification settings - Fork 0
Add RUNTIME_FILTER predicate primitive (Bloom) for MSE runtime filter #196
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
Open
UOETianleZhang
wants to merge
7
commits into
master
Choose a base branch
from
tianzhan/oa-983-phase-1-bloom-membership-predicate
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
9b02b0a
OA-983: Add BLOOM_MEMBERSHIP predicate primitive for MSE runtime filter
70fc63c
Address review on BLOOM_MEMBERSHIP predicate
39826e9
Generalize predicate type: BLOOM_MEMBERSHIP -> RUNTIME_FILTER
8ca8390
Sort imports in BloomRuntimeFilterEvaluatorFactoryTest
aa0e587
Add dictionary-based evaluator for RUNTIME_FILTER predicate
7d3d76f
Clean up dead funnel-type field and umbrella-dispatcher tests
db0960f
Document NULL handling contract for runtime filter (v1)
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
...main/java/org/apache/pinot/common/request/context/predicate/BloomMembershipPredicate.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| /** | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.apache.pinot.common.request.context.predicate; | ||
|
|
||
| import java.util.Objects; | ||
| import org.apache.pinot.common.request.context.ExpressionContext; | ||
|
|
||
|
|
||
| /** | ||
| * Predicate matching values that the carried {@link RuntimeBloomFilter} reports as possibly present. | ||
| * | ||
| * <p>Injected by the MSE runtime filter feature at the leaf-stage filter boundary. Inclusive | ||
| * predicate: a row matches when {@code bloomFilter.mightContain(value)} returns true. False | ||
| * positives are permitted; downstream the actual hash join still performs exact matching.</p> | ||
| * | ||
| * <p>This predicate carries the filter object directly rather than a serialized form. It is built | ||
| * inside the broker / server JVM that runs the join build side and handed to the leaf-stage | ||
| * filter tree via {@code QueryContext} (wired up in a later change). Cross-worker transport is | ||
| * out of scope here.</p> | ||
| */ | ||
| public class BloomMembershipPredicate extends BasePredicate { | ||
|
|
||
| private final RuntimeBloomFilter _bloomFilter; | ||
|
|
||
| public BloomMembershipPredicate(ExpressionContext lhs, RuntimeBloomFilter bloomFilter) { | ||
| super(lhs); | ||
| _bloomFilter = Objects.requireNonNull(bloomFilter, "bloomFilter"); | ||
| } | ||
|
|
||
| @Override | ||
| public Type getType() { | ||
| return Type.BLOOM_MEMBERSHIP; | ||
| } | ||
|
|
||
| public RuntimeBloomFilter getBloomFilter() { | ||
| return _bloomFilter; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) { | ||
| return true; | ||
| } | ||
| if (!(o instanceof BloomMembershipPredicate)) { | ||
| return false; | ||
| } | ||
| BloomMembershipPredicate that = (BloomMembershipPredicate) o; | ||
| // Reference equality on the bloom filter is intentional: two distinct RuntimeBloomFilter | ||
| // instances are conceptually different runtime filters even if they happen to contain the | ||
| // same keys. There is no cheap structural equality on Guava BloomFilter that we want to lean on. | ||
| return Objects.equals(_lhs, that._lhs) && _bloomFilter == that._bloomFilter; | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(_lhs, System.identityHashCode(_bloomFilter)); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return _lhs + " BLOOM_MEMBERSHIP(" + _bloomFilter.getDataType() + ")"; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
...n/src/main/java/org/apache/pinot/common/request/context/predicate/RuntimeBloomFilter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| /** | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.apache.pinot.common.request.context.predicate; | ||
|
|
||
| import com.google.common.hash.BloomFilter; | ||
| import com.google.common.hash.Funnels; | ||
| import org.apache.pinot.spi.data.FieldSpec.DataType; | ||
|
|
||
|
|
||
| /** | ||
| * In-memory Bloom filter built at query time and consumed by {@link BloomMembershipPredicate}. | ||
| * | ||
| * <p>The MSE runtime builds one of these from the hash-join build side, hands it to the probe | ||
| * side, and the leaf stage uses it to prune non-matching rows during the segment scan.</p> | ||
| * | ||
| * <p>The implementation mirrors {@code BloomFilterIdSet} (pinot-core) - same funnel choices, same | ||
| * raw-bits encoding for FLOAT/DOUBLE - but lives in pinot-common so it can be referenced by | ||
| * predicate classes. Serialization for cross-worker transport is intentionally out of scope here; | ||
| * follow-up changes will add a wire format alongside the runtime side-channel.</p> | ||
| * | ||
| * <p>The caller is responsible for invoking the {@code mightContain} overload that matches the | ||
| * column's data type. Calling the wrong overload is undefined: the underlying Guava | ||
| * {@link BloomFilter} is constructed with a single {@link com.google.common.hash.Funnel} matched | ||
| * to the {@link DataType}, so a wrong-typed call may throw {@link ClassCastException} (when the | ||
| * boxed value cannot be passed to that funnel) or return a meaningless membership answer. Callers | ||
| * must not rely on either outcome.</p> | ||
| */ | ||
| @SuppressWarnings("UnstableApiUsage") | ||
| public final class RuntimeBloomFilter { | ||
|
|
||
| /** | ||
| * Funnel-family selected at construction time based on the source {@link DataType}. FLOAT/DOUBLE | ||
| * reuse the INT/LONG funnels via raw-bits encoding: two float values with the same raw IEEE-754 | ||
| * bit pattern hash identically; values that differ only by bit pattern (e.g. {@code 0.0f} vs | ||
| * {@code -0.0f}, or distinct NaN payloads) hash to different slots. | ||
| */ | ||
| private enum FunnelType { | ||
| INT, LONG, STRING, BYTES | ||
| } | ||
|
|
||
| private final DataType _dataType; | ||
| private final FunnelType _funnelType; | ||
| private final BloomFilter<Object> _bloomFilter; | ||
|
|
||
| @SuppressWarnings({"unchecked", "rawtypes"}) | ||
| public RuntimeBloomFilter(DataType dataType, int expectedInsertions, double fpp) { | ||
| _dataType = dataType; | ||
| BloomFilter bf; | ||
| switch (dataType) { | ||
| case INT: | ||
| case FLOAT: | ||
| _funnelType = FunnelType.INT; | ||
| bf = BloomFilter.create(Funnels.integerFunnel(), expectedInsertions, fpp); | ||
| break; | ||
| case LONG: | ||
| case DOUBLE: | ||
| _funnelType = FunnelType.LONG; | ||
| bf = BloomFilter.create(Funnels.longFunnel(), expectedInsertions, fpp); | ||
| break; | ||
| case STRING: | ||
| _funnelType = FunnelType.STRING; | ||
| bf = BloomFilter.create(Funnels.unencodedCharsFunnel(), expectedInsertions, fpp); | ||
| break; | ||
| case BYTES: | ||
| _funnelType = FunnelType.BYTES; | ||
| bf = BloomFilter.create(Funnels.byteArrayFunnel(), expectedInsertions, fpp); | ||
| break; | ||
| default: | ||
| throw new IllegalArgumentException("RuntimeBloomFilter does not support data type: " + dataType); | ||
| } | ||
| _bloomFilter = (BloomFilter<Object>) bf; | ||
| } | ||
|
|
||
| public DataType getDataType() { | ||
| return _dataType; | ||
| } | ||
|
|
||
| public void add(int value) { | ||
| _bloomFilter.put(value); | ||
| } | ||
|
|
||
| public void add(long value) { | ||
| _bloomFilter.put(value); | ||
| } | ||
|
|
||
| public void add(float value) { | ||
| // Match BloomFilterIdSet: encode by raw IEEE-754 int bits so the INT funnel hashes values | ||
| // with the same bit pattern to the same slot. Values that differ only by bit pattern | ||
| // (-0.0f vs 0.0f, distinct NaN payloads) are NOT collapsed by this encoding. | ||
| _bloomFilter.put(Float.floatToRawIntBits(value)); | ||
| } | ||
|
|
||
| public void add(double value) { | ||
| _bloomFilter.put(Double.doubleToRawLongBits(value)); | ||
| } | ||
|
|
||
| public void add(String value) { | ||
| _bloomFilter.put(value); | ||
| } | ||
|
|
||
| public void add(byte[] value) { | ||
| _bloomFilter.put(value); | ||
| } | ||
|
|
||
| public boolean mightContain(int value) { | ||
| return _bloomFilter.mightContain(value); | ||
| } | ||
|
|
||
| public boolean mightContain(long value) { | ||
| return _bloomFilter.mightContain(value); | ||
| } | ||
|
|
||
| public boolean mightContain(float value) { | ||
| return _bloomFilter.mightContain(Float.floatToRawIntBits(value)); | ||
| } | ||
|
|
||
| public boolean mightContain(double value) { | ||
| return _bloomFilter.mightContain(Double.doubleToRawLongBits(value)); | ||
| } | ||
|
|
||
| public boolean mightContain(String value) { | ||
| return _bloomFilter.mightContain(value); | ||
| } | ||
|
|
||
| public boolean mightContain(byte[] value) { | ||
| return _bloomFilter.mightContain(value); | ||
| } | ||
|
|
||
| /** Returns the funnel family used internally; visible for tests. */ | ||
| FunnelType funnelType() { | ||
| return _funnelType; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are we relying on reference equality check ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we weren't actually relying on it anywhere, the defensive equals / hashCode override here was boilerplate without a real consumer.