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

better types #11713

Merged
merged 22 commits into from
Oct 19, 2021
Merged

better types #11713

merged 22 commits into from
Oct 19, 2021

Conversation

clintropolis
Copy link
Member

@clintropolis clintropolis commented Sep 15, 2021

Description

This PR enriches the native engine type system to use a more powerful set of classes than the existing enumerations, ValueType and ExprType, which are primarily used right now.

This is the start of a series of changes that will end up with 'complex metrics' being rebranded 'complex types' and being fully usable throughout the query system, including for expressions, grouping, and filtering.

To share the same structure and utilities when working with both the native engine and expressions, TypeSignature

public interface TypeSignature<Type extends TypeDescriptor>
{
  Type getType();
  @Nullable
  String getComplexTypeName();
  @Nullable
  TypeSignature<Type> getElementType();
}

has been added to model basic type information, and is implemented by the new ColumnType and ExpressionType classes, as well as ColumnCapabilities. TypeDescriptor is an interface shared by both ExprType and ValueType,

public interface TypeDescriptor
{
  boolean isNumeric();
  boolean isPrimitive();
  boolean isArray();
}

to expose some common facts about both sets of enums.

To aid in the construction of these types:

public interface TypeFactory<T extends TypeSignature<? extends TypeDescriptor>>
{
  T ofString();
  T ofFloat();
  T ofDouble();
  T ofLong();
  T ofArray(T elementType);
  T ofComplex(@Nullable String complexTypeName);
}

