1
0

[HUDI-2794] Guarding table service commits within a single lock to commit to both data table and metadata table (#4037)

* Fixing a single lock to commit table services across metadata table and data table

* Addressing comments

* rebasing with master
This commit is contained in:
Sivabalan Narayanan
2021-11-25 14:19:30 -05:00
committed by GitHub
parent b972aa5bf2
commit 7bb90e8caf
4 changed files with 49 additions and 74 deletions

View File

@@ -205,31 +205,19 @@ public class CleanActionExecutor<T extends HoodieRecordPayload, I, K, O> extends
Option.of(timer.endTimer()), Option.of(timer.endTimer()),
cleanStats cleanStats
); );
writeMetadata(metadata); if (!skipLocking) {
this.txnManager.beginTransaction(Option.empty(), Option.empty());
}
writeTableMetadata(metadata);
table.getActiveTimeline().transitionCleanInflightToComplete(inflightInstant, table.getActiveTimeline().transitionCleanInflightToComplete(inflightInstant,
TimelineMetadataUtils.serializeCleanMetadata(metadata)); TimelineMetadataUtils.serializeCleanMetadata(metadata));
LOG.info("Marked clean started on " + inflightInstant.getTimestamp() + " as complete"); LOG.info("Marked clean started on " + inflightInstant.getTimestamp() + " as complete");
return metadata; return metadata;
} catch (IOException e) { } catch (IOException e) {
throw new HoodieIOException("Failed to clean up after commit", e); throw new HoodieIOException("Failed to clean up after commit", e);
} } finally {
} if (!skipLocking) {
this.txnManager.endTransaction();
/**
* Update metadata table if available. Any update to metadata table happens within data table lock.
* @param cleanMetadata instance of {@link HoodieCleanMetadata} to be applied to metadata.
*/
private void writeMetadata(HoodieCleanMetadata cleanMetadata) {
if (config.isMetadataTableEnabled()) {
try {
if (!skipLocking) {
this.txnManager.beginTransaction(Option.empty(), Option.empty());
}
writeTableMetadata(cleanMetadata);
} finally {
if (!skipLocking) {
this.txnManager.endTransaction();
}
} }
} }
} }

View File

@@ -255,30 +255,18 @@ public abstract class BaseRollbackActionExecutor<T extends HoodieRecordPayload,
protected void finishRollback(HoodieInstant inflightInstant, HoodieRollbackMetadata rollbackMetadata) throws HoodieIOException { protected void finishRollback(HoodieInstant inflightInstant, HoodieRollbackMetadata rollbackMetadata) throws HoodieIOException {
try { try {
writeToMetadata(rollbackMetadata); if (!skipLocking) {
this.txnManager.beginTransaction(Option.empty(), Option.empty());
}
writeTableMetadata(rollbackMetadata);
table.getActiveTimeline().transitionRollbackInflightToComplete(inflightInstant, table.getActiveTimeline().transitionRollbackInflightToComplete(inflightInstant,
TimelineMetadataUtils.serializeRollbackMetadata(rollbackMetadata)); TimelineMetadataUtils.serializeRollbackMetadata(rollbackMetadata));
LOG.info("Rollback of Commits " + rollbackMetadata.getCommitsRollback() + " is complete"); LOG.info("Rollback of Commits " + rollbackMetadata.getCommitsRollback() + " is complete");
} catch (IOException e) { } catch (IOException e) {
throw new HoodieIOException("Error executing rollback at instant " + instantTime, e); throw new HoodieIOException("Error executing rollback at instant " + instantTime, e);
} } finally {
} if (!skipLocking) {
this.txnManager.endTransaction();
/**
* Update metadata table if available. Any update to metadata table happens within data table lock.
* @param rollbackMetadata instance of {@link HoodieRollbackMetadata} to be applied to metadata.
*/
private void writeToMetadata(HoodieRollbackMetadata rollbackMetadata) {
if (config.isMetadataTableEnabled()) {
try {
if (!skipLocking) {
this.txnManager.beginTransaction(Option.empty(), Option.empty());
}
writeTableMetadata(rollbackMetadata);
} finally {
if (!skipLocking) {
this.txnManager.endTransaction();
}
} }
} }
} }

View File

