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

Composite approach for checking in-filter values set in column dictionary #13133

Merged
merged 4 commits into from
Oct 13, 2022

Conversation

rohangarg
Copy link
Member

Currently, for checking the in-filter values in a column dictionary we use a binary search per value in the set. That works well for smaller value-sets but starts slowing down as the number of values in the set increase. To accommodate for large value-sets arising from large in-filters or from joins being pushed down as in-filters, we use sorted merge algorithm for merging the set and dictionary for larger values.

The following benchmark was run to find the cutoff point :

Benchmark     (dictionarySize)  (filterToDictionaryPercentage)  (selectivityPercentage)  Mode  Cnt        Score       Error  Units
binarySearch           1000000                               1                       10  avgt   10    11271.575 ±   621.620  us/op
binarySearch           1000000                               1                      100  avgt   10    16751.127 ±   580.550  us/op
binarySearch           1000000                               2                       10  avgt   10    25387.245 ±  1795.582  us/op
binarySearch           1000000                               2                      100  avgt   10    19707.334 ±   973.384  us/op
binarySearch           1000000                               5                       10  avgt   10    37256.779 ±  2287.203  us/op
binarySearch           1000000                               5                      100  avgt   10    47391.511 ±  1689.971  us/op
binarySearch           1000000                              10                       10  avgt   10    76204.615 ±  6515.056  us/op
binarySearch           1000000                              10                      100  avgt   10    71483.416 ±  7197.376  us/op
binarySearch           1000000                              12                       10  avgt   10  139481.091 ± 15513.357  us/op
binarySearch           1000000                              12                      100  avgt   10  142881.846 ±  9511.367  us/op
binarySearch           1000000                              15                       10  avgt   10   113786.273 ±  4123.158  us/op
binarySearch           1000000                              15                      100  avgt   10   165300.278 ± 10555.479  us/op
binarySearch           1000000                              20                       10  avgt   10   138410.942 ± 14330.367  us/op
binarySearch           1000000                              20                      100  avgt   10   137543.621 ±  9273.845  us/op
binarySearch           1000000                              30                       10  avgt   10   206512.608 ± 13497.954  us/op
binarySearch           1000000                              30                      100  avgt   10   305317.908 ± 12452.686  us/op
binarySearch           1000000                              50                       10  avgt   10   328867.893 ± 14594.249  us/op
binarySearch           1000000                              50                      100  avgt   10   332823.291 ± 26349.158  us/op
binarySearch           1000000                             100                       10  avgt   10   668720.906 ± 49193.818  us/op
binarySearch           1000000                             100                      100  avgt   10  1014546.405 ± 30709.670  us/op
sortedMerge            1000000                               1                       10  avgt   10    47220.743 ±  2598.755  us/op
sortedMerge            1000000                               1                      100  avgt   10    53634.485 ±  2886.296  us/op
sortedMerge            1000000                               2                       10  avgt   10    51201.356 ±  2745.801  us/op
sortedMerge            1000000                               2                      100  avgt   10    53046.058 ±  4500.987  us/op
sortedMerge            1000000                               5                       10  avgt   10    58501.742 ±  7320.870  us/op
sortedMerge            1000000                               5                      100  avgt   10    65597.519 ±  7356.548  us/op
sortedMerge            1000000                              10                       10  avgt   10    75347.468 ±  9417.556  us/op
sortedMerge            1000000                              10                      100  avgt   10    74601.584 ±  5251.078  us/op
sortedMerge            1000000                              12                       10  avgt   10   64838.734 ±  2644.738  us/op
sortedMerge            1000000                              12                      100  avgt   10   80342.737 ±  5414.306  us/op
sortedMerge            1000000                              15                       10  avgt   10    83345.836 ±  6034.488  us/op
sortedMerge            1000000                              15                      100  avgt   10    78405.299 ±  1651.375  us/op
sortedMerge            1000000                              20                       10  avgt   10   111307.577 ± 10924.456  us/op
sortedMerge            1000000                              20                      100  avgt   10    89371.173 ± 10347.814  us/op
sortedMerge            1000000                              30                       10  avgt   10   116740.355 ±  6003.945  us/op
sortedMerge            1000000                              30                      100  avgt   10   117312.763 ±  6325.076  us/op
sortedMerge            1000000                              50                       10  avgt   10   132145.411 ± 23121.259  us/op
sortedMerge            1000000                              50                      100  avgt   10   192802.722 ± 39032.876  us/op
sortedMerge            1000000                             100                       10  avgt   10   216079.634 ± 28725.333  us/op
sortedMerge            1000000                             100                      100  avgt   10   236561.476 ± 14369.673  us/op