has also been added. This is interning the type objects so that they are re-used in the engine to prevent the creation of a large amount of garbage (operating on the assumption that there won't be that many types and that they will be frequently used).

Beyond these new structures, the vast majority of changes in this PR are around replacing all occurrences of ValueType with ColumnType, and ExprType with ExpressionType, and adjusting type checking code accordingly.

JSON Serialization

To be as compatible as possible, serialization is currently done with a string based format. LONG, STRING, FLOAT, DOUBLE all remain the same as they were when ValueType was king, and serde as the appropriate ColumnType (or ExpressionType).

Array types have been significantly reworked to take advantage of the new structure: instead of dedicated typed arrays, LONG_ARRAY etc, ValueType and ExprType now have a single ARRAY type, and elementType contains a reference to the internal type. In the string serialized form, these now look like ARRAY<LONG>, ARRAY<STRING>, etc, but can translate the legacy values if encountered.

Finally, COMPLEX will read into an unknown complex type, but if the type information is present, will serialize as COMPLEX<typeName>.

I've added object JSON constructors too, in order to prepare for a future where we might want to use objects instead of this string based serde, but the strings are adequately flexible for now I think, since it will be possible to model all sorts of types that were not possible in the previous system, such as ARRAY<ARRAY<COMPLEX<typeName>> if we want to get wild with it.

SQL and INFORMATION_SCHEMA

The only change in this PR that will be apparent to most users is that now that complex type information is preserved through-out the engine, the INFORMATION_SCHEMA columns table can display the complex type information instead of OTHER:

Screen Shot 2021-09-04 at 6 48 41 PM

The JDBC type for complex is still reported as OTHER because i need to do some additional investigation on if there is anything more appropriate (it has to be something in java.sql.Types, but we could possibly consider setting the classname to the complex type instead of java.lang.Object, I need to better learn the rules about how JDBC is supposed to behave)

Future work

I think there is quite a lot of stuff we can do once this PR goes in, here is the short list of most immediate things I can think of.

Complex expressions

This is likely to be one of the first follow-up PRs I do after this, since the new type system will support making complex expressions, which is one of the last feature gaps of the expression system. After this there will be very little that cannot be done with expressions that is supported by the native engine.

Better input validation

There is a lot we could do after this PR to take advantage of the additional information it makes available. SQL planning and aggregator factories could both take advantage of the preserved complex type information to perform better validation on their inputs, and better behavior or error messaging whenever inappropriate types have been used. Similarly, this additional type information also should make it easier to make complex aggregator factories which are not split into separate build and merge factories since we should have adequate type information to be able to build the correct type of aggregator by examining the input type information.

Better arrays

The updated type system in this PR supports modeling nested arrays and arrays of complex types, but the array expressions have not been updated to actually create or support them.


Key changed/added classes in this PR
  • ValueType
  • ExprType
  • TypeDescriptor
  • TypeFactory
  • TypeSignature
  • ColumnType
  • ColumnTypeFactory
  • ExpressionType
  • ExpressionTypeFactory
  • ColumnCapabilities
  • RowSignature
  • Calcites
  • RowSignatures

This PR has:

  • been self-reviewed.
  • added Javadocs for most classes and all non-trivial methods. Linked related entities via Javadoc links.
  • 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.
  • been tested in a test Druid cluster.

@lgtm-com
Copy link

lgtm-com bot commented Sep 15, 2021

This pull request introduces 2 alerts when merging 7d433cb into 1370fcf - view on LGTM.com

new alerts:

  • 1 for Dereferenced variable may be null
  • 1 for Unused format argument

@clintropolis
Copy link
Member Author

btw, this PR is going to have lots of conflicts since ValueType and ExprType are in a lot of places, so we'll need to watch out for that stuff shortly after it merges

ExprType.STRING_ARRAY,
14,
ExpressionType.STRING_ARRAY,
15,
Copy link
Member Author

Choose a reason for hiding this comment

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

these changes in values are because arrays are stored with an extra byte now (one for ARRAY, one for element type).

@clintropolis
Copy link
Member Author

I've taken some measurements to check to see if there is any performance regression from the changes in this PR, and it looks ok to me. I chose expressions to measure since non-vectorized expression processing is the most intensive use of type stuff since things are checked every row, the majority of other uses of type information is per segment or per query.

before:

Benchmark                        (query)  (rowsPerSegment)  (vectorize)  Mode  Cnt     Score     Error  Units
SqlExpressionBenchmark.querySql        4           5000000        false  avgt    5   266.083 ±  15.995  ms/op
SqlExpressionBenchmark.querySql        4           5000000        force  avgt    5   107.886 ±   7.874  ms/op
SqlExpressionBenchmark.querySql       10           5000000        false  avgt    5  1112.108 ±  91.164  ms/op
SqlExpressionBenchmark.querySql       10           5000000        force  avgt    5   200.173 ±  20.951  ms/op
SqlExpressionBenchmark.querySql       17           5000000        false  avgt    5   592.618 ±  58.979  ms/op
SqlExpressionBenchmark.querySql       17           5000000        force  avgt    5   139.361 ±  15.943  ms/op
SqlExpressionBenchmark.querySql       24           5000000        false  avgt    5  1628.440 ± 553.147  ms/op
SqlExpressionBenchmark.querySql       24           5000000        force  avgt    5   748.706 ± 168.330  ms/op
SqlExpressionBenchmark.querySql       25           5000000        false  avgt    5   462.776 ±  38.855  ms/op
SqlExpressionBenchmark.querySql       25           5000000        force  avgt    5   105.150 ±  10.052  ms/op
SqlExpressionBenchmark.querySql       28           5000000        false  avgt    5   655.110 ±  99.443  ms/op
SqlExpressionBenchmark.querySql       28           5000000        force  avgt    5   148.242 ±  11.095  ms/op
SqlExpressionBenchmark.querySql       29           5000000        false  avgt    5   759.389 ±  52.080  ms/op
SqlExpressionBenchmark.querySql       29           5000000        force  avgt    5   164.460 ±  19.277  ms/op

after:

Benchmark                        (query)  (rowsPerSegment)  (vectorize)  Mode  Cnt     Score     Error  Units
SqlExpressionBenchmark.querySql        4           5000000        false  avgt    5   265.765 ±  19.279  ms/op
SqlExpressionBenchmark.querySql        4           5000000        force  avgt    5   105.661 ±   7.709  ms/op
SqlExpressionBenchmark.querySql       10           5000000        false  avgt    5  1234.551 ±  88.114  ms/op
SqlExpressionBenchmark.querySql       10           5000000        force  avgt    5   195.991 ±  10.841  ms/op
SqlExpressionBenchmark.querySql       17           5000000        false  avgt    5   583.678 ±  45.943  ms/op
SqlExpressionBenchmark.querySql       17           5000000        force  avgt    5   140.554 ±  14.233  ms/op
SqlExpressionBenchmark.querySql       24           5000000        false  avgt    5  1576.548 ± 179.227  ms/op
SqlExpressionBenchmark.querySql       24           5000000        force  avgt    5   733.372 ± 110.739  ms/op
SqlExpressionBenchmark.querySql       25           5000000        false  avgt    5   489.465 ±  15.953  ms/op
SqlExpressionBenchmark.querySql       25           5000000        force  avgt    5   107.505 ±   1.933  ms/op
SqlExpressionBenchmark.querySql       28           5000000        false  avgt    5   731.998 ±  64.078  ms/op
SqlExpressionBenchmark.querySql       28           5000000        force  avgt    5   149.572 ±  11.913  ms/op
SqlExpressionBenchmark.querySql       29           5000000        false  avgt    5   801.768 ±  23.847  ms/op
SqlExpressionBenchmark.querySql       29           5000000        force  avgt    5   163.067 ±   9.628  ms/op

@clintropolis
Copy link
Member Author

I've been doing a lot of testing with a mix of historicals, brokers, and routers that do and do not have the changes of this PR to determine how queries will behaving during various upgrade scenarios.

So far, the only issues I have run into are when you have updated brokers but old historicals (which is not the recommended upgrade order), and only for queries with subqueries which have array or complex types, e.g. something like:

SELECT dim1,dim2 FROM foo WHERE ARRAY_CONTAINS((SELECT ARRAY_AGG(DISTINCT dim1) FROM foo WHERE dim1 is not null), dim1)

If you have an updated broker but older historicals, you will observe an error of this form:

Error: Unknown exception

Cannot construct instance of `org.apache.druid.segment.column.ValueType`, problem: No enum constant org.apache.druid.segment.column.ValueType.ARRAY<STRING> at [Source: (org.eclipse.jetty.server.HttpInputOverHTTP); line: -1, column: 128] (through reference chain: org.apache.druid.query.scan.ScanQuery["dataSource"]->org.apache.druid.query.JoinDataSource["right"]->org.apache.druid.query.InlineDataSource["columnTypes"]->java.util.ArrayList[1])

com.fasterxml.jackson.databind.exc.ValueInstantiationException

because the inline datasource which is pushed down to the historicals is not forgiving with type information. I'm not sure if this is a big deal, since we do not recommend updating in this order, and I haven't run into any query shapes that run into issues when following the recommended order, so I think we might be able to get away without having some configuration option to force serialization of arrays and complex types in the old way.

Copy link
Contributor

@jihoonson jihoonson left a comment

Choose a reason for hiding this comment

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

Thank you for a nice PR. I'm looking forward to follow-ups 🙂. The benchmark result seems nice as I don't see any regression. The change in this PR looks good overall, I left one comment about documentation.

I'm not sure if this is a big deal, since we do not recommend updating in this order, and I haven't run into any query shapes that run into issues when following the recommended order, so I think we might be able to get away without having some configuration option to force serialization of arrays and complex types in the old way.

Thanks for doing upgrade tests! It sounds good to me. We can just document this as a known issue when the order doesn't match to the recommended one, maybe in the release notes.

import javax.annotation.Nullable;

@JsonSerialize(using = ToStringSerializer.class)
public class ColumnType extends BaseTypeSignature<ValueType>
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you document somewhere how ColumnType vs ExpressionType vs ColumnCapabilities are different and being used?

@clintropolis clintropolis merged commit 187df58 into apache:master Oct 19, 2021
@clintropolis clintropolis deleted the type-refactor branch October 19, 2021 08:47
@clintropolis clintropolis mentioned this pull request Oct 28, 2021
7 tasks
gianm added a commit to gianm/druid that referenced this pull request Nov 11, 2021
PR apache#11882 introduced a type comparison using ==, but while it was in flight,
another PR apache#11713 changed the type enum to a class. So the comparison should
properly be done with "equals".
clintropolis pushed a commit that referenced this pull request Nov 11, 2021
PR #11882 introduced a type comparison using ==, but while it was in flight,
another PR #11713 changed the type enum to a class. So the comparison should
properly be done with "equals".
@asdf2014
Copy link
Member

asdf2014 commented Dec 1, 2021

Hi, @clintropolis . Thanks a lot for this great patch. May I ask is there any way to solve this compatibility problem, otherwise after adding this patch, all the fields except the __time column will become Complex 😅

image

@clintropolis
Copy link
Member Author

Hi, @clintropolis . Thanks a lot for this great patch. May I ask is there any way to solve this compatibility problem, otherwise after adding this patch, all the fields except the __time column will become Complex 😅

Hmm, could you give some additional details? I don't observe this behavior when trying to reproduce locally, what is the output of INFORMATION_SCHEMA.COLUMNS?
Screen Shot 2021-11-30 at 8 30 50 PM

@asdf2014
Copy link
Member

asdf2014 commented Dec 1, 2021

@clintropolis Thanks for your comment. The reproduce process is that I first built a cluster with Druid 0.22.0, wrote a wikipedia DataSource, and then upgraded the version of the Druid cluster to 0.23.0. As you wish, there is the output of INFORMATION_SCHEMA.COLUMNS:

image

@asdf2014
Copy link
Member

asdf2014 commented Dec 1, 2021

@clintropolis BTW, the commit id of the Druid I used is f6e6ca2 , which is recent enough I believe.

@clintropolis
Copy link
Member Author

Is the cluster fully updated?

I think it would be possible for types to be displayed temporarily as unknown complex if the broker is upgraded before the historical servers because we've changed which segment metadata query field populates the types in the druid schema (#11895), but that is not the recommended upgrade order so I did not go out of my way to support it. It would probably be possible to fallback to the old type field on the broker if the upgrade is done out of order, but ideally the brokers should never be upgraded before historicals.

@asdf2014
Copy link
Member

asdf2014 commented Dec 1, 2021

@clintropolis Yes, the cluster has be updated fully. Cool, I got it. In addition to the upgrade of Broker and Historical nodes, the order of upgrade needs to be considered, what about the other 😅

@asdf2014
Copy link
Member

asdf2014 commented Dec 1, 2021

If the order of upgrading is overlord -> coordinator -> historical -> middle-manager -> broker -> router, can this compatibility problem be avoided

@clintropolis
Copy link
Member Author

Yes, the cluster has be updated fully.

Are the types still incorrect in your cluster? The issue I mentioned I think would be temporary, though I guess it could take some time before the issue corrects itself because it populates reactively, but I think could be resolve immediately by restarting the brokers

If the order of upgrading is overlord -> coordinator -> historical -> middle-manager -> broker -> router, can this compatibility problem be avoided

I did a fair bit of compatibility testing around upgrade order so I believe so, though the bulk of it was before #11895 went in, which is the change that added a new field to the segment metadata query response to populate the types in DruidSchema.

@asdf2014
Copy link
Member

asdf2014 commented Dec 1, 2021

@clintropolis Yes, I just rolling restarted Broker nodes, and the problem disappeared automatically. However, if the Broker is not restarted, the problem will persist. Thanks a lot for your comments. I think that it is useful to make more explanations in relevant documents to remind it, right 😄

@clintropolis
Copy link
Member Author

@clintropolis Yes, I just rolling restarted Broker nodes, and the problem disappeared automatically. However, if the Broker is not restarted, the problem will persist. Thanks a lot for your comments. I think that it is useful to make more explanations in relevant documents to remind it, right 😄

Yeah, i'll make sure that we call out in the release notes that following suggested upgrade order is going to be important for 0.23 if I don't get around to adding a fallback to try to populate the type from the legacy field before the release.

@clintropolis
Copy link
Member Author

I created #12016 to reduce the blast radius of upgrading a cluster out of order after #11895 so it will at least no longer be stuck with COMPLEX types for a very long period or until restart. This doesn't really change plans to call this out in the release notes, since query side upgrading is a bit more important than normal for this one, but after this change at least bad behavior from out of order upgrade will be limited to the normal stuff that happens during out of order upgrades and only affect queries running during the upgrade period (instead of being stuck in an incorrect state for some time after upgrade is complete).

@asdf2014
Copy link
Member

asdf2014 commented Dec 2, 2021

@clintropolis Thanks a lot for completing the improvement so quickly. This is very useful. 👍

clintropolis pushed a commit that referenced this pull request Jan 11, 2022
This change mimics what was done in PR #11917 to
fix the incompatibilities produced by #11713. #11917
fixed it with AggregatorFactory by creating default
methods to allow for extensions built against old
jars to still work.  This does the same for PostAggregator
@abhishekagarwal87 abhishekagarwal87 added this to the 0.23.0 milestone May 11, 2022
sachinsagare pushed a commit to pinterest/druid that referenced this pull request Oct 28, 2022
This change mimics what was done in PR apache#11917 to
fix the incompatibilities produced by apache#11713. apache#11917
fixed it with AggregatorFactory by creating default
methods to allow for extensions built against old
jars to still work.  This does the same for PostAggregator

(cherry picked from commit eb0bae4)
sachinsagare pushed a commit to sachinsagare/druid that referenced this pull request Nov 2, 2022
This change mimics what was done in PR apache#11917 to
fix the incompatibilities produced by apache#11713. apache#11917
fixed it with AggregatorFactory by creating default
methods to allow for extensions built against old
jars to still work.  This does the same for PostAggregator

(cherry picked from commit eb0bae4)
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.

6 participants