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

Incorporate estimatedComputeCost into all BitmapColumnIndex classes. #17125

Merged
merged 7 commits into from
Sep 26, 2024

Conversation

cecemei
Copy link
Contributor

@cecemei cecemei commented Sep 20, 2024

Incorporate estimatedComputeCost into all BitmapColumnIndex classes.

Description

In #17055, we added estimatedIndexComputeCost field to FilterBundle.Builder class, which would be used to sort child filters in AndFilter/OrFilter. The goal is to compute less expensive filters first, thereby enhancing query performance. This PR aims to incorporate estimatedComputeCost into all BitmapColumnIndex classes, serving as an initial measure for estimating the cost of filters.

An overall approach of estimating the cost is to assess how many bitmaps we expect to union or intersect. Dictionary lookup would also incur some overhead.

  • AllTrueBitmapColumnIndex, AllFalseBitmapColumnIndex, AllUnknownBitmapColumnIndex. The cost is 0.
  • SimpleImmutableBitmapIndex. The cost is 0.
  • SimpleBitmapColumnIndex instances. It generally involves one binary search, and maybe union with null bitmap. The cost is 1.
    • Note I changed one usage in ListFilteredDruidPredicateIndexes to use DictionaryScanningBitmapIndex instead.
  • DictionaryRangeScanningBitmapIndex. The cost is the size of scanning range.
  • DictionaryScanningBitmapIndex. The cost is the size of dictionary.
  • BaseValueSetIndexesFromIterable.
    • buildBitmapColumnIndexFromSortedIteratorScan. Cost is max of value set size and dictionary size.
    • buildBitmapColumnIndexFromSortedIteratorBinarySearch. Cost is value set size.
    • buildBitmapColumnIndexFromIteratorBinarySearch. Cost is value set size.
  • NestedVariantStringValueSetIndexes. For each value, we need to look up from three global dictionaries (stringDictionary, longDictionary and doubleDictionary), and one local dictionary. Therefore we define a base cost of 3 (INDEX_COMPUTE_SCALE). The cost of building index from value set would be 3 * size of value set.
  • IsBooleanFilter and NotFilter, the cost is the same as baseIndex.
  • AndFilter and OrFilter, cost is 0 since the bundle would sum up the cost of its child filters.
  • SpatialFilter. There's no good way to define cost, so putting down max integer for now, it'll always be evaluated last.

Turned on CURSOR_AUTO_ARRANGE_FILTERS by default in this PR.

While working on this PR, I had some ideas on refactoring some of the usages of BitmapColumnIndex, specifically:

  • Consolidate the usage of SimpleImmutableBitmapIndex and SimpleBitmapColumnIndex. The BitmapColumnIndex interface defines a method computeBitmapResult(BitmapResultFactory<T> bitmapResultFactory, boolean includeUnknown). The includeUnknown param is not used in SimpleImmutableBitmapIndex because the bitmap is already pre-computed. Maybe we could have an UnknownBitmapSupplier instead.
  • Create a DictionaryBinarySearchBitmapIndex class which could extract the iterator definition out, maybe can replace all usages of SimpleImmutableBitmapDelegatingIterableIndex.

Benchmark comparison

I'm comparing the results with CURSOR_AUTO_ARRANGE_FILTERS flag enabled and disabled.

query 1

SELECT string2, SUM(long1) FROM foo WHERE string5 LIKE '%1%' AND string1 = '1000' GROUP BY 1 ORDER BY 2

Flag off

Benchmark                        (deferExpressionDimensions)  (query)  (rowsPerSegment)  (schema)  (vectorize)  Mode  Cnt    Score    Error  Units
SqlExpressionBenchmark.querySql         fixedWidthNonNumeric       46           5000000  explicit        force  avgt    5  326.808 ±  5.295  ms/op
SqlExpressionBenchmark.querySql         fixedWidthNonNumeric       46           5000000      auto        force  avgt    5  313.831 ±  3.502  ms/op

Flag on

Benchmark                        (deferExpressionDimensions)  (query)  (rowsPerSegment)  (schema)  (vectorize)  Mode  Cnt    Score    Error  Units
SqlExpressionBenchmark.querySql         fixedWidthNonNumeric       46           5000000  explicit        force  avgt    5  110.170 ±  4.968  ms/op
SqlExpressionBenchmark.querySql         fixedWidthNonNumeric       46           5000000      auto        force  avgt    5  109.375 ±  1.388  ms/op

query 2

