-
Notifications
You must be signed in to change notification settings - Fork 1.5k
PARQUET-2237 Improve performance when filters in RowGroupFilter can match exactly #1023
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
base: master
Are you sure you want to change the base?
Conversation
(cherry picked from commit 2ce35c7)
Unit test: - Updated ParquetWriter to support setting row group size in long - Removed Xmx settings in the pom to allow more memory for the tests Co-authored-by: Gabor Szadovszky <[email protected]>
…le (apache#913) * use try-with-resource statement for ParquetFileReader to call close explicitly
… … (apache#925) * PARQUET-2078 Failed to read parquet file after writing with the same parquet version * PARQUET-2078 Failed to read parquet file after writing with the same parquet version Read path fix that make usage of this information: RowGroup[n].file_offset = RowGroup[n-1].file_offset + RowGroup[n-1].total_compressed_size * PARQUET-2078 Failed to read parquet file after writing with the same parquet version addressing review comments: more check on writer side. * PARQUET-2078 Failed to read parquet file after writing with the same parquet version taking alignment padding and sumarry file into account * PARQUET-2078 Failed to read parquet file after writing with the same parquet version only throw exception when: 1.footer(first column of block meta) encrypted and 2.file_offset corrupted * PARQUET-2078 Failed to read parquet file after writing with the same parquet version only check firstColumnChunk.isSetMeta_data() for the first block * PARQUET-2078 Failed to read parquet file after writing with the same parquet version address review comments: empty lines * PARQUET-2078 Failed to read parquet file after writing with the same parquet version check first rowgroup's file_offset too(SPARK-36696) * PARQUET-2078 Failed to read parquet file after writing with the same parquet version Using Preconditions.checkState instead of assert in write path remove summary file footers case check in read path(which will never happen) * PARQUET-2078 Failed to read parquet file after writing with the same parquet version more special case for first row group
The purpose of this change is to fail the build if some classes are used from not direct dependencies. Only classes from direct dependencies shall be used. Also fixed some references that broke this rule.
This reverts commit 261e320.
(cherry picked from commit 1695d92)
This reverts commit 0f6fc7f.
wgtmac
left a comment
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.
Thanks @yabola for the fix! The idea of the patch is good and the algorithm should be correct (at least I cannot come up with a counter example yet). However, we still need to be careful just in case. My main concern is the test coverage. In addition, we may also need an option to toggle it off just in case.
| boolean drop = false; | ||
| // Whether one filter can exactly determine the existence/nonexistence of the value. | ||
| // If true then we can skip the remaining filters to save time and space. | ||
| AtomicBoolean canExactlyDetermine = new AtomicBoolean(false); |
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.
Why atomic?
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.
It used to be for the convenience of fetching the returned results. But I will change my codes in another implemention later
|
|
||
| private <T extends Comparable<T>> void markCanExactlyDetermine(Set<T> dictSet) { | ||
| if (dictSet == null) { | ||
| canExactlyDetermine = false; |
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.
It seems that canExactlyDetermine should use OR to update its value. Otherwise, any predicate with a null dict will set it to false even if previous predicates have marked it to true.
Additionally, we may have a chance to shortcut the evaluation as well if any predicate has set it to true.
| } | ||
|
|
||
| @Test | ||
| public void testCanSkipOtherFilters() { |
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.
The test looks a little bit insufficient. More kinds of predicates and compound predicates need to be covered. Also test of RowGroupFilter is missing.
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.
I will add more UT
| boolean drop = false; | ||
| // Whether one filter can exactly determine the existence/nonexistence of the value. | ||
| // If true then we can skip the remaining filters to save time and space. | ||
| AtomicBoolean canExactlyDetermine = new AtomicBoolean(false); |
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.
I'd suggest rename canExactlyDetermine to preciselyDetermined. Or even better, use an enum something like below
enum PredicateEvaluation {
CAN_DROP, /* the block can be dropped for sure */
CANNOT_DROP, /* the block cannot be dropped for sure*/
MAY_DROP, /* cannot decide yet, may be dropped by other filter levels */
}In this way, we can merge the the two boolean values here. The downside is that the code may need more refactoring to add the enum value to different filter classes.
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.
I will change my implemention
|
Unfortunately we cannot modify the signature of any public methods. My suggestion was to make the new enum serves as an internal state of the visitor (and probably use it to terminate evaluation early). Then add a new method to return the final state. Does it work? |
|
+1, let's not modify the signature. |
abfb2fa to
5dfd8b4
Compare
|
@wgtmac @shangxinli I thought of a way to avoid interface modification and distinguish by Boolean objects. Please take a look |
|
Emmmm, If this way is not suitable, I can use the filter internal variable to record it and keep compatibility |
wgtmac
left a comment
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.
The new methods look reasonable to me. Could you please add more tests?
| import org.apache.parquet.filter2.predicate.Operators; | ||
|
|
||
| /** | ||
| * Used in Filters to mark whether the block data matches the condition. |
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'd better explain explicitly that the evaluation decision it whether to drop the row group. That's why BLOCK_MIGHT_MATCH is false and the AND expression uses || in the implementation. This is counter-intuitive at the first glance.
parquet-hadoop/src/main/java/org/apache/parquet/filter2/compat/PredicateEvaluation.java
Outdated
Show resolved
Hide resolved
| public static final Boolean BLOCK_CANNOT_MATCH = new Boolean(true); | ||
|
|
||
| public static Boolean evaluateAnd(Operators.And and, FilterPredicate.Visitor<Boolean> predicate) { | ||
| Boolean left = and.getLeft().accept(predicate); |
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.
In the current implementation, left and right predicates are always evaluated. We can short cut the evaluation if left == BLOCK_CANNOT_MATCH and skip evaluation of right predicate. It would save some cost if the right expr will read dictionary.
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.
Yes, thanks
|
|
||
| public static Boolean evaluateOr(Operators.Or or, FilterPredicate.Visitor<Boolean> predicate) { | ||
| Boolean left = or.getLeft().accept(predicate); | ||
| Boolean right = or.getRight().accept(predicate); |
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.
ditto
| } | ||
| } | ||
|
|
||
| public static Boolean isDeterminedPredicate(Boolean predicate) { |
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.
Please add some comments to the public methods.
| Boolean predicate = BLOCK_MIGHT_MATCH; | ||
|
|
||
| if (levels.contains(FilterLevel.STATISTICS)) { | ||
|
|
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.
Please remove this blank line.
| if(!drop && levels.contains(FilterLevel.DICTIONARY)) { | ||
| drop = DictionaryFilter.canDrop(filterPredicate, block.getColumns(), reader.getDictionaryReader(block)); | ||
| if (levels.contains(FilterLevel.DICTIONARY)) { | ||
|
|
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.
ditto
| } | ||
|
|
||
| private <T extends Comparable<T>> Boolean drop(Set<T> dictSet, Set<T> values) { | ||
| private <T extends Comparable<T>> Boolean predicate(Set<T> dictSet, Set<T> values) { |
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.
| private <T extends Comparable<T>> Boolean predicate(Set<T> dictSet, Set<T> values) { | |
| private <T extends Comparable<T>> Boolean evaluate(Set<T> dictSet, Set<T> values) { |
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.
Use a verb instead of noun.
| return !hasNulls(meta); | ||
| // so if there are no nulls in this chunk, we can drop it, | ||
| // if there has nulls in this chunk, we must take it | ||
| return !hasNulls(meta) ? BLOCK_CANNOT_MATCH : BLOCK_MUST_MATCH; |
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.
I suspect that hasNull will always return precise information. When null_count is missing, hasNulls also returns true. Replacing BLOCK_MUST_MATCH with BLOCK_MIGHT_MATCH here makes more sense to me.
| return BLOCK_CANNOT_MATCH; | ||
| } else { | ||
| // if value > min, we must take it | ||
| return BLOCK_MUST_MATCH; |
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.
Sometimes the min/max values are actually lower/upper bounds. Does this optimization still work for that case?
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.
I think when Statistics#hasNonNullValue marks as true, minMax will be generated by the real data content, and it can represent the real data minMax ( when Statistics#hasNonNullValue is false, it has also been processed before.)
I think if we can use minMax to judge the BLOCK_CANNOT_MATCH , we can also judge the BLOCK_MUST_MATCH in some case.
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.
@wgtmac, do you aware of any implementations where the min/max values of the row group statistics are used this way? Unfortunately, the specification does not say anything about the min or max values has to be part of the dataset or not. The safe side would be to not to rely on this requirement. (For column index statistics we have defined that the related min/max values do not need to be part of the pages but it is not relevant here.)
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.
@gszadovszky Thank you for your review.
In the original implementation, BLOCK_CANNOT_MATCH can be judged using minMax.
So if we follow the specification, can we use minMax as the data result of an enlarged range? So we can accurately judge that when the data is not in this range.
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.
In fact, what I wanted to do at the beginning was to avoid the use of BloomFilter through minMax and dictionary(if column has) as much as possible, because the minMax and dictionary are more accurate and BloomFilter may cost time and memory.
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.
Good catch! I am not familiar with the old story. Does format v1 support bloom filter?
Yes, and Spark use parquet v1 by default
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.
Godd catch indeed, @yabola! Could you open a separate jira and maybe a PR for this finding?
@wgtmac, performance. Let's see the following scenario. We have dictionary encoding but not for all the pages. We also have Bloom filter. Does it worth reading the dictionary to check if a value is in there knowing if it doesn't we still want to check the Bloom filter? I don't know the answer, maybe yes. But if it is a no, then the whole concept of this PR is questionable.
For the case of all the pages are dictionary encoded we should not have Bloom filters therefore it doesn't really matter if we return BLOCK_MIGHT_MATCH or BLOCK_MUST_MATCH in case we find the interested values in the dictionary.
Since we might already written some Bloom filters for fully dictionary encoded column chunks we should handle this scenario. But we can do it easily buy skipping reading Bloom filters in this case completely.
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 have dictionary encoding but not for all the pages. We also have Bloom filter.
Yes, that's true.
Does it worth reading the dictionary to check if a value is in there knowing if it doesn't we still want to check the Bloom filter?
In this case, the dictionary will not be read via expandDictionary(meta) by DictionaryFilter if hasNonDictionaryPages(meta) returns true and will not make performance worse. e.g. https://github.com/apache/parquet-mr/blob/261f7d2679407c833545b56f4c85a4ae8b5c9ed4/parquet-hadoop/src/main/java/org/apache/parquet/filter2/dictionarylevel/DictionaryFilter.java#L388
For the case of all the pages are dictionary encoded we should not have Bloom filters therefore it doesn't really matter if we return BLOCK_MIGHT_MATCH or BLOCK_MUST_MATCH in case we find the interested values in the dictionary.
It is difficult to make the trade-off here. If we only have one predicate, then the dictionary will read any way, either by the DictionaryFilter or by reading the data later if the row group cannot be dropped. However, if we have other predicates that can drop the row group, then reading the dictionary here by DictionaryFilter is worthless.
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.
@wgtmac, @yabola, let me summarize my thoughts because I'm afraid I didn't describe them well before. Please correct me if I'm wrong.
In this PR we are trying to optimize the logic of RowGroupFilter. The problem with the current implementation is we step forward to the next filter even if the previous one would prove that a value we are searching for is actually (not possibly) in the row group. The idea is to introduce BLOCK_MUST_MATCH and if this is returned by any of the filters we would not step forward to the next filter and add the row group to the list (do not drop it). We currently have 3 row group level filters.
StatisticsFilter: Because of the lower/upper bound issue we cannot really improve this (except for the specific case when min=max)DictionaryFilter: We only can improve (?) the case when not all the pages are dictionary encoded because otherwise we would not have a Bloom filter so we won't step to the next filter anyway. So the dilemma is whether it worth to load the dictionary (which is potentially large since not all the values in the column chunk can fit in it) or is it better to use Bloom filter only. (The latter one is the current implementation.)BloomFilterImpl: By nature we do not have aBLOCK_MUST_MATCHoption.
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.
@gszadovszky @wgtmac @zhongyujiang Thank you very much for working on it. I have some thoughts.
We can improve (?) the case when not all the pages are dictionary encoded
-
I can't make sure if it is suitable to load dictionary even if pages are not all decoded. (I may choose not to change this behavior)
-
However considering the origin
BloomFilterbug in parquet v1, we might have to do something to avoid usingBloomFilter(even if pages are all encoded).
In the code implementation we may have to use some flag to mark if dictionaryDictionaryFilter#expandDictionarysuccessfully (method will throwIOExceptionand we can'texpandDictionaryagain inBloomFilterImpl).
Or we could also useBLOCK_MUST_MATCHlike this PR.
StatisticsFilter: Because of the lower/upper bound issue we cannot really improve this (except for the specific case when min=max)
If we only use it when min=max, I think it might not really improve .
|
@wgtmac Thanks for your review, I will add UT later. Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean(true);
boolean b3 = true;
boolean b4 = true;
assert b1 != b2;
assert b1.equals(b2);
assert b2 == b3 == b4; |
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.
@wgtmac @shangxinli I add new UT TestRowGroupFilterExactly , if you have time, please take a look, thanks!
| .withRecordFilter(FilterCompat.get(filter)).build(); | ||
|
|
||
| // simulate the previous behavior, only skip other filters when predicate is BLOCK_CANNOT_MATCH | ||
| testEvaluation.setTestExactPredicate(Collections.singletonList(BLOCK_CANNOT_MATCH)); |
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.
simulate the previous behavior, only skip other filters when predicate is BLOCK_CANNOT_MATCH
| public static Boolean evaluateAnd(Operators.And and, FilterPredicate.Visitor<Boolean> predicate) { | ||
| Boolean left = and.getLeft().accept(predicate); | ||
| if (left == BLOCK_CANNOT_MATCH) { | ||
| // seems unintuitive to put an || not an && here but we can |
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.
The comment does not match the code now.
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.
Thanks for your review, I update comments and add more UT for And Or
| Boolean right = and.getRight().accept(predicate); | ||
| if (right == BLOCK_CANNOT_MATCH) { | ||
| return BLOCK_CANNOT_MATCH; | ||
| } else if (left == BLOCK_MUST_MATCH && right == BLOCK_MUST_MATCH) { |
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.
| } else if (left == BLOCK_MUST_MATCH && right == BLOCK_MUST_MATCH) { | |
| } else if (left == BLOCK_MUST_MATCH || right == BLOCK_MUST_MATCH) { |
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.
Both left and right are not BLOCK_CANNOT_MATCH, so it can return BLOCK_MUST_MATCH if either side is BLOCK_MUST_MATCH
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.
if left is BLOCK_MUST_MATCH , right is BLOCK_MIGHT_MATCH , left & right should be BLOCK_MIGHT_MATCH.
Because in the next filter may let right be BLOCK_CANNOT_MATCH and we should drop it.
And I add new UT
In StatisticsFilter left might match (but can't match in DictionaryFilter), right must match -> return might match in StatisticsFilter, return can't match in DictionaryFilter
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.
I didn't get your point here. @yabola
If the current expression is A and B, then following result applies regardless of other expressions:
- A is BLOCK_MUST_MATCH and B is BLOCK_MUST_MATCH => BLOCK_MUST_MATCH
- A is BLOCK_MUST_MATCH and B is BLOCK_MIGHT_MATCH => BLOCK_MUST_MATCH
- A is BLOCK_MIGHT_MATCH and B is BLOCK_MUST_MATCH => BLOCK_MUST_MATCH
- A is BLOCK_MIGHT_MATCH and B is BLOCK_MIGHT_MATCH => BLOCK_MIGHT_MATCH
- A is BLOCK_CANNOT_MATCH or/and B is BLOCK_CANNOT_MATCH => BLOCK_CANNOT_MATCH
| // if left or right operation must need the block, then we must take the block | ||
| return BLOCK_MUST_MATCH; | ||
| } else if (left == BLOCK_CANNOT_MATCH && right == BLOCK_CANNOT_MATCH) { | ||
| // seems unintuitive to put an && not an || here |
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.
The comment does not match the code.
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.
done
|
cc @zhongyujiang |
|
@gszadovszky @shangxinli If you have time, please also take a look, thanks~ |
|
|
||
| // drop if value <= min | ||
| return stats.compareMinToValue(value) >= 0; | ||
|
|
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.
Minor: extra blank line.
|
|
||
| // drop if value < min | ||
| return stats.compareMinToValue(value) > 0; | ||
|
|
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.
Minor: extra blank line.
|
|
||
| private final Map<ColumnPath, ColumnChunkMetaData> columns = new HashMap<ColumnPath, ColumnChunkMetaData>(); | ||
|
|
||
| public static boolean canDrop(FilterPredicate pred, List<ColumnChunkMetaData> columns, BloomFilterReader bloomFilterReader) { |
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.
Minor: This line can remain unchanged if we move the #predicate down.
| if (dictSet != null && !dictSet.contains(value)) { | ||
| return BLOCK_CANNOT_MATCH; | ||
| } | ||
| if (dictSet != null && dictSet.contains(value)) { | ||
| return BLOCK_MUST_MATCH; | ||
| } |
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.
| if (dictSet != null && !dictSet.contains(value)) { | |
| return BLOCK_CANNOT_MATCH; | |
| } | |
| if (dictSet != null && dictSet.contains(value)) { | |
| return BLOCK_MUST_MATCH; | |
| } | |
| if(dictSet != null) { | |
| return dictSet.contains(value) ? BLOCK_MUST_MATCH || BLOCK_CANNOT_MATCH; | |
| } |
| private static final boolean BLOCK_MIGHT_MATCH = false; | ||
| private static final boolean BLOCK_CANNOT_MATCH = true; | ||
|
|
||
| public static boolean canDrop(FilterPredicate pred, List<ColumnChunkMetaData> columns) { |
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.
Minor: This line can remain unchanged if we move the #predicate down.
| Boolean predicate = BLOCK_MIGHT_MATCH; | ||
| if (levels.contains(FilterLevel.STATISTICS)) { | ||
| predicate = StatisticsFilter.predicate(filterPredicate, block.getColumns()); | ||
| if(isExactPredicate(predicate)) { |
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.
nit: name of isExactPredicate is a little bit unclear.
| Boolean right = and.getRight().accept(predicate); | ||
| if (right == BLOCK_CANNOT_MATCH) { | ||
| return BLOCK_CANNOT_MATCH; | ||
| } else if (left == BLOCK_MUST_MATCH && right == BLOCK_MUST_MATCH) { |
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.
I didn't get your point here. @yabola
If the current expression is A and B, then following result applies regardless of other expressions:
- A is BLOCK_MUST_MATCH and B is BLOCK_MUST_MATCH => BLOCK_MUST_MATCH
- A is BLOCK_MUST_MATCH and B is BLOCK_MIGHT_MATCH => BLOCK_MUST_MATCH
- A is BLOCK_MIGHT_MATCH and B is BLOCK_MUST_MATCH => BLOCK_MUST_MATCH
- A is BLOCK_MIGHT_MATCH and B is BLOCK_MIGHT_MATCH => BLOCK_MIGHT_MATCH
- A is BLOCK_CANNOT_MATCH or/and B is BLOCK_CANNOT_MATCH => BLOCK_CANNOT_MATCH
KE-40948 Add rowGroup filters info
|
@wgtmac @gszadovszky
|
|
Thanks @yabola for coming up with this idea. Let's continue the discussion about the BloomFilter building idea in the jira. Meanwhile, I've been thinking about the actual problem as well. Currently, for row group filtering we are checking the min/max values first which is correct since this is the most fast thing to do. Then the dictionary and then the bloom filter. The ordering of the latter two is not obvious to me in every scenarios. At the time of filtering we did not start reading the actual row group so there is no advantage in I/O to read the dictionary first. Furthermore, searching something in the bloom filter is much faster than in the dictionary. And the size of the bloom filter is probably less than the size of the dictionary. Though, it would require some measurements to prove if it is a good idea to get the bloom filter before the dictionary. What do you think? |
What I did in production is to issue async I/Os of dictionaries (if all data pages are dictionary-encoded in that column chunk and the dictionary is not big) and bloom filters of selected row groups in advance. The reason is to eliminate blocking I/O when pushing down the predicates. However, the parquet specs only records the offset to bloom filter. So I also added the length of each bloom filter in the key value metadata section (probably a good reason to add to the specs as well?) |
It is a good idea to adjust filter order and prefer the use of lighter filters first to judge. But this lacks practical scenarios, and I am not sure which is a better choice, need to think more about it. |
…ence#68) * KE-40433 add page index filter log * KE-40433 release 1.12.2-kylin-r5
If we can accurately judge by the minMax status, we don’t need to load the dictionary from filesystem and compare one by one anymore.
Similarly , Bloomfilter needs to load from filesystem, it may costs time and memory. If we can exactly determine the existence/nonexistence of the value from minMax or dictionary filters , then we can avoid using Bloomfilter to Improve performance.
For example,
x1in the block, if minMax in status is all greater thanx1, then we don't need to read dictionary from filesystem and compare one by one.