This PR has:

  • been self-reviewed.
  • added documentation for new or modified features or behaviors.
  • 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.

break;
// if the size of in-filter values is less than the threshold percentage of dictionary size, then use binary search
// based lookup per value. The algorithm works well for smaller number of values.
if (size < SORTED_MERGE_RATIO_THRESHOLD * dictionary.size()) {
Copy link
Member

Choose a reason for hiding this comment

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

We can determine the strategy at the point of Iterator<ImmutableBitmap> object is instantiated.

And it would be much better if we split the returned Iterator<ImmutableBitmap> into two inner classes, one is for the binary search, the other is for the sorted merge.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that would be much more readable.

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense, done. Had done it earlier like that, but changed it last moment.

@@ -175,6 +177,8 @@ public String getValue(int index)
extends BaseGenericIndexedDictionaryEncodedIndex<ByteBuffer> implements StringValueSetIndex, Utf8ValueSetIndex
{
private static final int SIZE_WORTH_CHECKING_MIN = 8;
private static final double SORTED_MERGE_RATIO_THRESHOLD = 0.12D;
Copy link
Member

Choose a reason for hiding this comment

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

Could you add some javadoc to explain why the default threshold is 0.12?

Copy link
Contributor

Choose a reason for hiding this comment

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

We could link the issue that has the benchmarks.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added javadoc explanation for the threshold

Copy link
Contributor

@kfaraz kfaraz left a comment

Choose a reason for hiding this comment

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

Left some comments.

@@ -826,4 +862,28 @@ public void inspectRuntimeShape(RuntimeShapeInspector inspector)
}
};
}

public class ValueWithIndex
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess it would be cleaner to just use a ListIterator which provides nextIndex().
You wouldn't be able to peek the next index though, and you might have to work around that.
(That could be easier to do if we go with @FrankChen021 's suggestion to separate the two kinds of
searches into two different iterables.)

Another alternative could be to just use Pair but I am not a fan of it.

If you do decide to use this class, however, I would suggest putting as a top level class in druid-core/org.apache.druid.java.util.common, as other parts of the code might have similar requirements.

Copy link
Member Author

Choose a reason for hiding this comment

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

removed the iterator itself, so not needed anymore.

break;
// if the size of in-filter values is less than the threshold percentage of dictionary size, then use binary search
// based lookup per value. The algorithm works well for smaller number of values.
if (size < SORTED_MERGE_RATIO_THRESHOLD * dictionary.size()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that would be much more readable.

@abhishekagarwal87
Copy link
Contributor

@rohangarg - thanks for putting these benchmarks. In terms of query latencies, what difference have you observed?

@rohangarg
Copy link
Member Author

The force-push is done since I rebased from latest master. The new changes are a part of new commit and aren't squashed into the old ones.

@rohangarg
Copy link
Member Author

@rohangarg - thanks for putting these benchmarks. In terms of query latencies, what difference have you observed?

I have not tried benchmarking queries as of now since the amount of difference would depend on the data and the type of query being used. The benchmark added can measure the improvement with in-filter operation independently.

Copy link
Contributor

@cheddar cheddar left a comment

Choose a reason for hiding this comment

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

You've got one stale comment in there and the CI isn't passing for some reason, but the code looks good.

@rohangarg rohangarg merged commit 45dfd67 into apache:master Oct 13, 2022
@kfaraz kfaraz added this to the 25.0 milestone Nov 22, 2022
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.

5 participants