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

IncrementalIndex Tests and Benchmarks Parametrization #10593

Merged
merged 12 commits into from
Jan 8, 2021

Conversation

liran-funaro
Copy link
Contributor

@liran-funaro liran-funaro commented Nov 18, 2020

Fixes #10494.

Description

Note: This PR only affects tests and benchmarks.
It would help developers evaluate incremental-index extensions, such as oak-incremental-index (#10001).

#10335 added a per incremental-index builder, but the parent class builder (IncrementalIndex.Builder) was not removed to avoid 100+ line changes in the test code.
This PR removes IncrementalIndex.Builder and refactor all its usage (only in tests the test/benchmarks code).
In addition, where needed, a parametrization was added so it will test/benchmark both builder implementations (on-heap and off-heap).

Add test cases for each index type

All tests that are relevant to the incremental index were modified. The modifications include the parametrization of the tests for all incremental-index implementations: on-heap and off-heap. In addition, this PR includes a bug fix in OffheapIncrementalIndex that was found using these tests.

To support this, a new helper class was added: IncrementalIndexCreator.
This class handle creating the appropriate index according to its name and closing it at the end of each test.

Add benchmark cases for each index type

All the benchmarks that are relevant to the incremental index were added an incremental-index parametrization: on-heap or off-heap.
In addition, some of these benchmarks were modified to resolve some issues that were encountered.

We list here the additional modifications we made to some of the benchmarks.

  • Add some additional parametrization:
    • rollup opportunity for the row generator
    • number of rows per segment
    • query order: descending/ascending
  • Add a missing tearDown() procedure
  • Properly close the queryable index in the tearDown() procedure
  • Moved any temporary folder creation and deletion to the setup()/tearDown() methods so they would not affect the measurements of the results
  • Use a predefined seed for reproducible results, to be compliant with most benchmarks
  • Add scopes (@State(Scope.Benchmark)) that allow us to test the incremental index without the overhead of the setup procedure of the queryable index benchmark
    • One scope for benchmarking queries on the incremental index
    • One scope for benchmarking queries on the queryable index

In addition, to reduce code duplications, a few methods were added to DataGenerator:

  1. void addToIndex(IncrementalIndex<?> index, int numOfRows): adds rows from this generator to an existing index
  2. List<InputRow> toList(int numOfRows): adds rows from this generator to a new list

User Experience Changes

After this PR, the user should not expect changes in most benchmarks results.
However, some benchmarks behavior will change as follows:

Runtime

Expected change: the following benchmarks will run much faster due to eliminating redundancy setup/teardown procedure calls.
However, the benchmarks reported results should not change.
FilteredAggregatorBenchmark, GroupByBenchmark, ScanBenchmark, SearchBenchmark, TimeseriesBenchmark, TopNBenchmark

Parametrization

Expected change: the following benchmarks will have additional parametrization options, hence they might take longer to run and produce more results.

  • indexType parametrization (will also test the off-heap implementation): FilteredAggregatorBenchmark, IncrementalIndexRowTypeBenchmark, IncrementalIndexReadBenchmark, IndexIngestionBenchmark, IndexPersistBenchmark, GroupByBenchmark, ScanBenchmark, SearchBenchmark, TimeseriesBenchmark, TopNBenchmark
  • descending query parametrization: FilteredAggregatorBenchmark, TimeseriesBenchmark
  • rollupOpportunity ingestion parametrization: IndexIngestionBenchmark

Unified Benchmarks Behaviour

Expected change: these changes affect some of the benchmarks' reported results as follows:

  • A rowsPerSegment parametrization was added to IncrementalIndexRowTypeBenchmark. Before the number of rows was not parametrized and it reported the time per single row insertion. Now it reports the total insertion time of all the rows, like the rest of the tests report.
  • ScanBenchmark, SearchBenchmark, GroupByBenchmark: now using a fixed seed, so the results are reproducible but might be slightly different than what was before with the random seed.
  • IndexPersistBenchmark: this benchmark previously cleaned the temporary data folder inside the tested method, instead of in the teardown procedure. For large index sizes, it affected the benchmark result significantly. With this modification, the results will be different (shorter times), but it will better reflect the "persist" performance.

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.

@liran-funaro liran-funaro changed the title IncrementalIndex Tests and Benchmarks Refactor IncrementalIndex Tests and Benchmarks Parametrization Nov 19, 2020
Copy link

@Eshcar Eshcar left a comment

Choose a reason for hiding this comment

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

Thank Liran for this PR.

It is generally a very big PR.
The text description helps a lot in reviewing the code.
However, there are some classes with very big changes, and it is not always easy to track back the reason for these changes, see my comments below.
Adding (many) lines of documentation within the code can greatly help the next reviewers read and approve the code.

@@ -227,10 +228,10 @@ public void tearDown() throws IOException

private IncrementalIndex makeIncIndex()
{
return new IncrementalIndex.Builder()
return new OnheapIncrementalIndex.Builder()
Copy link

Choose a reason for hiding this comment

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

shouldn't this method take a parameter to decide which type of index to return?
or is this the default builder?
then maybe buildDefaultIncIndex and the default should be some hard coded value that can be changed over time

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. But for the sake of reducing the diff size, I'd prefer to avoid this refactor.

{
FileUtils.deleteDirectory(tmpDir);
Copy link

Choose a reason for hiding this comment

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

the diff here is very misleading - this line is part of a one line method tearDown that was deleted

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that it was not deleted. It just moved below to the teardown of the QueryableIndexState: qIndexesDir.delete();

public static class IncrementalIndexState
{
@Param({"onheap", "offheap"})
private String indexType;
Copy link

Choose a reason for hiding this comment

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

since now there is a new extension point for incremental index, shouldn't the type be extendable as well?
use enum instead of string and names like defaultOnHeap and OakOffHeap so additional on/off-heap implementations can be added in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea here is indeed to allow a future index to be tested with the same code.
Using Enum will force this enumeration to list all existing index types in the core Druid package, albeit the index may only exist as an extension.
This way (using string), the user can choose any indexType name in the command line without it having to be pre-defined in the code.

@TearDown
public void tearDown()
{
qIndex.close();
Copy link

Choose a reason for hiding this comment

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

no option for qIndex to be null? e.g., if indexFile is empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure. But if setup fails, this might be an issue. Added a null check just to be safe.

public void setup2()
{
incIndex = makeIncIndex();
incFloatIndex = makeIncIndex();
Copy link

Choose a reason for hiding this comment

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

Does adding all rows into one index equivalent to having 3 indices?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Notice that the setup level was changed to per invocation, so for each benchmark, a new index is created.
There wasn't really a need for three different indices in the first place.

{
log.info("SETUP CALLED AT " + +System.currentTimeMillis());

ComplexMetrics.registerSerde("hyperUnique", new HyperUniquesSerde());

executorService = Execs.multiThreaded(numProcessingThreads, "GroupByThreadPool[%d]");
Copy link

Choose a reason for hiding this comment

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

from this point onward a bit hard to follow the reasoning for the changes? what part of the PR description does this relate to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These changes are due to the scoping of the benchmark. This setup method now only in charge of initializing everything common for benchmarking both the incremental-index and the queriable-index.
Anything specific to the incremental or queriable index was moved to its designated scope below.

bufferIndex = indexAndOffset[0];
bufferOffset = indexAndOffset[1];
aggBuffer = aggBuffers.get(bufferIndex).get();
ByteBuffer aggBuffer = aggBuffers.get(indexAndOffset[0]).get();
Copy link

Choose a reason for hiding this comment

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

what's the reasoning for these changes? add documentation to explain

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Before this change, the code that responsible for the aggregation ran after a new row was inserted to indexAndOffsets (see line 209 below). This means that the new row was visible before any data was aggregated to it.
This does not correspond with the on-heap index behavior, which first aggregates the data, then inserts the row to the index.
According to IncrementalIndexIngestionTest.testMultithreadAddFacts(), the on-heap behavior is the correct one, so I changed it accordingly so the test will pass for this index as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm I feel that this fix to OffheapIncrementalIndex can be independent in its own separate issue and PR since it is a bug and would make it easier for tracking in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If this bug fix will be in a separate PR, then this PR will have a failing test.

@@ -321,10 +322,10 @@ private static QueryableIndex buildIndex(String storeDoubleAsFloat) throws IOExc
)
.build();

final IncrementalIndex index = new IncrementalIndex.Builder()
Copy link

Choose a reason for hiding this comment

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

from here on forward same 5 lines changes repeat for different tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is true for tests that do not test the incremental-index implementation. For such tests, we use the on-heap implementation because it is the most stable.
Tests that test the index itself, are now parametrized so they have more modifications other than these 5 lines.

@@ -268,7 +217,7 @@ private static MapBasedInputRow getLongRow(long timestamp, int dimensionCount)
public void testCaseSensitivity() throws Exception
{
long timestamp = System.currentTimeMillis();
IncrementalIndex index = closerRule.closeLater(indexCreator.createIndex(DEFAULT_AGGREGATOR_FACTORIES));
IncrementalIndex<?> index = indexCreator.createIndex((Object) DEFAULT_AGGREGATOR_FACTORIES);
Copy link

Choose a reason for hiding this comment

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

all changes from here are due to the generic type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. To allow testing any new incremental index.

Copy link

Choose a reason for hiding this comment

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

Thanks for addressing the questions and issues
LGTM

import java.nio.ByteBuffer;

/**
* Since the off-heap incremental index is not yet supported in production ingestion, we define its spec here only
Copy link

Choose a reason for hiding this comment

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

add a more general documentation of the role of this class for the time it is supported

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.

Copy link

@Eshcar Eshcar left a comment

Choose a reason for hiding this comment

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

The changes in this PR would help people evaluate the oak extension.

@@ -268,7 +217,7 @@ private static MapBasedInputRow getLongRow(long timestamp, int dimensionCount)
public void testCaseSensitivity() throws Exception
{
long timestamp = System.currentTimeMillis();
IncrementalIndex index = closerRule.closeLater(indexCreator.createIndex(DEFAULT_AGGREGATOR_FACTORIES));
IncrementalIndex<?> index = indexCreator.createIndex((Object) DEFAULT_AGGREGATOR_FACTORIES);
Copy link

Choose a reason for hiding this comment

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

Thanks for addressing the questions and issues
LGTM

Copy link
Contributor

@a2l007 a2l007 left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. I've left some comments. If you have the results from your local run of the modified benchmarks, could you please post them here?

}

/**
* Add rows form any generator to an index.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: form -> from

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

public static AppendableIndexSpec parseIndexType(String indexType) throws JsonProcessingException
{
return JSON_MAPPER.readValue(
String.format(Locale.ENGLISH, "{\"type\": \"%s\"}", indexType),
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use StringUtils.format here instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

* @param c a list of collections of parameters
* @return the cartesian product of all parameters
*/
public static List<Object[]> cartesianProduct(Collection<?>... c)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this method need public visibility?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. Changed to private.

bufferIndex = indexAndOffset[0];
bufferOffset = indexAndOffset[1];
aggBuffer = aggBuffers.get(bufferIndex).get();
ByteBuffer aggBuffer = aggBuffers.get(indexAndOffset[0]).get();
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm I feel that this fix to OffheapIncrementalIndex can be independent in its own separate issue and PR since it is a bug and would make it easier for tracking in the future.

@@ -110,28 +119,28 @@ public void setup() throws IOException
);

incIndex = makeIncIndex();
gen.addToIndex(incIndex, rowsPerSegment);
Copy link
Contributor

Choose a reason for hiding this comment

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

I see there are other usages of gen.nextRow() that haven't been replaced. Is the plan to replace them in an follow up PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It wasn't planned, but I don't mind creating a follow-up PR for that or replacing everything in this PR.
What do you think is better?

Copy link
Contributor

Choose a reason for hiding this comment

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

Changing it in a follow-up PR sounds good to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK. I'll publish a new PR for this once this PR is merged.

@liran-funaro
Copy link
Contributor Author

@a2l007 Thanks for your review. I made modifications accordingly.
Let me know if you think more changes should be made, or if we can proceed to merge it.

I have results for the benchmarks I changed, but they are not with the default parameters.
I'll run the benchmarks overnight and post the results here.

@liran-funaro
Copy link
Contributor Author

@a2l007 The benchmark run results are available here: https://pastebin.pl/view/raw/8a5559c7

Copy link
Contributor

@a2l007 a2l007 left a comment

Choose a reason for hiding this comment

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

LGTM apart from one minor comment.
Also I'm curious if you've noticed any improvement in the benchmark scores even though the benchmark related changes are mainly refactoring and parametrization?

@@ -110,28 +119,28 @@ public void setup() throws IOException
);

incIndex = makeIncIndex();
gen.addToIndex(incIndex, rowsPerSegment);
Copy link
Contributor

Choose a reason for hiding this comment

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

Changing it in a follow-up PR sounds good to me.

@Param({"none", "moderate", "high"})
private String rollupOpportunity;
@Param({"0", "1000", "10000"})
private int rollupOpportunity;
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry I missed this earlier, but I feel that we should retain the textual values for rollupOpportunity as that is more user-friendly when reading the benchmark results.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK. I rolled back this change.

@liran-funaro
Copy link
Contributor Author

Thanks, @a2l007. Please let me know what other modifications are required to approve this PR.

Regarding your question:

Also I'm curious if you've noticed any improvement in the benchmark scores even though the benchmark-related changes are mainly refactoring and parameterization?

First, it is much faster to run the benchmarks due to less redundant setup/teardown procedure calls (achieved via scopes).
However, for most of the benchmarks, I didn't notice any performance changes.

The exceptions are as follows:

  • IndexPersistBenchmark: this benchmark previously cleaned the temporary data folder inside the tested method, instead of in the teardown procedure. For large index sizes, it affected the benchmark result significantly.
    With this modification, the results will be different (shorter times), but it will better reflect the "persist" performance.

  • IncrementalIndexRowTypeBenchmark: before the number of rows was not parametrized and it reported the time per single row insertion. Now it reports the total insertion time of all the rows, like the rest of the tests report. There is no option in JMH to set the @OperationsPerInvocation(MAX_ROWS) with respect to the parameter rowsPerSegment.

  • ScanBenchmark, SearchBenchmark: now using a fixed seed, so the results are reproducible but might be different than what was before with the random seed.

@a2l007
Copy link
Contributor

a2l007 commented Dec 16, 2020

  • IndexPersistBenchmark: this benchmark previously cleaned the temporary data folder inside the tested method, instead of in the teardown procedure. For large index sizes, it affected the benchmark result significantly.
    With this modification, the results will be different (shorter times), but it will better reflect the "persist" performance.

  • IncrementalIndexRowTypeBenchmark: before the number of rows was not parametrized and it reported the time per single row insertion. Now it reports the total insertion time of all the rows, like the rest of the tests report. There is no option in JMH to set the @OperationsPerInvocation(MAX_ROWS) with respect to the parameter rowsPerSegment.

  • ScanBenchmark, SearchBenchmark: now using a fixed seed, so the results are reproducible but might be different than what was before with the random seed.

I feel that this should be called out in the release notes so that users running benchmarks are aware of this change before upgrading. Could you please add a short description in the PR description mentioning the changes a user should expect running benchmarks before upgrading to this version. This would help the release manager add the blurb to the release notes.

Copy link
Contributor

@a2l007 a2l007 left a comment

Choose a reason for hiding this comment

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

Thanks for the PR!

@liran-funaro
Copy link
Contributor Author

@a2l007 I added a "User Experience Changes" section in the PR description.
Thanks!

@liran-funaro
Copy link
Contributor Author

Can we proceed to merge this PR?

@gianm
Copy link
Contributor

gianm commented Jan 8, 2021

Can we proceed to merge this PR?

I haven't studied the patch, but the description makes sense to me (thanks for the detailed description), @a2l007 has reviewed it, and it seems pretty low risk (test/benchmark only) so I would say yes. Thanks for the contribution!

@gianm gianm merged commit 08ab82f into apache:master Jan 8, 2021
gianm added a commit to gianm/druid that referenced this pull request Jan 8, 2021
@liran-funaro
Copy link
Contributor Author

Thanks!

gianm added a commit that referenced this pull request Jan 8, 2021
JulianJaffePinterest pushed a commit to JulianJaffePinterest/druid that referenced this pull request Jan 22, 2021
* Remove redundant IncrementalIndex.Builder

* Parametrize incremental index tests and benchmarks

- Reveal and fix a bug in OffheapIncrementalIndex

* Fix forbiddenapis error: Forbidden method invocation: java.lang.String#format(java.lang.String,java.lang.Object[]) [Uses default locale]

* Fix Intellij errors: declared exception is never thrown

* Add documentation and validate before closing objects on tearDown.

* Add documentation to OffheapIncrementalIndexTestSpec

* Doc corrections and minor changes.

* Add logging for generated rows.

* Refactor new tests/benchmarks.

* Improve IncrementalIndexCreator documentation

* Add required tests for DataGenerator

* Revert "rollupOpportunity" to be a string
JulianJaffePinterest pushed a commit to JulianJaffePinterest/druid that referenced this pull request Jan 22, 2021
@jihoonson jihoonson added this to the 0.21.0 milestone Jul 15, 2021
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.

Remove redundant IncrementalIndex.Builder
5 participants