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

Native Avro File Reader #17221

Merged
merged 5 commits into from Jun 21, 2023

Conversation

jklamer
Copy link
Member

@jklamer jklamer commented Apr 24, 2023

Description

Add classes and utilities to replace Hive library AvroSerde deserializing functionality by creating a Trino native Avro page source.

The changes are largely split into two module:

  • trino-hive-formats General use avro library that will allow connectors to define custom page building code or use default behavior. Responsible for resolution of read/write schemas and file bytes decoding.
  • trino-hive The Hive plugins usage of the above library to ensure backwards compatibility with current implementation along with custom schema sourcing code. Includes custom solutions for handling reader projection use cases.

Main features:

  • Column masking pushdown to decrease memory footprint for most queries
  • Reading bytes directly from file into pages without Object creation or casting in majority of cases

Main classes for review:

  • io.trino.hive.formats.avro.AvroPageDataReader:
    • A cross between org.apache.avro.io.FastReaderBuilder and io.trino.hive.formats.line.json.JsonDeserializer
    • impelents org.apache.avro.io.DatumReader
    • Accumulates pages as the Avro Library reads through the file. Handling schema read resolution (reordering, skips, defaults, promotions)
  • io.trino.plugin.hive.avro.AvroHiveFileUtils: handles HiveType -> Avro Schema mapping in place of org.apache.hadoop.hive.serde2.avro.AvroSerDe
  • io.trino.plugin.hive.avro.HiveAvroTypeManager: customizes the format library with special HiveType and Timestamp transformations in a backwards compatible way. Flattens behavior currently located in a number of files into functions it supplies to the library.

Additional context and related issues

This is part of a broader effort to optionalize the Trino dependency on hive/hadoop.

Release notes

( ) This is not user-visible or docs only and no release notes are required.
( ) Release notes are required, please propose a release note for me.
(X) Release notes are required, with the following suggested text:

New Avro Native Reader enabled by default in trine-hive plugin.
Disable with config avro.native-reader.enabled=false or using session property SET SESSION <catalog_name>.avro_native_reader_enabled = false if experiencing issues with this new reader.

@cla-bot cla-bot bot added the cla-signed label Apr 24, 2023
@jklamer jklamer requested review from dain and electrum April 24, 2023 23:54
@github-actions github-actions bot added hive Hive connector tests:hive labels Apr 25, 2023
@jklamer jklamer force-pushed the jklamer/AvroHiveFileFormatReader branch 2 times, most recently from 1e57d63 to afde27b Compare April 25, 2023 18:13
@jklamer jklamer force-pushed the jklamer/AvroHiveFileFormatReader branch 2 times, most recently from 5033fb9 to 487d3a5 Compare April 28, 2023 15:17
Copy link
Member

@dain dain left a comment

Choose a reason for hiding this comment

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

I'm still reviewing but here are some initial comments

}
case ENUM -> VarcharType.VARCHAR;
case ARRAY -> new ArrayType(typeFromAvro(schema.getElementType(), avroTypeManager, enclosingRecords));
case MAP -> new MapType(VarcharType.VARCHAR, typeFromAvro(schema.getValueType(), avroTypeManager, enclosingRecords), new TypeOperators());
Copy link
Member

Choose a reason for hiding this comment

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

You should to use the TypeOperators from ConnectorContext.getTypeManager().getTypeOperators(), but if that is too difficult you could create a static final. The TypeOperators are effectiely generating code with method handles so if you are constantly creating new instances, the code will always be cold (a.k.a., slow)

Copy link
Member Author

Choose a reason for hiding this comment

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

I might need some help wiring this up the way you're envisioning

Copy link
Member

Choose a reason for hiding this comment

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

reminder we need to resolve this one

@@ -113,6 +123,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.trino</groupId>
<artifactId>trino-main</artifactId>
Copy link
Member

Choose a reason for hiding this comment

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

Do we really need this dependency? I would prefer if we did not need this in this module.

Copy link
Member Author

Choose a reason for hiding this comment

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

Im using it for io.trino.block.BlockAssertions can I move that class to SPI?

Copy link
Member Author

Choose a reason for hiding this comment

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

should I make own version?

Copy link
Member

Choose a reason for hiding this comment

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

I think we should move it to the SPI. When you do that, switch the TestNG assertEquals usage to AssertJ, since we're trying to move away from TestNG in new code.

Copy link
Member Author

Choose a reason for hiding this comment

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

Getting the rolling IntelliJ errors on the refactor. I'll ping you offline on what you think we should do here.

Field ColorType.COLOR, referenced in method BlockAssertions.createColorRepeatBlock(int, int), will not be accessible in module trino-spi  Field TestingConnectorSession.SESSION, referenced in method BlockAssertions.getOnlyValue(Type, Block), will not be accessible in module trino-spi  Field IpAddressType.IPADDRESS, referenced in method BlockAssertions.createRandomBlockForType(Type, int, float), will not be accessible in module trino-spi

