Skip to content
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6ddb4fc
t
Karl-WangSK Sep 11, 2020
21f0d89
Description: feat: We met performance issue with foo module recently,…
Karl-WangSK Sep 11, 2020
c5f47c7
Merge remote-tracking branch 'update_stream/master'
Karl-WangSK Sep 15, 2020
835fce8
feat: update preCombine
Karl-WangSK Sep 20, 2020
b548678
add test
Karl-WangSK Sep 20, 2020
e6efb8c
update
Karl-WangSK Sep 22, 2020
f9e3df9
preCombine all HoodieRecords and update all fields according to order…
Karl-WangSK Sep 23, 2020
ec6e8a2
update
Karl-WangSK Oct 1, 2020
a9d4831
update
Karl-WangSK Oct 1, 2020
de06b54
Merge branch 'master' into HUDI-1284
Karl-WangSK Oct 1, 2020
bf5f22b
update
Karl-WangSK Oct 1, 2020
ac7940d
Merge branch 'HUDI-1284' of github.com:Karl-WangSK/hudi into HUDI-1284
Karl-WangSK Oct 1, 2020
54e2a08
update
Karl-WangSK Oct 1, 2020
11323b2
Update DataSourceUtils.java
Karl-WangSK Oct 1, 2020
94e9c5c
update
Karl-WangSK Oct 3, 2020
b574874
Merge branch 'master' into HUDI-1284
Karl-WangSK Oct 10, 2020
f678964
Update AbstractWriteHelper.java
Karl-WangSK Oct 10, 2020
cca47ce
update
Karl-WangSK Oct 13, 2020
2e3e799
update
Karl-WangSK Oct 13, 2020
a6464f6
update
Karl-WangSK Oct 13, 2020
02a2dfc
update
Karl-WangSK Oct 14, 2020
f142420
update
Karl-WangSK Oct 22, 2020
1382b04
update
Karl-WangSK Oct 22, 2020
cf1de28
update
Karl-WangSK Oct 23, 2020
a75f293
add serializableSchema
Karl-WangSK Nov 11, 2020
c855aef
Merge branch 'master' into HUDI-1284
Karl-WangSK Nov 11, 2020
d8959f6
update
Karl-WangSK Nov 11, 2020
10aa16c
Merge branch 'HUDI-1284' of github.com:Karl-WangSK/hudi into HUDI-1284
Karl-WangSK Nov 11, 2020
427d0f3
update
Karl-WangSK Nov 11, 2020
bf9446f
update
Karl-WangSK Nov 11, 2020
d44f9e6
update
Karl-WangSK Nov 11, 2020
2c8c08d
update
Karl-WangSK Nov 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.apache.hudi.client.common.HoodieEngineContext;
import org.apache.hudi.common.model.HoodieRecordPayload;
import org.apache.hudi.common.util.Option;
import org.apache.hudi.exception.HoodieUpsertException;
import org.apache.hudi.index.HoodieIndex;
import org.apache.hudi.table.HoodieTable;
Expand All @@ -39,10 +40,23 @@ public HoodieWriteMetadata<O> write(String instantTime,
int shuffleParallelism,
BaseCommitActionExecutor<T, I, K, O, R> executor,
boolean performTagging) {
return write(instantTime, inputRecords, context, table, shouldCombine, shuffleParallelism,
Option.empty(), executor, performTagging);
}