SELECT string2, SUM(long1) FROM foo WHERE string5 LIKE '%1%' AND (string3 in ('1', '10', '20', '22', '32') AND long2 IN (1, 19, 21, 23, 25, 26, 46) AND double3 < 1010.0 AND double3 > 1000.0 AND (string4 = '1' OR REGEXP_EXTRACT(string1, '^1') IS NOT NULL OR REGEXP_EXTRACT('Z' || string2, '^Z2') IS NOT NULL)) AND string1 = '1000' GROUP BY 1 ORDER BY 2

Flag off

Benchmark                        (deferExpressionDimensions)  (query)  (rowsPerSegment)  (schema)  (vectorize)  Mode  Cnt    Score    Error  Units
SqlExpressionBenchmark.querySql         fixedWidthNonNumeric       47           5000000  explicit        force  avgt    5  333.207 ± 14.524  ms/op
SqlExpressionBenchmark.querySql         fixedWidthNonNumeric       47           5000000      auto        force  avgt    5  334.476 ± 39.445  ms/op

Flag on

Benchmark                        (deferExpressionDimensions)  (query)  (rowsPerSegment)  (schema)  (vectorize)  Mode  Cnt    Score    Error  Units
SqlExpressionBenchmark.querySql         fixedWidthNonNumeric       47           5000000  explicit        force  avgt    5  106.400 ± 14.760  ms/op
SqlExpressionBenchmark.querySql         fixedWidthNonNumeric       47           5000000      auto        force  avgt    5  105.784 ± 26.924  ms/op

query 3

SELECT SUM(long1) FROM foo WHERE string5 LIKE '%1%' AND string1 = '1000'

Flag off

Benchmark                        (query)  (rowsPerSegment)  (schema)  (stringEncoding)  (vectorize)  Mode  Cnt    Score   Error  Units
SqlNestedDataBenchmark.querySql       56           5000000  explicit              none        force  avgt    5  195.104 ± 3.935  ms/op
SqlNestedDataBenchmark.querySql       56           5000000      auto              none        force  avgt    5  224.047 ± 1.598  ms/op

Flag on

Benchmark                        (query)  (rowsPerSegment)  (schema)  (stringEncoding)  (vectorize)  Mode  Cnt    Score   Error  Units
SqlNestedDataBenchmark.querySql       56           5000000  explicit              none        force  avgt    5  8.200 ± 0.473  ms/op
SqlNestedDataBenchmark.querySql       56           5000000      auto              none        force  avgt    5  8.243 ± 0.809  ms/op

This PR has:

  • been self-reviewed.
  • added documentation for new or modified features or behaviors.
  • a release note entry in the PR description.
  • added Javadocs for most classes and all non-trivial methods. Linked related entities via Javadoc links.
  • added or updated version, license, or notice information in licenses.yaml
  • added comments explaining the "why" and the intent of the code wherever would not be obvious for an unfamiliar reader.
  • added unit tests or modified existing tests to cover new code paths, ensuring the threshold for code coverage is met.
  • added integration tests.
  • been tested in a test Druid cluster.