@jklamer jklamer force-pushed the jklamer/AvroHiveFileFormatReader branch 4 times, most recently from 5feca4b to d101b9e Compare May 2, 2023 21:29
Copy link
Member

@dain dain left a comment

Choose a reason for hiding this comment

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

I reviewed Add Native Avro to Page code with connector defined mappings commit. My comments are mostly stylistic, and the code looks good.

I am concerned about the testing. Most of the tests in this commit are just verifying the correct number of rows and that no exceptions were thrown. I think we need basic tests for every type and a few complex combinations of types.

protected static TrinoInputFile createWrittenFileWithSchema(int count, Schema schema)
throws IOException
{
Iterator<Object> randomData = new RandomData(schema, count).iterator();
Copy link
Member

Choose a reason for hiding this comment

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

this is really nice

@jklamer jklamer force-pushed the jklamer/AvroHiveFileFormatReader branch 2 times, most recently from 45aa870 to 9752755 Compare May 9, 2023 23:13
for (String columnName : maskedColumns) {
Schema.Field field = tableSchema.getField(columnName);
if (Objects.isNull(field)) {
continue;
Copy link
Member Author

Choose a reason for hiding this comment

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

Check

}

private static BlockBuildingDecoder createBlockBuildingDecoderForAction(Resolver.Action action, AvroTypeManager typeManager)
throws AvroTypeException
Copy link
Contributor

Choose a reason for hiding this comment

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

I probably wouldn't recommend using the resolver. The only thing that needs to be resolved is the order to read struct fields, skip, and then how to fill in defaults. That's actually pretty simple and can be done directly. Check out our implementation in Python.

Basically, that creates a list of (optional position, reader) pairs by doing the following:

  1. Loop over the file schema's record and create a pair for every field
    • If there is a corresponding field in the read schema, use that field's position from the read schema
    • If the field is not in the read schema, use empty
  2. Loop over the read schema and add any needed pairs
    • If the field was in the file schema, skip it
    • If the field was missing, create a pair from the field's position in the read schema and a constant reader with the field's default

Then to read, you just loop over those pairs. If the position is present, get the value from the reader and set that position in the record. If the position is not present, call skip to consume and discard the bytes.

I think this is overall way cleaner, although I like what you're doing here and using the resolver to create your reader tree, rather than resolving in each record.

Copy link
Member Author

@jklamer jklamer Jun 7, 2023

Choose a reason for hiding this comment

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

Oh that's a good point. I definitely don't love the data model of the Actions returned for this or other use cases. When I was implementing I was just kinda peeling back layers of Generic Datum Reader and FastDatumReader until I didn't need to anymore and just pretty much copied the use from those classes. The reason I might keep using it is that is also provides Reader and Writer Union resolution steps that I originally wan't super confident on implementing but need (I imagine I could but would need to think on it, I think it would involve load bearing exceptions). From my understanding after looking at the pyiceberg code, Iceberg doesn't support non-trivial(optional) unions and doesn't need to do union resolution correct?

Copy link
Member

Choose a reason for hiding this comment

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

Do you want to address this now or in a follow up?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll ping @rdblue offline and we'll get this sorted.

@electrum
Copy link
Member

Capitalize the commit titles: https://cbea.ms/git-commit/

}

private static BlockBuildingDecoder createBlockBuildingDecoderForAction(Resolver.Action action, AvroTypeManager typeManager)
throws AvroTypeException
Copy link
Member

Choose a reason for hiding this comment

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

Do you want to address this now or in a follow up?

{
BlockBuilder entryBuilder = builder.beginBlockEntry();
long entriesInBlock = decoder.readMapStart();
// TODO need to filter out all but last value for key?
Copy link
Member

Choose a reason for hiding this comment

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

What would happen if we encounter a file with duplicate keys?

logicalType = fromSchemaIgnoreInvalid(schema);
break;
case LOCAL_TIMESTAMP_MICROS + LOCAL_TIMESTAMP_MILLIS:
log.debug("Logical type " + typeName + " not currently supported by by Trino");
Copy link
Member

Choose a reason for hiding this comment

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

Is this something we plan to support? I'm wondering how the log message might be useful.

Copy link
Member Author

Choose a reason for hiding this comment

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

I would actually like to talk with you about this. I want to make sure I understand our TimestampWithTimeZoneType enough to know if maps directly to this logical type or can be coerced in a way that makes sense.

@jklamer jklamer force-pushed the jklamer/AvroHiveFileFormatReader branch from e5f4d4c to 13183c0 Compare June 21, 2023 16:59
@jklamer jklamer force-pushed the jklamer/AvroHiveFileFormatReader branch from 13183c0 to 35e959d Compare June 21, 2023 19:54
@electrum electrum merged commit 6f5d455 into trinodb:master Jun 21, 2023
68 checks passed
@jklamer
Copy link
Member Author

jklamer commented Jun 21, 2023

@b-slim @weijiii , be aware this has been merged. Let me know if you find any issues with it while using.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla-signed hive Hive connector
Development

Successfully merging this pull request may close these issues.

None yet

4 participants