-
Notifications
You must be signed in to change notification settings - Fork 2.5k
[HUDI-1632] Supports merge on read write mode for Flink writer #2593
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,9 +37,11 @@ | |
| import org.apache.hudi.common.util.CommitUtils; | ||
| import org.apache.hudi.common.util.Option; | ||
| import org.apache.hudi.config.HoodieWriteConfig; | ||
| import org.apache.hudi.exception.HoodieCommitException; | ||
| import org.apache.hudi.exception.HoodieNotSupportedException; | ||
| import org.apache.hudi.index.FlinkHoodieIndex; | ||
| import org.apache.hudi.index.HoodieIndex; | ||
| import org.apache.hudi.io.FlinkAppendHandle; | ||
| import org.apache.hudi.io.FlinkCreateHandle; | ||
| import org.apache.hudi.io.FlinkMergeHandle; | ||
| import org.apache.hudi.io.HoodieWriteHandle; | ||
|
|
@@ -48,14 +50,19 @@ | |
| import org.apache.hudi.table.HoodieFlinkTable; | ||
| import org.apache.hudi.table.HoodieTable; | ||
| import org.apache.hudi.table.action.HoodieWriteMetadata; | ||
| import org.apache.hudi.table.action.compact.FlinkCompactHelpers; | ||
| import org.apache.hudi.table.upgrade.FlinkUpgradeDowngrade; | ||
|
|
||
| import com.codahale.metrics.Timer; | ||
| import org.apache.hadoop.conf.Configuration; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.io.IOException; | ||
| import java.text.ParseException; | ||
| import java.util.Comparator; | ||
| import java.util.HashMap; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.stream.Collectors; | ||
|
|
@@ -64,6 +71,8 @@ | |
| public class HoodieFlinkWriteClient<T extends HoodieRecordPayload> extends | ||
| AbstractHoodieWriteClient<T, List<HoodieRecord<T>>, List<HoodieKey>, List<WriteStatus>> { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger(HoodieFlinkWriteClient.class); | ||
|
|
||
| /** | ||
| * FileID to write handle mapping in order to record the write handles for each file group, | ||
| * so that we can append the mini-batch data buffer incrementally. | ||
|
|
@@ -127,21 +136,9 @@ public List<WriteStatus> upsert(List<HoodieRecord<T>> records, String instantTim | |
| table.validateUpsertSchema(); | ||
| preWrite(instantTime, WriteOperationType.UPSERT); | ||
| final HoodieRecord<T> record = records.get(0); | ||
| final HoodieRecordLocation loc = record.getCurrentLocation(); | ||
| final String fileID = loc.getFileId(); | ||
| final boolean isInsert = loc.getInstantTime().equals("I"); | ||
| final HoodieWriteHandle<?, ?, ?, ?> writeHandle; | ||
| if (bucketToHandles.containsKey(fileID)) { | ||
| writeHandle = bucketToHandles.get(fileID); | ||
| } else { | ||
| // create the write handle if not exists | ||
| writeHandle = isInsert | ||
| ? new FlinkCreateHandle<>(getConfig(), instantTime, table, record.getPartitionPath(), | ||
| fileID, table.getTaskContextSupplier()) | ||
| : new FlinkMergeHandle<>(getConfig(), instantTime, table, records.listIterator(), record.getPartitionPath(), | ||
| fileID, table.getTaskContextSupplier()); | ||
| bucketToHandles.put(fileID, writeHandle); | ||
| } | ||
| final boolean isDelta = table.getMetaClient().getTableType().equals(HoodieTableType.MERGE_ON_READ); | ||
| final HoodieWriteHandle<?, ?, ?, ?> writeHandle = getOrCreateWriteHandle(record, isDelta, getConfig(), | ||
| instantTime, table, record.getPartitionPath(), records.listIterator()); | ||
| HoodieWriteMetadata<List<WriteStatus>> result = ((HoodieFlinkTable<T>) table).upsert(context, writeHandle, instantTime, records); | ||
| if (result.getIndexLookupDuration().isPresent()) { | ||
| metrics.updateIndexMetrics(LOOKUP_STR, result.getIndexLookupDuration().get().toMillis()); | ||
|
|
@@ -160,7 +157,12 @@ public List<WriteStatus> insert(List<HoodieRecord<T>> records, String instantTim | |
| getTableAndInitCtx(WriteOperationType.INSERT, instantTime); | ||
| table.validateUpsertSchema(); | ||
| preWrite(instantTime, WriteOperationType.INSERT); | ||
| HoodieWriteMetadata<List<WriteStatus>> result = table.insert(context, instantTime, records); | ||
| // create the write handle if not exists | ||
| final HoodieRecord<T> record = records.get(0); | ||
| final boolean isDelta = table.getMetaClient().getTableType().equals(HoodieTableType.MERGE_ON_READ); | ||
| final HoodieWriteHandle<?, ?, ?, ?> writeHandle = getOrCreateWriteHandle(record, isDelta, getConfig(), | ||
| instantTime, table, record.getPartitionPath(), records.listIterator()); | ||
| HoodieWriteMetadata<List<WriteStatus>> result = ((HoodieFlinkTable<T>) table).insert(context, writeHandle, instantTime, records); | ||
| if (result.getIndexLookupDuration().isPresent()) { | ||
| metrics.updateIndexMetrics(LOOKUP_STR, result.getIndexLookupDuration().get().toMillis()); | ||
| } | ||
|
|
@@ -207,13 +209,40 @@ protected List<WriteStatus> postWrite(HoodieWriteMetadata<List<WriteStatus>> res | |
| } | ||
|
|
||
| @Override | ||
| public void commitCompaction(String compactionInstantTime, List<WriteStatus> writeStatuses, Option<Map<String, String>> extraMetadata) throws IOException { | ||
| throw new HoodieNotSupportedException("Compaction is not supported yet"); | ||
| public void commitCompaction( | ||
| String compactionInstantTime, | ||
| List<WriteStatus> writeStatuses, | ||
| Option<Map<String, String>> extraMetadata) throws IOException { | ||
| HoodieFlinkTable<T> table = HoodieFlinkTable.create(config, (HoodieFlinkEngineContext) context); | ||
| HoodieCommitMetadata metadata = FlinkCompactHelpers.newInstance().createCompactionMetadata( | ||
| table, compactionInstantTime, writeStatuses, config.getSchema()); | ||
| extraMetadata.ifPresent(m -> m.forEach(metadata::addMetadata)); | ||
| completeCompaction(metadata, writeStatuses, table, compactionInstantTime); | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems it just contains commitCompaction and planScheduler, where is the real compact logic ? I see there exists a CompactFunction and CompactCommitSink , but I don't find where to call these.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can take
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand the planscheduler to generate plan, and then commitCompaction will read the plan to see if it should commit, but I'm confused to connect all these together, when to trigger the real compact ? if we use CompactFunction , how to understand the async here ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for explanation. |
||
| @Override | ||
| protected void completeCompaction(HoodieCommitMetadata metadata, List<WriteStatus> writeStatuses, HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List<WriteStatus>> table, String compactionCommitTime) { | ||
| throw new HoodieNotSupportedException("Compaction is not supported yet"); | ||
| public void completeCompaction( | ||
| HoodieCommitMetadata metadata, | ||
| List<WriteStatus> writeStatuses, | ||
| HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List<WriteStatus>> table, | ||
| String compactionCommitTime) { | ||
| this.context.setJobStatus(this.getClass().getSimpleName(), "Collect compaction write status and commit compaction"); | ||
| List<HoodieWriteStat> writeStats = writeStatuses.stream().map(WriteStatus::getStat).collect(Collectors.toList()); | ||
| finalizeWrite(table, compactionCommitTime, writeStats); | ||
| LOG.info("Committing Compaction {} finished with result {}.", compactionCommitTime, metadata); | ||
| FlinkCompactHelpers.newInstance().completeInflightCompaction(table, compactionCommitTime, metadata); | ||
|
|
||
| if (compactionTimer != null) { | ||
| long durationInMs = metrics.getDurationInMs(compactionTimer.stop()); | ||
| try { | ||
| metrics.updateCommitMetrics(HoodieActiveTimeline.COMMIT_FORMATTER.parse(compactionCommitTime).getTime(), | ||
| durationInMs, metadata, HoodieActiveTimeline.COMPACTION_ACTION); | ||
| } catch (ParseException e) { | ||
| throw new HoodieCommitException("Commit time is not of valid format. Failed to commit compaction " | ||
| + config.getBasePath() + " at time " + compactionCommitTime, e); | ||
| } | ||
| } | ||
| LOG.info("Compacted successfully on commit " + compactionCommitTime); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -244,6 +273,46 @@ public void cleanHandles() { | |
| this.bucketToHandles.clear(); | ||
| } | ||
|
|
||
| /** | ||
| * Get or create a new write handle in order to reuse the file handles. | ||
| * | ||
| * @param record The first record in the bucket | ||
| * @param isDelta Whether the table is in MOR mode | ||
| * @param config Write config | ||
| * @param instantTime The instant time | ||
| * @param table The table | ||
| * @param partitionPath Partition path | ||
| * @param recordItr Record iterator | ||
| * @return Existing write handle or create a new one | ||
| */ | ||
| private HoodieWriteHandle<?, ?, ?, ?> getOrCreateWriteHandle( | ||
| HoodieRecord<T> record, | ||
| boolean isDelta, | ||
| HoodieWriteConfig config, | ||
| String instantTime, | ||
| HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List<WriteStatus>> table, | ||
| String partitionPath, | ||
| Iterator<HoodieRecord<T>> recordItr) { | ||
| final HoodieRecordLocation loc = record.getCurrentLocation(); | ||
| final String fileID = loc.getFileId(); | ||
| if (bucketToHandles.containsKey(fileID)) { | ||
| return bucketToHandles.get(fileID); | ||
| } | ||
| final HoodieWriteHandle<?, ?, ?, ?> writeHandle; | ||
| if (isDelta) { | ||
|
yanghua marked this conversation as resolved.
Outdated
|
||
| writeHandle = new FlinkAppendHandle<>(config, instantTime, table, partitionPath, fileID, recordItr, | ||
| table.getTaskContextSupplier()); | ||
| } else if (loc.getInstantTime().equals("I")) { | ||
| writeHandle = new FlinkCreateHandle<>(config, instantTime, table, partitionPath, | ||
| fileID, table.getTaskContextSupplier()); | ||
| } else { | ||
| writeHandle = new FlinkMergeHandle<>(config, instantTime, table, recordItr, partitionPath, | ||
| fileID, table.getTaskContextSupplier()); | ||
| } | ||
| this.bucketToHandles.put(fileID, writeHandle); | ||
| return writeHandle; | ||
| } | ||
|
|
||
| private HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List<WriteStatus>> getTableAndInitCtx(HoodieTableMetaClient metaClient, WriteOperationType operationType) { | ||
| if (operationType == WriteOperationType.DELETE) { | ||
| setWriteSchemaForDeletes(metaClient); | ||
|
|
@@ -305,4 +374,16 @@ public void transitionRequestedToInflight(String tableType, String inFlightInsta | |
| activeTimeline.transitionRequestedToInflight(requested, Option.empty(), | ||
| config.shouldAllowMultiWriteOnSameInstant()); | ||
| } | ||
|
|
||
| public void rollbackInflightCompaction(HoodieInstant inflightInstant) { | ||
| HoodieFlinkTable<T> table = HoodieFlinkTable.create(config, (HoodieFlinkEngineContext) context); | ||
| HoodieTimeline pendingCompactionTimeline = table.getActiveTimeline().filterPendingCompactionTimeline(); | ||
| if (pendingCompactionTimeline.containsInstant(inflightInstant)) { | ||
| rollbackInflightCompaction(inflightInstant, table); | ||
| } | ||
| } | ||
|
|
||
| public HoodieFlinkTable<T> getHoodieTable() { | ||
| return HoodieFlinkTable.create(config, (HoodieFlinkEngineContext) context); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In previous PRs, you used
instantTimeof HoodieRecordLocation to judge update operation, right? So, let keep the same?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the default logic for Hoodie, we should keep it, the
FlinkAppendHandlealready overrides the behavior.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's in the common package, my problem.