Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class HoodieCompactionConfig extends DefaultHoodieConfig {
public static final String INLINE_COMPACT_PROP = "hoodie.compact.inline";
// Run a compaction every N delta commits
public static final String INLINE_COMPACT_NUM_DELTA_COMMITS_PROP = "hoodie.compact.inline.max.delta.commits";
public static final String INLINE_COMPACT_ELAPSED_TIME_PROP = "hoodie.compact.inline.max.delta.time";
Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
public static final String INLINE_COMPACT_NUM_DELTA_COMMITS_ENABLED_PROP = "hoodie.compact.inline.max.delta.commits.enable";
public static final String INLINE_COMPACT_ELAPSED_TIME_ENABLED_PROP = "hoodie.compact.inline.max.delta.time.enable";
public static final String CLEANER_FILE_VERSIONS_RETAINED_PROP = "hoodie.cleaner.fileversions.retained";
public static final String CLEANER_COMMITS_RETAINED_PROP = "hoodie.cleaner.commits.retained";
public static final String CLEANER_INCREMENTAL_MODE = "hoodie.cleaner.incremental.mode";
Expand Down Expand Up @@ -109,6 +112,9 @@ public class HoodieCompactionConfig extends DefaultHoodieConfig {
private static final String DEFAULT_INLINE_COMPACT = "false";
private static final String DEFAULT_INCREMENTAL_CLEANER = "true";
private static final String DEFAULT_INLINE_COMPACT_NUM_DELTA_COMMITS = "5";
private static final String DEFAULT_INLINE_COMPACT_ELAPSED_TIME = String.valueOf(60 * 60);
Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
private static final String DEFAULT_INLINE_COMPACT_NUM_DELTA_COMMITS_ENABLED = "true";
private static final String DEFAULT_INLINE_COMPACT_ELAPSED_TIME_ENABLED = "false";
private static final String DEFAULT_CLEANER_FILE_VERSIONS_RETAINED = "3";
private static final String DEFAULT_CLEANER_COMMITS_RETAINED = "10";
private static final String DEFAULT_MAX_COMMITS_TO_KEEP = "30";
Expand Down Expand Up @@ -235,6 +241,21 @@ public Builder withMaxNumDeltaCommitsBeforeCompaction(int maxNumDeltaCommitsBefo
return this;
}

public Builder withMaxDeltaTimeBeforeCompaction(int maxDeltaTimeBeforeCompaction) {
props.setProperty(INLINE_COMPACT_ELAPSED_TIME_PROP, String.valueOf(maxDeltaTimeBeforeCompaction));
return this;
}

public Builder withMaxNumDeltaCommitsBeforeCompactionEnabled(boolean maxNumDeltaCommitsBeforeCompactionEnabled) {
props.setProperty(INLINE_COMPACT_NUM_DELTA_COMMITS_ENABLED_PROP, String.valueOf(maxNumDeltaCommitsBeforeCompactionEnabled));
return this;
}

public Builder withMaxDeltaTimeBeforeCompactionEnabled(boolean withMaxDeltaTimeBeforeCompactionEnabled) {
props.setProperty(INLINE_COMPACT_ELAPSED_TIME_ENABLED_PROP, String.valueOf(withMaxDeltaTimeBeforeCompactionEnabled));
return this;
}

public Builder withCompactionLazyBlockReadEnabled(Boolean compactionLazyBlockReadEnabled) {
props.setProperty(COMPACTION_LAZY_BLOCK_READ_ENABLED_PROP, String.valueOf(compactionLazyBlockReadEnabled));
return this;
Expand Down Expand Up @@ -271,6 +292,12 @@ public HoodieCompactionConfig build() {
DEFAULT_INLINE_COMPACT);
setDefaultOnCondition(props, !props.containsKey(INLINE_COMPACT_NUM_DELTA_COMMITS_PROP),
INLINE_COMPACT_NUM_DELTA_COMMITS_PROP, DEFAULT_INLINE_COMPACT_NUM_DELTA_COMMITS);
setDefaultOnCondition(props, !props.containsKey(INLINE_COMPACT_ELAPSED_TIME_PROP),
INLINE_COMPACT_ELAPSED_TIME_PROP, DEFAULT_INLINE_COMPACT_ELAPSED_TIME);
setDefaultOnCondition(props, !props.containsKey(INLINE_COMPACT_ELAPSED_TIME_ENABLED_PROP),
INLINE_COMPACT_ELAPSED_TIME_ENABLED_PROP, DEFAULT_INLINE_COMPACT_ELAPSED_TIME_ENABLED);
setDefaultOnCondition(props, !props.containsKey(INLINE_COMPACT_NUM_DELTA_COMMITS_ENABLED_PROP),
INLINE_COMPACT_NUM_DELTA_COMMITS_ENABLED_PROP, DEFAULT_INLINE_COMPACT_NUM_DELTA_COMMITS_ENABLED);
setDefaultOnCondition(props, !props.containsKey(CLEANER_POLICY_PROP), CLEANER_POLICY_PROP,
DEFAULT_CLEANER_POLICY);
setDefaultOnCondition(props, !props.containsKey(CLEANER_FILE_VERSIONS_RETAINED_PROP),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.apache.hudi.config;

import org.apache.hadoop.hbase.io.compress.Compression;
Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
import org.apache.hudi.client.WriteStatus;
import org.apache.hudi.client.bootstrap.BootstrapMode;
import org.apache.hudi.common.config.DefaultHoodieConfig;
Expand All @@ -36,7 +37,6 @@
import org.apache.hudi.metrics.datadog.DatadogHttpClient.ApiSite;
import org.apache.hudi.table.action.compact.strategy.CompactionStrategy;

import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.parquet.hadoop.metadata.CompressionCodecName;

import javax.annotation.concurrent.Immutable;
Expand Down Expand Up @@ -397,10 +397,22 @@ public boolean isInlineCompaction() {
return Boolean.parseBoolean(props.getProperty(HoodieCompactionConfig.INLINE_COMPACT_PROP));
}

public boolean getInlineCompactDeltaElapsedEnabled() {
return Boolean.parseBoolean(props.getProperty(HoodieCompactionConfig.INLINE_COMPACT_ELAPSED_TIME_ENABLED_PROP));
}

public boolean getInlineCompactDeltaNumCommitEnabled() {
return Boolean.parseBoolean(props.getProperty(HoodieCompactionConfig.INLINE_COMPACT_NUM_DELTA_COMMITS_ENABLED_PROP));
}

public int getInlineCompactDeltaCommitMax() {
return Integer.parseInt(props.getProperty(HoodieCompactionConfig.INLINE_COMPACT_NUM_DELTA_COMMITS_PROP));
}

public int getInlineCompactDeltaElapsedTimeMax() {
return Integer.parseInt(props.getProperty(HoodieCompactionConfig.INLINE_COMPACT_ELAPSED_TIME_PROP));
}

public CompactionStrategy getCompactionStrategy() {
return ReflectionUtils.loadClass(props.getProperty(HoodieCompactionConfig.COMPACTION_STRATEGY_PROP));
}
Expand Down Expand Up @@ -468,7 +480,7 @@ public String getClusteringExecutionStrategyClass() {
public long getClusteringMaxBytesInGroup() {
return Long.parseLong(props.getProperty(HoodieClusteringConfig.CLUSTERING_MAX_BYTES_PER_GROUP));
}
Comment thread
Karl-WangSK marked this conversation as resolved.

Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
public long getClusteringSmallFileLimit() {
return Long.parseLong(props.getProperty(HoodieClusteringConfig.CLUSTERING_PLAN_SMALL_FILE_LIMIT));
}
Expand All @@ -484,7 +496,7 @@ public long getClusteringTargetFileMaxBytes() {
public int getTargetPartitionsForClustering() {
return Integer.parseInt(props.getProperty(HoodieClusteringConfig.CLUSTERING_TARGET_PARTITIONS));
}
Comment thread
Karl-WangSK marked this conversation as resolved.

Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
public String getClusteringSortColumns() {
return props.getProperty(HoodieClusteringConfig.CLUSTERING_SORT_COLUMNS_PROPERTY);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
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.table.timeline.HoodieActiveTimeline;
import org.apache.hudi.common.table.timeline.HoodieInstant;
import org.apache.hudi.common.table.timeline.HoodieTimeline;
import org.apache.hudi.common.table.view.SyncableFileSystemView;
import org.apache.hudi.common.util.Option;
import org.apache.hudi.common.util.collection.Pair;
Expand All @@ -37,6 +39,7 @@
import org.apache.spark.api.java.JavaRDD;

import java.io.IOException;
import java.text.ParseException;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -60,17 +63,32 @@ protected HoodieCompactionPlan scheduleCompaction() {
LOG.info("Checking if compaction needs to be run on " + config.getBasePath());
Option<HoodieInstant> lastCompaction = table.getActiveTimeline().getCommitTimeline()
.filterCompletedInstants().lastInstant();
String lastCompactionTs = "0";
HoodieTimeline deltaCommits = table.getActiveTimeline().getDeltaCommitTimeline();
String lastCompactionTs;
int deltaCommitsSinceLastCompaction;
if (lastCompaction.isPresent()) {
lastCompactionTs = lastCompaction.get().getTimestamp();
deltaCommitsSinceLastCompaction = deltaCommits.findInstantsAfter(lastCompactionTs, Integer.MAX_VALUE).countInstants();
} else {
lastCompactionTs = deltaCommits.firstInstant().get().getTimestamp();
Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
deltaCommitsSinceLastCompaction = deltaCommits.findInstantsAfterOrEquals(lastCompactionTs, Integer.MAX_VALUE).countInstants();
}

int deltaCommitsSinceLastCompaction = table.getActiveTimeline().getDeltaCommitTimeline()
.findInstantsAfter(lastCompactionTs, Integer.MAX_VALUE).countInstants();
if (config.getInlineCompactDeltaCommitMax() > deltaCommitsSinceLastCompaction) {
LOG.info("Not scheduling compaction as only " + deltaCommitsSinceLastCompaction
+ " delta commits was found since last compaction " + lastCompactionTs + ". Waiting for "
+ config.getInlineCompactDeltaCommitMax());
// judge if we need to compact according to num delta commits and time elapsed
boolean numCommitEnabled = config.getInlineCompactDeltaNumCommitEnabled();
boolean timeEnabled = config.getInlineCompactDeltaElapsedEnabled();
boolean compactable;
if (numCommitEnabled && !timeEnabled) {
compactable = config.getInlineCompactDeltaCommitMax() > deltaCommitsSinceLastCompaction;
} else if (!numCommitEnabled && timeEnabled) {
compactable = parseToTimestamp(lastCompactionTs) + config.getInlineCompactDeltaElapsedTimeMax() > parseToTimestamp(instantTime);
} else {
compactable = config.getInlineCompactDeltaCommitMax() > deltaCommitsSinceLastCompaction
&& parseToTimestamp(lastCompactionTs) + config.getInlineCompactDeltaElapsedTimeMax() > parseToTimestamp(instantTime);
}
Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
if (compactable) {
LOG.info(String.format("Not scheduling compaction as only %s delta commits was found since last compaction %s."
Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
+ "Waiting for %s,or %sms elapsed time need since last compaction %s.", deltaCommitsSinceLastCompaction,
lastCompactionTs, config.getInlineCompactDeltaCommitMax(), config.getInlineCompactDeltaElapsedTimeMax(), lastCompactionTs));
return new HoodieCompactionPlan();
}

Expand All @@ -90,4 +108,13 @@ protected HoodieCompactionPlan scheduleCompaction() {
}
}

public Long parseToTimestamp(String time) {
Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
long timestamp;
try {
timestamp = HoodieActiveTimeline.COMMIT_FORMATTER.parse(time).getTime() / 1000;
} catch (ParseException e) {
throw new HoodieCompactionException(e.getMessage(), e);
}
return timestamp;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.apache.hudi.common.table.HoodieTableMetaClient;
import org.apache.hudi.common.table.timeline.HoodieActiveTimeline;
import org.apache.hudi.common.table.timeline.HoodieTimeline;
import org.apache.hudi.common.util.CollectionUtils;
import org.apache.hudi.config.HoodieCompactionConfig;
import org.apache.hudi.config.HoodieWriteConfig;
import org.junit.jupiter.api.Test;
Expand All @@ -39,21 +38,25 @@

public class TestInlineCompaction extends CompactionTestBase {

private HoodieWriteConfig getConfigForInlineCompaction(int maxDeltaCommits) {
private HoodieWriteConfig getConfigForInlineCompaction(int maxDeltaCommits, int maxDeltaTime) {
return getConfigBuilder(false)
.withCompactionConfig(HoodieCompactionConfig.newBuilder()
.withInlineCompaction(true).withMaxNumDeltaCommitsBeforeCompaction(maxDeltaCommits).build())
.withInlineCompaction(true)
.withMaxNumDeltaCommitsBeforeCompaction(maxDeltaCommits)
.withMaxDeltaTimeBeforeCompaction(maxDeltaTime)
.withMaxDeltaTimeBeforeCompactionEnabled(true).build())
.build();
}

@Test
public void testCompactionIsNotScheduledEarly() throws Exception {
// Given: make two commits
HoodieWriteConfig cfg = getConfigForInlineCompaction(3);
HoodieWriteConfig cfg = getConfigForInlineCompaction(3, 60);
try (SparkRDDWriteClient<?> writeClient = getHoodieWriteClient(cfg)) {
List<HoodieRecord> records = dataGen.generateInserts("000", 100);
List<HoodieRecord> records = dataGen.generateInserts(HoodieActiveTimeline.createNewInstantTime(), 100);
HoodieReadClient readClient = getHoodieReadClient(cfg.getBasePath());
runNextDeltaCommits(writeClient, readClient, Arrays.asList("000", "001"), records, cfg, true, new ArrayList<>());
List<String> instants = IntStream.range(0, 2).mapToObj(i -> HoodieActiveTimeline.createNewInstantTime()).collect(Collectors.toList());
runNextDeltaCommits(writeClient, readClient, instants, records, cfg, true, new ArrayList<>());
HoodieTableMetaClient metaClient = new HoodieTableMetaClient(hadoopConf, cfg.getBasePath());

// Then: ensure no compaction is executedm since there are only 2 delta commits
Expand All @@ -62,9 +65,9 @@ public void testCompactionIsNotScheduledEarly() throws Exception {
}

@Test
public void testSuccessfulCompaction() throws Exception {
public void testSuccessfulCompactionBasedOnNumCommits() throws Exception {
// Given: make three commits
HoodieWriteConfig cfg = getConfigForInlineCompaction(3);
HoodieWriteConfig cfg = getConfigForInlineCompaction(3, 60);
List<String> instants = IntStream.range(0, 2).mapToObj(i -> HoodieActiveTimeline.createNewInstantTime()).collect(Collectors.toList());

try (SparkRDDWriteClient<?> writeClient = getHoodieWriteClient(cfg)) {
Expand All @@ -85,32 +88,95 @@ public void testSuccessfulCompaction() throws Exception {
}

@Test
public void testCompactionRetryOnFailure() throws Exception {
public void testSuccessfulCompactionBasedOnTime() throws Exception {
// Given: make one commit
HoodieWriteConfig cfg = getConfigForInlineCompaction(5, 10);

try (SparkRDDWriteClient<?> writeClient = getHoodieWriteClient(cfg)) {
String instantTime = HoodieActiveTimeline.createNewInstantTime();
List<HoodieRecord> records = dataGen.generateInserts(instantTime, 10);
HoodieReadClient readClient = getHoodieReadClient(cfg.getBasePath());
runNextDeltaCommits(writeClient, readClient, Arrays.asList(instantTime), records, cfg, true, new ArrayList<>());

// after 10s, that will trigger compaction
Thread.sleep(10000);
Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
String finalInstant = HoodieActiveTimeline.createNewInstantTime();
HoodieTableMetaClient metaClient = new HoodieTableMetaClient(hadoopConf, cfg.getBasePath());
createNextDeltaCommit(finalInstant, dataGen.generateUpdates(finalInstant, 100), writeClient, metaClient, cfg, false);

// Then: ensure the file slices are compacted as per policy
metaClient = new HoodieTableMetaClient(hadoopConf, cfg.getBasePath());
assertEquals(3, metaClient.getActiveTimeline().getCommitsAndCompactionTimeline().countInstants());
assertEquals(HoodieTimeline.COMMIT_ACTION, metaClient.getActiveTimeline().lastInstant().get().getAction());
}
}

@Test
public void testCompactionRetryOnFailureBasedOnNumCommits() throws Exception {
// Given: two commits, schedule compaction and its failed/in-flight
HoodieWriteConfig cfg = getConfigBuilder(false)
.withCompactionConfig(HoodieCompactionConfig.newBuilder()
.withInlineCompaction(false).withMaxNumDeltaCommitsBeforeCompaction(1).build())
.build();
List<String> instants = CollectionUtils.createImmutableList("000", "001");
List<String> instants = IntStream.range(0, 2).mapToObj(i -> HoodieActiveTimeline.createNewInstantTime()).collect(Collectors.toList());
String instantTime2;
try (SparkRDDWriteClient<?> writeClient = getHoodieWriteClient(cfg)) {
List<HoodieRecord> records = dataGen.generateInserts(instants.get(0), 100);
HoodieReadClient readClient = getHoodieReadClient(cfg.getBasePath());
runNextDeltaCommits(writeClient, readClient, instants, records, cfg, true, new ArrayList<>());
// Schedule compaction 002, make it in-flight (simulates inline compaction failing)
scheduleCompaction("002", writeClient, cfg);
moveCompactionFromRequestedToInflight("002", cfg);
// Schedule compaction instant2, make it in-flight (simulates inline compaction failing)
instantTime2 = HoodieActiveTimeline.createNewInstantTime();
scheduleCompaction(instantTime2, writeClient, cfg);
moveCompactionFromRequestedToInflight(instantTime2, cfg);
}

// When: a third commit happens
HoodieWriteConfig inlineCfg = getConfigForInlineCompaction(2);
HoodieWriteConfig inlineCfg = getConfigForInlineCompaction(2, 60);
String instantTime3 = HoodieActiveTimeline.createNewInstantTime();
try (SparkRDDWriteClient<?> writeClient = getHoodieWriteClient(inlineCfg)) {
HoodieTableMetaClient metaClient = new HoodieTableMetaClient(hadoopConf, cfg.getBasePath());
createNextDeltaCommit(instantTime3, dataGen.generateUpdates(instantTime3, 100), writeClient, metaClient, inlineCfg, false);
}

// Then: 1 delta commit is done, the failed compaction is retried
metaClient = new HoodieTableMetaClient(hadoopConf, cfg.getBasePath());
assertEquals(4, metaClient.getActiveTimeline().getCommitsAndCompactionTimeline().countInstants());
assertEquals(instantTime2, metaClient.getActiveTimeline().getCommitTimeline().filterCompletedInstants().firstInstant().get().getTimestamp());
}

@Test
public void testCompactionRetryOnFailureBasedOnTime() throws Exception {
// Given: two commits, schedule compaction and its failed/in-flight
HoodieWriteConfig cfg = getConfigBuilder(false)
.withCompactionConfig(HoodieCompactionConfig.newBuilder()
.withInlineCompaction(false).withMaxDeltaTimeBeforeCompaction(5)
.withMaxDeltaTimeBeforeCompactionEnabled(true).build())
.build();
String instantTime;
List<String> instants = IntStream.range(0, 2).mapToObj(i -> HoodieActiveTimeline.createNewInstantTime()).collect(Collectors.toList());
try (SparkRDDWriteClient<?> writeClient = getHoodieWriteClient(cfg)) {
List<HoodieRecord> records = dataGen.generateInserts(instants.get(0), 100);
HoodieReadClient readClient = getHoodieReadClient(cfg.getBasePath());
runNextDeltaCommits(writeClient, readClient, instants, records, cfg, true, new ArrayList<>());
// Schedule compaction instantTime, make it in-flight (simulates inline compaction failing)
Thread.sleep(10000);
Comment thread
Karl-WangSK marked this conversation as resolved.
Outdated
instantTime = HoodieActiveTimeline.createNewInstantTime();
scheduleCompaction(instantTime, writeClient, cfg);
moveCompactionFromRequestedToInflight(instantTime, cfg);
}

// When: commit happens after 10s
HoodieWriteConfig inlineCfg = getConfigForInlineCompaction(5, 10);
String instantTime2;
try (SparkRDDWriteClient<?> writeClient = getHoodieWriteClient(inlineCfg)) {
HoodieTableMetaClient metaClient = new HoodieTableMetaClient(hadoopConf, cfg.getBasePath());
createNextDeltaCommit("003", dataGen.generateUpdates("003", 100), writeClient, metaClient, inlineCfg, false);
instantTime2 = HoodieActiveTimeline.createNewInstantTime();
createNextDeltaCommit(instantTime2, dataGen.generateUpdates(instantTime2, 10), writeClient, metaClient, inlineCfg, false);
}

// Then: 1 delta commit is done, the failed compaction is retried
metaClient = new HoodieTableMetaClient(hadoopConf, cfg.getBasePath());
assertEquals(4, metaClient.getActiveTimeline().getCommitsAndCompactionTimeline().countInstants());
assertEquals("002", metaClient.getActiveTimeline().getCommitTimeline().filterCompletedInstants().firstInstant().get().getTimestamp());
assertEquals(instantTime, metaClient.getActiveTimeline().getCommitTimeline().filterCompletedInstants().firstInstant().get().getTimestamp());
}
}