@@ -362,12 +362,19 @@ public class HoodieFlinkWriteClient<T extends HoodieRecordPayload> extends
String compactionCommitTime) { String compactionCommitTime) {
this.context.setJobStatus(this.getClass().getSimpleName(), "Collect compaction write status and commit compaction"); this.context.setJobStatus(this.getClass().getSimpleName(), "Collect compaction write status and commit compaction");
List<HoodieWriteStat> writeStats = writeStatuses.stream().map(WriteStatus::getStat).collect(Collectors.toList()); List<HoodieWriteStat> writeStats = writeStatuses.stream().map(WriteStatus::getStat).collect(Collectors.toList());
finalizeWrite(table, compactionCommitTime, writeStats); try {
// commit to data table after committing to metadata table. HoodieInstant compactionInstant = new HoodieInstant(HoodieInstant.State.INFLIGHT, HoodieTimeline.COMPACTION_ACTION, compactionCommitTime);
writeTableMetadata(table, metadata, new HoodieInstant(HoodieInstant.State.INFLIGHT, HoodieTimeline.COMPACTION_ACTION, compactionCommitTime)); this.txnManager.beginTransaction(Option.of(compactionInstant), Option.empty());
LOG.info("Committing Compaction {} finished with result {}.", compactionCommitTime, metadata); finalizeWrite(table, compactionCommitTime, writeStats);
CompactHelpers.getInstance().completeInflightCompaction(table, compactionCommitTime, metadata); // commit to data table after committing to metadata table.
// Do not do any conflict resolution here as we do with regular writes. We take the lock here to ensure all writes to metadata table happens within a
// single lock (single writer). Because more than one write to metadata table will result in conflicts since all of them updates the same partition.
table.getMetadataWriter().ifPresent(w -> w.update(metadata, compactionInstant.getTimestamp(), table.isTableServiceAction(compactionInstant.getAction())));
LOG.info("Committing Compaction {} finished with result {}.", compactionCommitTime, metadata);
CompactHelpers.getInstance().completeInflightCompaction(table, compactionCommitTime, metadata);
} finally {
this.txnManager.endTransaction();
}
if (compactionTimer != null) { if (compactionTimer != null) {
long durationInMs = metrics.getDurationInMs(compactionTimer.stop()); long durationInMs = metrics.getDurationInMs(compactionTimer.stop());
try { try {
@@ -399,19 +406,6 @@ public class HoodieFlinkWriteClient<T extends HoodieRecordPayload> extends
throw new HoodieNotSupportedException("Clustering is not supported yet"); throw new HoodieNotSupportedException("Clustering is not supported yet");
} }
private void writeTableMetadata(HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List<WriteStatus>> table,
HoodieCommitMetadata commitMetadata,
HoodieInstant hoodieInstant) {
try {
this.txnManager.beginTransaction(Option.of(hoodieInstant), Option.empty());
// Do not do any conflict resolution here as we do with regular writes. We take the lock here to ensure all writes to metadata table happens within a
// single lock (single writer). Because more than one write to metadata table will result in conflicts since all of them updates the same partition.
table.getMetadataWriter().ifPresent(w -> w.update(commitMetadata, hoodieInstant.getTimestamp(), table.isTableServiceAction(hoodieInstant.getAction())));
} finally {
this.txnManager.endTransaction();
}
}
@Override @Override
protected HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List<WriteStatus>> getTableAndInitCtx(WriteOperationType operationType, String instantTime) { protected HoodieTable<T, List<HoodieRecord<T>>, List<HoodieKey>, List<WriteStatus>> getTableAndInitCtx(WriteOperationType operationType, String instantTime) {
HoodieTableMetaClient metaClient = createMetaClient(true); HoodieTableMetaClient metaClient = createMetaClient(true);

View File

@@ -313,11 +313,17 @@ public class SparkRDDWriteClient<T extends HoodieRecordPayload> extends
String compactionCommitTime) { String compactionCommitTime) {
this.context.setJobStatus(this.getClass().getSimpleName(), "Collect compaction write status and commit compaction"); this.context.setJobStatus(this.getClass().getSimpleName(), "Collect compaction write status and commit compaction");
List<HoodieWriteStat> writeStats = writeStatuses.map(WriteStatus::getStat).collect(); List<HoodieWriteStat> writeStats = writeStatuses.map(WriteStatus::getStat).collect();
finalizeWrite(table, compactionCommitTime, writeStats); try {
writeTableMetadataForTableServices(table, metadata, new HoodieInstant(HoodieInstant.State.INFLIGHT, HoodieTimeline.COMPACTION_ACTION, compactionCommitTime)); HoodieInstant compactionInstant = new HoodieInstant(HoodieInstant.State.INFLIGHT, HoodieTimeline.COMPACTION_ACTION, compactionCommitTime);
// commit to data table after committing to metadata table. this.txnManager.beginTransaction(Option.of(compactionInstant), Option.empty());
LOG.info("Committing Compaction " + compactionCommitTime + ". Finished with result " + metadata); finalizeWrite(table, compactionCommitTime, writeStats);
CompactHelpers.getInstance().completeInflightCompaction(table, compactionCommitTime, metadata); // commit to data table after committing to metadata table.
writeTableMetadataForTableServices(table, metadata, compactionInstant);
LOG.info("Committing Compaction " + compactionCommitTime + ". Finished with result " + metadata);
CompactHelpers.getInstance().completeInflightCompaction(table, compactionCommitTime, metadata);
} finally {
this.txnManager.endTransaction();
}
WriteMarkersFactory.get(config.getMarkersType(), table, compactionCommitTime) WriteMarkersFactory.get(config.getMarkersType(), table, compactionCommitTime)
.quietDeleteMarkerDir(context, config.getMarkersDeleteParallelism()); .quietDeleteMarkerDir(context, config.getMarkersDeleteParallelism());
if (compactionTimer != null) { if (compactionTimer != null) {
@@ -385,9 +391,11 @@ public class SparkRDDWriteClient<T extends HoodieRecordPayload> extends
throw new HoodieClusteringException("Clustering failed to write to files:" throw new HoodieClusteringException("Clustering failed to write to files:"
+ writeStats.stream().filter(s -> s.getTotalWriteErrors() > 0L).map(s -> s.getFileId()).collect(Collectors.joining(","))); + writeStats.stream().filter(s -> s.getTotalWriteErrors() > 0L).map(s -> s.getFileId()).collect(Collectors.joining(",")));
} }
finalizeWrite(table, clusteringCommitTime, writeStats);
writeTableMetadataForTableServices(table, metadata, new HoodieInstant(HoodieInstant.State.INFLIGHT, HoodieTimeline.REPLACE_COMMIT_ACTION, clusteringCommitTime));
try { try {
HoodieInstant clusteringInstant = new HoodieInstant(HoodieInstant.State.INFLIGHT, HoodieTimeline.REPLACE_COMMIT_ACTION, clusteringCommitTime);
this.txnManager.beginTransaction(Option.of(clusteringInstant), Option.empty());
finalizeWrite(table, clusteringCommitTime, writeStats);
writeTableMetadataForTableServices(table, metadata,clusteringInstant);
// try to save statistics info to hudi // try to save statistics info to hudi
if (config.isDataSkippingEnabled() && config.isLayoutOptimizationEnabled() && !config.getClusteringSortColumns().isEmpty()) { if (config.isDataSkippingEnabled() && config.isLayoutOptimizationEnabled() && !config.getClusteringSortColumns().isEmpty()) {
table.updateStatistics(context, writeStats, clusteringCommitTime, true); table.updateStatistics(context, writeStats, clusteringCommitTime, true);
@@ -398,6 +406,8 @@ public class SparkRDDWriteClient<T extends HoodieRecordPayload> extends
Option.of(metadata.toJsonString().getBytes(StandardCharsets.UTF_8))); Option.of(metadata.toJsonString().getBytes(StandardCharsets.UTF_8)));
} catch (IOException e) { } catch (IOException e) {
throw new HoodieClusteringException("unable to transition clustering inflight to complete: " + clusteringCommitTime, e); throw new HoodieClusteringException("unable to transition clustering inflight to complete: " + clusteringCommitTime, e);
} finally {
this.txnManager.endTransaction();
} }
WriteMarkersFactory.get(config.getMarkersType(), table, clusteringCommitTime) WriteMarkersFactory.get(config.getMarkersType(), table, clusteringCommitTime)
.quietDeleteMarkerDir(context, config.getMarkersDeleteParallelism()); .quietDeleteMarkerDir(context, config.getMarkersDeleteParallelism());
@@ -415,16 +425,11 @@ public class SparkRDDWriteClient<T extends HoodieRecordPayload> extends
} }
private void writeTableMetadataForTableServices(HoodieTable<T, JavaRDD<HoodieRecord<T>>, JavaRDD<HoodieKey>, JavaRDD<WriteStatus>> table, HoodieCommitMetadata commitMetadata, private void writeTableMetadataForTableServices(HoodieTable<T, JavaRDD<HoodieRecord<T>>, JavaRDD<HoodieKey>, JavaRDD<WriteStatus>> table, HoodieCommitMetadata commitMetadata,
HoodieInstant hoodieInstant) { HoodieInstant hoodieInstant) {
try { boolean isTableServiceAction = table.isTableServiceAction(hoodieInstant.getAction());
this.txnManager.beginTransaction(Option.of(hoodieInstant), Option.empty()); // Do not do any conflict resolution here as we do with regular writes. We take the lock here to ensure all writes to metadata table happens within a
boolean isTableServiceAction = table.isTableServiceAction(hoodieInstant.getAction()); // single lock (single writer). Because more than one write to metadata table will result in conflicts since all of them updates the same partition.
// Do not do any conflict resolution here as we do with regular writes. We take the lock here to ensure all writes to metadata table happens within a table.getMetadataWriter().ifPresent(w -> w.update(commitMetadata, hoodieInstant.getTimestamp(), isTableServiceAction));
// single lock (single writer). Because more than one write to metadata table will result in conflicts since all of them updates the same partition.
table.getMetadataWriter().ifPresent(w -> w.update(commitMetadata, hoodieInstant.getTimestamp(), isTableServiceAction));
} finally {
this.txnManager.endTransaction();
}
} }
@Override @Override