return ListFilteredDimensionSpec.filterAllowList(
values,
factory.makeDimensionSelector(delegate),
delegate.getExtractionFn() != null

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note

Invoking
DimensionSpec.getExtractionFn
should be avoided because it has been deprecated.
return ListFilteredDimensionSpec.filterDenyList(
values,
factory.makeDimensionSelector(delegate),
delegate.getExtractionFn() != null

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note

Invoking
DimensionSpec.getExtractionFn
should be avoided because it has been deprecated.
@cecemei cecemei marked this pull request as ready for review September 23, 2024 16:58
@Override
public int estimatedComputeCost()
{
return 0;
Copy link
Member

Choose a reason for hiding this comment

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

it looks like this is mainly used for null value index, should this be 1 to be consistent with the equality indexes, like ValueIndexes.forValue, since the null indexes still have a bitmap?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right this index seems mainly for null index, so it's just 1 bitmap with no union. When I looked up forValue seems like it's possible there're two bitmaps (one for the value and one for null) with one union. That's why i decided 0 for this, and 1 for other SimpleBitmapIndex instances. I feel SimpleImmutableBitmapIndex is slightly cheaper since no binary search for dictionary and no bitmap union.

@@ -1204,6 +1256,9 @@ private abstract class NestedVariantIndexes
final FrontCodedIntArrayIndexed arrayDictionary = globalArrayDictionarySupplier == null
? null
: globalArrayDictionarySupplier.get();
// For every single String value, we need to look up indexes from stringDictionary, longDictionary and
// doubleDictionary. Hence, the compute cost for one value is 3.
static final int INDEX_COMPUTE_SCALE = 3;
Copy link
Member

Choose a reason for hiding this comment

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

i actually think 1 would probably be ok here too since we still only use a single bitmap, but this is also fine

@@ -199,7 +199,7 @@ public String getFormatString()
// 42, 43 big cardinality like predicate filter
"SELECT SUM(long1) FROM foo WHERE string5 LIKE '%1%'",
"SELECT SUM(JSON_VALUE(nested, '$.long1' RETURNING BIGINT)) FROM foo WHERE JSON_VALUE(nested, '$.nesteder.string5') LIKE '%1%'",
// 44, 45 big cardinality like filter + selector filter
// 44, 45 big cardinality like filter + selector filter with different ordering
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ran the tests for 44,45,46,47. The scores are very similar, since we've the ordering:

Benchmark                        (query)  (rowsPerSegment)  (schema)  (stringEncoding)  (vectorize)  Mode  Cnt  Score   Error  Units
SqlNestedDataBenchmark.querySql       44           5000000  explicit              none        force  avgt    5  7.753 ± 0.380  ms/op
SqlNestedDataBenchmark.querySql       44           5000000      auto              none        force  avgt    5  8.023 ± 0.940  ms/op
SqlNestedDataBenchmark.querySql       45           5000000  explicit              none        force  avgt    5  7.976 ± 0.735  ms/op
SqlNestedDataBenchmark.querySql       45           5000000      auto              none        force  avgt    5  7.820 ± 0.863  ms/op
SqlNestedDataBenchmark.querySql       46           5000000  explicit              none        force  avgt    5  7.495 ± 0.279  ms/op
SqlNestedDataBenchmark.querySql       46           5000000      auto              none        force  avgt    5  7.861 ± 0.691  ms/op
SqlNestedDataBenchmark.querySql       47           5000000  explicit              none        force  avgt    5  7.577 ± 0.405  ms/op
SqlNestedDataBenchmark.querySql       47           5000000      auto              none        force  avgt    5  7.735 ± 0.491  ms/op

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comparing with upstream/master branch:

Benchmark                        (query)  (rowsPerSegment)  (schema)  (stringEncoding)  (vectorize)  Mode  Cnt    Score   Error  Units
SqlNestedDataBenchmark.querySql       44           5000000  explicit              none        force  avgt    5  215.894 ± 2.712  ms/op
SqlNestedDataBenchmark.querySql       44           5000000      auto              none        force  avgt    5  208.718 ± 5.545  ms/op
SqlNestedDataBenchmark.querySql       45           5000000  explicit              none        force  avgt    5  221.829 ± 7.157  ms/op
SqlNestedDataBenchmark.querySql       45           5000000      auto              none        force  avgt    5  216.632 ± 2.712  ms/op
SqlNestedDataBenchmark.querySql       46           5000000  explicit              none        force  avgt    5    7.431 ± 0.286  ms/op
SqlNestedDataBenchmark.querySql       46           5000000      auto              none        force  avgt    5    7.396 ± 0.186  ms/op
SqlNestedDataBenchmark.querySql       47           5000000  explicit              none        force  avgt    5    7.487 ± 0.287  ms/op
SqlNestedDataBenchmark.querySql       47           5000000      auto              none        force  avgt    5    7.451 ± 0.203  ms/op

Copy link
Member

@clintropolis clintropolis left a comment

Choose a reason for hiding this comment

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

🤘 🚀

{
return Integer.MAX_VALUE;
}
int estimatedComputeCost();
Copy link
Member

Choose a reason for hiding this comment

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

i know this isn't new in this PR, but i feel like maybe the javadoc should mention that the estimated cost should be related to the number of bitmap operations that need to be performed to compute the filter bitmap

Copy link
Contributor Author

@cecemei cecemei Sep 25, 2024

Choose a reason for hiding this comment

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

added more explanation on this.

@clintropolis clintropolis merged commit a2b011c into apache:master Sep 26, 2024
90 checks passed
@clintropolis clintropolis added this to the 31.0.0 milestone Sep 26, 2024
cecemei added a commit to cecemei/druid that referenced this pull request Sep 26, 2024
…es. (apache#17125)

changes:
* filter index processing is now automatically ordered based on estimated 'cost', which is approximated based on how many expected bitmap operations are required to construct the bitmap used for the 'offset'
* cursorAutoArrangeFilters context flag now defaults to true, but can be set to false to disable cost based filter index sorting
clintropolis pushed a commit that referenced this pull request Sep 30, 2024
…es. (#17125) (#17172)

changes:
* filter index processing is now automatically ordered based on estimated 'cost', which is approximated based on how many expected bitmap operations are required to construct the bitmap used for the 'offset'
* cursorAutoArrangeFilters context flag now defaults to true, but can be set to false to disable cost based filter index sorting
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.

2 participants