public HoodieWriteMetadata<O> write(String instantTime,
I inputRecordsRDD,
HoodieEngineContext context,
HoodieTable<T, I, K, O> table,
boolean shouldCombine,
int shuffleParallelism,
Option<String> schema,
BaseCommitActionExecutor<T, I, K, O, R> executor,
boolean performTagging) {
try {
// De-dupe/merge if needed
I dedupedRecords =
combineOnCondition(shouldCombine, inputRecords, shuffleParallelism, table);
combineOnCondition(shouldCombine, inputRecordsRDD, shuffleParallelism, table, schema);

Instant lookupBegin = Instant.now();
I taggedRecords = dedupedRecords;
Expand Down Expand Up @@ -70,8 +84,8 @@ private I tag(
}

public I combineOnCondition(
boolean condition, I records, int parallelism, HoodieTable<T, I, K, O> table) {
return condition ? deduplicateRecords(records, table, parallelism) : records;
boolean condition, I records, int parallelism, HoodieTable<T, I, K, O> table, Option<String> schema) {
return condition ? deduplicateRecords(records, table, parallelism, schema) : records;
}

/**
Expand All @@ -82,10 +96,10 @@ public I combineOnCondition(
* @return Collection of HoodieRecord already be deduplicated
*/
public I deduplicateRecords(
I records, HoodieTable<T, I, K, O> table, int parallelism) {
return deduplicateRecords(records, table.getIndex(), parallelism);
I records, HoodieTable<T, I, K, O> table, int parallelism, Option<String> schema) {
return deduplicateRecords(records, table.getIndex(), parallelism, schema);
}

public abstract I deduplicateRecords(
I records, HoodieIndex<T, I, K, O> index, int parallelism);
I records, HoodieIndex<T, I, K, O> index, int parallelism, Option<String> schema);
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public HoodieWriteMetadata<JavaRDD<WriteStatus>> bulkInsert(JavaRDD<HoodieRecord

if (performDedupe) {
dedupedRecords = (JavaRDD<HoodieRecord<T>>) SparkWriteHelper.newInstance().combineOnCondition(config.shouldCombineBeforeInsert(), inputRecords,
config.getBulkInsertShuffleParallelism(), table);
config.getBulkInsertShuffleParallelism(), table, Option.empty());
}

final JavaRDD<HoodieRecord<T>> repartitionedRecords;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.apache.hudi.common.model.HoodieRecord;
import org.apache.hudi.common.model.HoodieRecordPayload;
import org.apache.hudi.common.model.WriteOperationType;
import org.apache.hudi.common.util.Option;
import org.apache.hudi.config.HoodieWriteConfig;
import org.apache.hudi.table.HoodieTable;

Expand All @@ -44,6 +45,7 @@ public SparkUpsertCommitActionExecutor(HoodieSparkEngineContext context,
@Override
public HoodieWriteMetadata<JavaRDD<WriteStatus>> execute() {
return SparkWriteHelper.newInstance().write(instantTime, inputRecordsRDD, context, table,
config.shouldCombineBeforeUpsert(), config.getUpsertShuffleParallelism(), this, true);
config.shouldCombineBeforeUpsert(), config.getUpsertShuffleParallelism(),
Comment thread
Karl-WangSK marked this conversation as resolved.
Option.of(config.getSchema()), this, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@

package org.apache.hudi.table.action.commit;

import org.apache.avro.Schema;
import org.apache.hudi.client.WriteStatus;
import org.apache.hudi.common.model.HoodieKey;
import org.apache.hudi.common.model.HoodieRecord;
import org.apache.hudi.common.model.HoodieRecordPayload;
import org.apache.hudi.common.model.UpdatePrecombineAvroPayload;
import org.apache.hudi.common.util.Option;
import org.apache.hudi.index.HoodieIndex;

import org.apache.spark.api.java.JavaRDD;
Expand All @@ -33,8 +36,8 @@
*
* @param <T>
*/
public class SparkWriteHelper<T extends HoodieRecordPayload,R> extends AbstractWriteHelper<T, JavaRDD<HoodieRecord<T>>,
JavaRDD<HoodieKey>, JavaRDD<WriteStatus>, R> {
public class SparkWriteHelper<T extends HoodieRecordPayload, R> extends AbstractWriteHelper<T, JavaRDD<HoodieRecord<T>>,
JavaRDD<HoodieKey>, JavaRDD<WriteStatus>, R> {
private SparkWriteHelper() {
}

Expand All @@ -49,7 +52,7 @@ public static SparkWriteHelper newInstance() {
@Override
public JavaRDD<HoodieRecord<T>> deduplicateRecords(JavaRDD<HoodieRecord<T>> records,
HoodieIndex<T, JavaRDD<HoodieRecord<T>>, JavaRDD<HoodieKey>, JavaRDD<WriteStatus>> index,
int parallelism) {
int parallelism, Option<String> schema) {
boolean isIndexingGlobal = index.isGlobal();
return records.mapToPair(record -> {
HoodieKey hoodieKey = record.getKey();
Expand All @@ -58,7 +61,14 @@ public JavaRDD<HoodieRecord<T>> deduplicateRecords(JavaRDD<HoodieRecord<T>> reco
return new Tuple2<>(key, record);
}).reduceByKey((rec1, rec2) -> {
@SuppressWarnings("unchecked")
T reducedData = (T) rec1.getData().preCombine(rec2.getData());
T reducedData;
//To prevent every records from parsing schema
if (rec2.getData() instanceof UpdatePrecombineAvroPayload) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can this prevent old payload impl from calling preCombine(_, schema) ?

reducedData = schema.isPresent() ? (T) rec1.getData().preCombine(rec2.getData(), new Schema.Parser().parse(schema.get()))
: (T) rec1.getData().preCombine(rec2.getData());
} else {
reducedData = (T) rec1.getData().preCombine(rec2.getData());
}
// we cannot allow the user to change the key or partitionPath, since that will affect
// everything
// so pick it from one of the records.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.hudi.common.model.HoodieRecord;
import org.apache.hudi.common.model.HoodieRecordPayload;
import org.apache.hudi.common.model.WriteOperationType;
import org.apache.hudi.common.util.Option;
import org.apache.hudi.config.HoodieWriteConfig;
import org.apache.hudi.table.HoodieTable;

Expand All @@ -44,6 +45,7 @@ public SparkUpsertDeltaCommitActionExecutor(HoodieSparkEngineContext context,
@Override
public HoodieWriteMetadata execute() {
return SparkWriteHelper.newInstance().write(instantTime, inputRecordsRDD, context, table,
config.shouldCombineBeforeUpsert(), config.getUpsertShuffleParallelism(),this, true);
config.shouldCombineBeforeUpsert(), config.getUpsertShuffleParallelism(),
Option.of(config.getSchema()), this, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,15 @@ private void testDeduplication(
// Global dedup should be done based on recordKey only
HoodieIndex index = mock(HoodieIndex.class);
when(index.isGlobal()).thenReturn(true);
List<HoodieRecord<RawTripTestPayload>> dedupedRecs = SparkWriteHelper.newInstance().deduplicateRecords(records, index, 1).collect();
List<HoodieRecord<RawTripTestPayload>> dedupedRecs = SparkWriteHelper.newInstance().deduplicateRecords(records, index, 1,
null).collect();
assertEquals(1, dedupedRecs.size());
assertNodupesWithinPartition(dedupedRecs);

// non-Global dedup should be done based on both recordKey and partitionPath
index = mock(HoodieIndex.class);
when(index.isGlobal()).thenReturn(false);
dedupedRecs = SparkWriteHelper.newInstance().deduplicateRecords(records, index, 1).collect();
dedupedRecs = SparkWriteHelper.newInstance().deduplicateRecords(records, index, 1, null).collect();
assertEquals(2, dedupedRecs.size());
assertNodupesWithinPartition(dedupedRecs);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public abstract class BaseAvroPayload implements Serializable {
/**
* Avro data extracted from the source converted to bytes.
*/
public final byte[] recordBytes;
public byte[] recordBytes;

/**
* For purposes of preCombining.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,19 @@ public interface HoodieRecordPayload<T extends HoodieRecordPayload> extends Seri
* When more than one HoodieRecord have the same HoodieKey, this function combines them before attempting to
* insert/upsert (if combining turned on in HoodieClientConfig).
*/
@PublicAPIMethod(maturity = ApiMaturityLevel.STABLE)
@Deprecated
T preCombine(T another);

/**
* When more than one HoodieRecord have the same HoodieKey, this function combines all fields(which is not null)
Comment thread
Karl-WangSK marked this conversation as resolved.
* before attempting to insert/upsert (if combining turned on in HoodieClientConfig).
*
*/
@PublicAPIMethod(maturity = ApiMaturityLevel.STABLE)
default T preCombine(T another, Schema schema) throws IOException {
Comment thread
Karl-WangSK marked this conversation as resolved.
return preCombine(another);
}

/**
* This methods lets you write custom merging/combining logic to produce new values as a function of current value on
* storage and whats contained in this object.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.hudi.common.model;

import org.apache.avro.Schema;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.generic.IndexedRecord;
import org.apache.hudi.common.util.Option;

import java.io.IOException;
import java.util.List;

/**
* subclass of OverwriteWithLatestAvroPayload.
*
* Extract the function precombine of UpdatePrecombineAvroPayload and combineAndGetUpdateValue of OverwriteNonDefaultsWithLatestAvroPayload.
* Which means When more than one HoodieRecord have the same HoodieKey, this function will combine all fields(which is not null)
* Before attempting to insert/upsert And when insert/upsert into storage.
*/
public class PartialAvroPayload extends UpdatePrecombineAvroPayload {
public PartialAvroPayload(GenericRecord record, Comparable orderingVal) {
super(record, orderingVal);
}

public PartialAvroPayload(Option<GenericRecord> record) {
super(record);
}

@Override
public Option<IndexedRecord> combineAndGetUpdateValue(IndexedRecord currentValue, Schema schema) throws IOException {
Option<IndexedRecord> recordOption = getInsertValue(schema);
if (!recordOption.isPresent()) {
return Option.empty();
}

GenericRecord insertRecord = (GenericRecord) recordOption.get();
GenericRecord currentRecord = (GenericRecord) currentValue;

if (isDeleteRecord(insertRecord)) {
return Option.empty();
} else {
List<Schema.Field> fields = schema.getFields();
fields.forEach(field -> {
Object value = insertRecord.get(field.name());
Object defaultValue = field.defaultVal();
if (!overwriteField(value, defaultValue)) {
currentRecord.put(field.name(), value);
}
});
return Option.of(currentRecord);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.hudi.common.model;

import org.apache.avro.Schema;
import org.apache.avro.generic.GenericRecord;
import org.apache.hudi.avro.HoodieAvroUtils;
import org.apache.hudi.common.util.Option;

import java.io.IOException;
import java.util.List;

/**
* subclass of OverwriteWithLatestAvroPayload.
*
* <ol>
* <li>preCombine - When more than one HoodieRecord have the same HoodieKey, this function combines all fields(which is not null)
* before attempting to insert/upsert.
* eg: 1)
* Before:
* id name age ts
* 1 Karl null 0.0
* 1 null 18 0.0
* After:
* id name age ts
* 1 Karl 18 0.0
* </ol>
*/
public class UpdatePrecombineAvroPayload extends OverwriteWithLatestAvroPayload {
public UpdatePrecombineAvroPayload(GenericRecord record, Comparable orderingVal) {
super(record, orderingVal);
}

public UpdatePrecombineAvroPayload(Option<GenericRecord> record) {
super(record);
}

@Override
public OverwriteWithLatestAvroPayload preCombine(OverwriteWithLatestAvroPayload another, Schema schema) throws IOException {
// pick the payload with greatest ordering value and aggregate all the fields,choosing the
// value that is not null
GenericRecord thisValue = HoodieAvroUtils.bytesToAvro(this.recordBytes, schema);
GenericRecord anotherValue = HoodieAvroUtils.bytesToAvro(another.recordBytes, schema);
List<Schema.Field> fields = schema.getFields();

if (another.orderingVal.compareTo(orderingVal) > 0) {
GenericRecord anotherRoc = combineAllFields(fields, anotherValue, thisValue);
another.recordBytes = HoodieAvroUtils.avroToBytes(anotherRoc);
return another;
} else {
GenericRecord thisRoc = combineAllFields(fields, thisValue, anotherValue);
this.recordBytes = HoodieAvroUtils.avroToBytes(thisRoc);
return this;
}
}

public GenericRecord combineAllFields(List<Schema.Field> fields, GenericRecord priorRec, GenericRecord secPriorRoc) {
for (int i = 0; i < fields.size(); i++) {
Object priorValue = priorRec.get(fields.get(i).name());
Object secPriorValue = secPriorRoc.get(fields.get(i).name());
Object defaultVal = fields.get(i).defaultVal();
if (overwriteField(priorValue, defaultVal) && !overwriteField(secPriorValue, defaultVal)) {
priorRec.put(fields.get(i).name(), secPriorValue);
}
}
return priorRec;
}
}
Loading