This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 9559ca685 [core] Optimize multiple commit to reduce conflicts (#4286)
9559ca685 is described below
commit 9559ca6857f398cc9d37817c60ed4170dc607fc4
Author: Jingsong Lee <[email protected]>
AuthorDate: Wed Oct 9 19:42:09 2024 +0800
[core] Optimize multiple commit to reduce conflicts (#4286)
---
.../paimon/operation/FileStoreCommitImpl.java | 360 ++++++++++++++-------
.../java/org/apache/paimon/utils/ObjectsFile.java | 2 +-
.../apache/paimon/operation/FileDeletionTest.java | 1 +
.../paimon/operation/FileStoreCommitTest.java | 2 +
4 files changed, 247 insertions(+), 118 deletions(-)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
index 0b19cae17..34f928cc2 100644
---
a/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
+++
b/paimon-core/src/main/java/org/apache/paimon/operation/FileStoreCommitImpl.java
@@ -49,6 +49,7 @@ import org.apache.paimon.table.BucketMode;
import org.apache.paimon.table.sink.CommitCallback;
import org.apache.paimon.table.sink.CommitMessage;
import org.apache.paimon.table.sink.CommitMessageImpl;
+import org.apache.paimon.table.source.ScanMode;
import org.apache.paimon.types.RowType;
import org.apache.paimon.utils.FileStorePathFactory;
import org.apache.paimon.utils.IOUtils;
@@ -251,7 +252,7 @@ public class FileStoreCommitImpl implements FileStoreCommit
{
Map<String, String> properties,
boolean checkAppendFiles) {
if (LOG.isDebugEnabled()) {
- LOG.debug("Ready to commit\n" + committable.toString());
+ LOG.debug("Ready to commit\n{}", committable.toString());
}
long started = System.nanoTime();
@@ -712,29 +713,40 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
String branchName,
@Nullable String statsFileName) {
int cnt = 0;
+ RetryResult retryResult = null;
while (true) {
Snapshot latestSnapshot = snapshotManager.latestSnapshot();
cnt++;
if (cnt >= commitMaxRetries) {
+ if (retryResult != null) {
+ retryResult.cleanAll();
+ }
throw new RuntimeException(
String.format(
"Commit failed after %s attempts, there maybe
exist commit conflicts between multiple jobs.",
commitMaxRetries));
}
- if (tryCommitOnce(
- tableFiles,
- changelogFiles,
- indexFiles,
- identifier,
- watermark,
- logOffsets,
- commitKind,
- latestSnapshot,
- conflictCheck,
- branchName,
- statsFileName)) {
+
+ CommitResult result =
+ tryCommitOnce(
+ retryResult,
+ tableFiles,
+ changelogFiles,
+ indexFiles,
+ identifier,
+ watermark,
+ logOffsets,
+ commitKind,
+ latestSnapshot,
+ conflictCheck,
+ branchName,
+ statsFileName);
+
+ if (result.isSuccess()) {
break;
}
+
+ retryResult = (RetryResult) result;
}
return cnt;
}
@@ -763,6 +775,7 @@ public class FileStoreCommitImpl implements FileStoreCommit
{
List<ManifestEntry> currentEntries =
scan.withSnapshot(latestSnapshot)
.withPartitionFilter(partitionFilter)
+ .withKind(ScanMode.ALL)
.plan()
.files();
for (ManifestEntry entry : currentEntries) {
@@ -789,27 +802,36 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
changesWithOverwrite.addAll(changes);
indexChangesWithOverwrite.addAll(indexFiles);
- if (tryCommitOnce(
- changesWithOverwrite,
- Collections.emptyList(),
- indexChangesWithOverwrite,
- identifier,
- watermark,
- logOffsets,
- Snapshot.CommitKind.OVERWRITE,
- latestSnapshot,
- mustConflictCheck(),
- branchName,
- null)) {
+ CommitResult result =
+ tryCommitOnce(
+ null,
+ changesWithOverwrite,
+ Collections.emptyList(),
+ indexChangesWithOverwrite,
+ identifier,
+ watermark,
+ logOffsets,
+ Snapshot.CommitKind.OVERWRITE,
+ latestSnapshot,
+ mustConflictCheck(),
+ branchName,
+ null);
+
+ if (result.isSuccess()) {
break;
}
+
+ // TODO optimize OVERWRITE too
+ RetryResult retryResult = (RetryResult) result;
+ retryResult.cleanAll();
}
return cnt;
}
@VisibleForTesting
- boolean tryCommitOnce(
- List<ManifestEntry> tableFiles,
+ CommitResult tryCommitOnce(
+ @Nullable RetryResult retryResult,
+ List<ManifestEntry> deltaFiles,
List<ManifestEntry> changelogFiles,
List<IndexManifestEntry> indexFiles,
long identifier,
@@ -830,7 +852,7 @@ public class FileStoreCommitImpl implements FileStoreCommit
{
if (LOG.isDebugEnabled()) {
LOG.debug("Ready to commit table files to snapshot {}",
newSnapshotId);
- for (ManifestEntry entry : tableFiles) {
+ for (ManifestEntry entry : deltaFiles) {
LOG.debug(" * {}", entry);
}
LOG.debug("Ready to commit changelog to snapshot {}",
newSnapshotId);
@@ -839,30 +861,56 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
}
}
+ List<SimpleFileEntry> baseDataFiles = new ArrayList<>();
if (latestSnapshot != null &&
conflictCheck.shouldCheck(latestSnapshot.id())) {
// latestSnapshotId is different from the snapshot id we've
checked for conflicts,
// so we have to check again
- noConflictsOrFail(latestSnapshot.commitUser(), latestSnapshot,
tableFiles);
+ try {
+ List<BinaryRow> changedPartitions =
+ deltaFiles.stream()
+ .map(ManifestEntry::partition)
+ .distinct()
+ .collect(Collectors.toList());
+ if (retryResult != null && retryResult.latestSnapshot != null)
{
+ baseDataFiles = new ArrayList<>(retryResult.baseDataFiles);
+ List<SimpleFileEntry> incremental =
+ readIncrementalChanges(
+ retryResult.latestSnapshot,
latestSnapshot, changedPartitions);
+ if (!incremental.isEmpty()) {
+ baseDataFiles.addAll(incremental);
+ baseDataFiles = new
ArrayList<>(FileEntry.mergeEntries(baseDataFiles));
+ }
+ } else {
+ baseDataFiles =
+
readAllEntriesFromChangedPartitions(latestSnapshot, changedPartitions);
+ }
+ noConflictsOrFail(
+ latestSnapshot.commitUser(),
+ baseDataFiles,
+ SimpleFileEntry.from(deltaFiles));
+ } catch (Exception e) {
+ if (retryResult != null) {
+ retryResult.cleanAll();
+ }
+ throw e;
+ }
}
Snapshot newSnapshot;
- String previousChangesListName = null;
- String newChangesListName = null;
- String changelogListName = null;
- String newIndexManifest = null;
- List<ManifestFileMeta> oldMetas = new ArrayList<>();
- List<ManifestFileMeta> newMetas = new ArrayList<>();
- List<ManifestFileMeta> changelogMetas = new ArrayList<>();
+ String baseManifestList = null;
+ String deltaManifestList = null;
+ String changelogManifestList = null;
+ String oldIndexManifest = null;
+ String indexManifest = null;
+ List<ManifestFileMeta> mergeBeforeManifests = new ArrayList<>();
+ List<ManifestFileMeta> mergeAfterManifests = new ArrayList<>();
try {
long previousTotalRecordCount = 0L;
Long currentWatermark = watermark;
- String previousIndexManifest = null;
if (latestSnapshot != null) {
previousTotalRecordCount =
scan.totalRecordCount(latestSnapshot);
- List<ManifestFileMeta> previousManifests =
- manifestList.readDataManifests(latestSnapshot);
// read all previous manifest files
- oldMetas.addAll(previousManifests);
+ mergeBeforeManifests =
manifestList.readDataManifests(latestSnapshot);
// read the last snapshot to complete the bucket's offsets
when logOffsets does not
// contain all buckets
Map<Integer, Long> latestLogOffsets =
latestSnapshot.logOffsets();
@@ -876,41 +924,49 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
? latestWatermark
: Math.max(currentWatermark,
latestWatermark);
}
- previousIndexManifest = latestSnapshot.indexManifest();
+ oldIndexManifest = latestSnapshot.indexManifest();
}
- // merge manifest files with changes
- newMetas.addAll(
+
+ // try to merge old manifest files to create base manifest list
+ mergeAfterManifests =
ManifestFileMerger.merge(
- oldMetas,
+ mergeBeforeManifests,
manifestFile,
manifestTargetSize.getBytes(),
manifestMergeMinCount,
manifestFullCompactionSize.getBytes(),
partitionType,
- manifestReadParallelism));
- previousChangesListName = manifestList.write(newMetas);
+ manifestReadParallelism);
+ baseManifestList = manifestList.write(mergeAfterManifests);
// the added records subtract the deleted records from
- long deltaRecordCount = recordCountAdd(tableFiles) -
recordCountDelete(tableFiles);
+ long deltaRecordCount = recordCountAdd(deltaFiles) -
recordCountDelete(deltaFiles);
long totalRecordCount = previousTotalRecordCount +
deltaRecordCount;
- // write new changes into manifest files
- List<ManifestFileMeta> newChangesManifests =
manifestFile.write(tableFiles);
- newMetas.addAll(newChangesManifests);
- newChangesListName = manifestList.write(newChangesManifests);
+ boolean rewriteIndexManifest = true;
+ if (retryResult != null) {
+ deltaManifestList = retryResult.deltaManifestList;
+ changelogManifestList = retryResult.changelogManifestList;
+ if (Objects.equals(oldIndexManifest,
retryResult.oldIndexManifest)) {
+ rewriteIndexManifest = false;
+ indexManifest = retryResult.newIndexManifest;
+ LOG.info("Reusing index manifest {} for retry.",
indexManifest);
+ } else {
+ cleanIndexManifest(retryResult.oldIndexManifest,
retryResult.newIndexManifest);
+ }
+ } else {
+ // write new delta files into manifest files
+ deltaManifestList =
manifestList.write(manifestFile.write(deltaFiles));
- // write changelog into manifest files
- if (!changelogFiles.isEmpty()) {
- changelogMetas.addAll(manifestFile.write(changelogFiles));
- changelogListName = manifestList.write(changelogMetas);
+ // write changelog into manifest files
+ if (!changelogFiles.isEmpty()) {
+ changelogManifestList =
manifestList.write(manifestFile.write(changelogFiles));
+ }
}
- // write new index manifest
- String indexManifest =
- indexManifestFile.writeIndexFiles(
- previousIndexManifest, indexFiles, bucketMode);
- if (!Objects.equals(indexManifest, previousIndexManifest)) {
- newIndexManifest = indexManifest;
+ if (rewriteIndexManifest) {
+ indexManifest =
+ indexManifestFile.writeIndexFiles(oldIndexManifest,
indexFiles, bucketMode);
}
long latestSchemaId = schemaManager.latest().get().id();
@@ -935,9 +991,9 @@ public class FileStoreCommitImpl implements FileStoreCommit
{
new Snapshot(
newSnapshotId,
latestSchemaId,
- previousChangesListName,
- newChangesListName,
- changelogListName,
+ baseManifestList,
+ deltaManifestList,
+ changelogManifestList,
indexManifest,
commitUser,
identifier,
@@ -951,14 +1007,12 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
statsFileName);
} catch (Throwable e) {
// fails when preparing for commit, we should clean up
- cleanUpTmpManifests(
- previousChangesListName,
- newChangesListName,
- changelogListName,
- newIndexManifest,
- oldMetas,
- newMetas,
- changelogMetas);
+ if (retryResult != null) {
+ retryResult.cleanAll();
+ }
+ cleanUpReuseTmpManifests(
+ deltaManifestList, changelogManifestList,
oldIndexManifest, indexManifest);
+ cleanUpNoReuseTmpManifests(baseManifestList, mergeBeforeManifests,
mergeAfterManifests);
throw new RuntimeException(
String.format(
"Exception occurs when preparing snapshot #%d
(path %s) by user %s "
@@ -1023,8 +1077,8 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
identifier,
commitKind.name()));
}
- commitCallbacks.forEach(callback -> callback.call(tableFiles,
newSnapshot));
- return true;
+ commitCallbacks.forEach(callback -> callback.call(deltaFiles,
newSnapshot));
+ return new SuccessResult();
}
// atomic rename fails, clean up and try again
@@ -1040,15 +1094,28 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
identifier,
commitKind.name(),
commitTime));
- cleanUpTmpManifests(
- previousChangesListName,
- newChangesListName,
- changelogListName,
- newIndexManifest,
- oldMetas,
- newMetas,
- changelogMetas);
- return false;
+ cleanUpNoReuseTmpManifests(baseManifestList, mergeBeforeManifests,
mergeAfterManifests);
+ return new RetryResult(
+ deltaManifestList,
+ changelogManifestList,
+ oldIndexManifest,
+ indexManifest,
+ latestSnapshot,
+ baseDataFiles);
+ }
+
+ private List<SimpleFileEntry> readIncrementalChanges(
+ Snapshot from, Snapshot to, List<BinaryRow> changedPartitions) {
+ List<SimpleFileEntry> entries = new ArrayList<>();
+ for (long i = from.id() + 1; i <= to.id(); i++) {
+ List<SimpleFileEntry> delta =
+ scan.withSnapshot(i)
+ .withKind(ScanMode.DELTA)
+ .withPartitionFilter(changedPartitions)
+ .readSimpleEntries();
+ entries.addAll(delta);
+ }
+ return entries;
}
@SafeVarargs
@@ -1060,8 +1127,14 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
.map(ManifestEntry::partition)
.distinct()
.collect(Collectors.toList());
+ return readAllEntriesFromChangedPartitions(snapshot,
changedPartitions);
+ }
+
+ private List<SimpleFileEntry> readAllEntriesFromChangedPartitions(
+ Snapshot snapshot, List<BinaryRow> changedPartitions) {
try {
return scan.withSnapshot(snapshot)
+ .withKind(ScanMode.ALL)
.withPartitionFilter(changedPartitions)
.readSimpleEntries();
} catch (Throwable e) {
@@ -1069,14 +1142,6 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
}
}
- private void noConflictsOrFail(
- String baseCommitUser, Snapshot latestSnapshot,
List<ManifestEntry> changes) {
- noConflictsOrFail(
- baseCommitUser,
- readAllEntriesFromChangedPartitions(latestSnapshot, changes),
- SimpleFileEntry.from(changes));
- }
-
private void noConflictsOrFail(
String baseCommitUser,
List<SimpleFileEntry> baseEntries,
@@ -1276,37 +1341,49 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
}
}
- private void cleanUpTmpManifests(
- String previousChangesListName,
- String newChangesListName,
- String changelogListName,
- String newIndexManifest,
- List<ManifestFileMeta> oldMetas,
- List<ManifestFileMeta> newMetas,
- List<ManifestFileMeta> changelogMetas) {
- // clean up newly created manifest list
- if (previousChangesListName != null) {
- manifestList.delete(previousChangesListName);
- }
- if (newChangesListName != null) {
- manifestList.delete(newChangesListName);
+ private void cleanUpNoReuseTmpManifests(
+ String baseManifestList,
+ List<ManifestFileMeta> mergeBeforeManifests,
+ List<ManifestFileMeta> mergeAfterManifests) {
+ if (baseManifestList != null) {
+ manifestList.delete(baseManifestList);
}
- if (changelogListName != null) {
- manifestList.delete(changelogListName);
+ Set<String> oldMetaSet =
+ mergeBeforeManifests.stream()
+ .map(ManifestFileMeta::fileName)
+ .collect(Collectors.toSet());
+ for (ManifestFileMeta suspect : mergeAfterManifests) {
+ if (!oldMetaSet.contains(suspect.fileName())) {
+ manifestFile.delete(suspect.fileName());
+ }
}
- if (newIndexManifest != null) {
- indexManifestFile.delete(newIndexManifest);
+ }
+
+ private void cleanUpReuseTmpManifests(
+ String deltaManifestList,
+ String changelogManifestList,
+ String oldIndexManifest,
+ String newIndexManifest) {
+ if (deltaManifestList != null) {
+ for (ManifestFileMeta manifest :
manifestList.read(deltaManifestList)) {
+ manifestFile.delete(manifest.fileName());
+ }
+ manifestList.delete(deltaManifestList);
}
- // clean up newly merged manifest files
- Set<ManifestFileMeta> oldMetaSet = new HashSet<>(oldMetas); // for
faster searching
- for (ManifestFileMeta suspect : newMetas) {
- if (!oldMetaSet.contains(suspect)) {
- manifestList.delete(suspect.fileName());
+
+ if (changelogManifestList != null) {
+ for (ManifestFileMeta manifest :
manifestList.read(changelogManifestList)) {
+ manifestFile.delete(manifest.fileName());
}
+ manifestList.delete(changelogManifestList);
}
- // clean up changelog manifests
- for (ManifestFileMeta meta : changelogMetas) {
- manifestList.delete(meta.fileName());
+
+ cleanIndexManifest(oldIndexManifest, newIndexManifest);
+ }
+
+ private void cleanIndexManifest(String oldIndexManifest, String
newIndexManifest) {
+ if (newIndexManifest != null && !Objects.equals(oldIndexManifest,
newIndexManifest)) {
+ indexManifestFile.delete(newIndexManifest);
}
}
@@ -1363,4 +1440,53 @@ public class FileStoreCommitImpl implements
FileStoreCommit {
static ConflictCheck mustConflictCheck() {
return latestSnapshot -> true;
}
+
+ private interface CommitResult {
+ boolean isSuccess();
+ }
+
+ private static class SuccessResult implements CommitResult {
+
+ @Override
+ public boolean isSuccess() {
+ return true;
+ }
+ }
+
+ private class RetryResult implements CommitResult {
+
+ private final String deltaManifestList;
+ private final String changelogManifestList;
+
+ private final String oldIndexManifest;
+ private final String newIndexManifest;
+
+ private final Snapshot latestSnapshot;
+ private final List<SimpleFileEntry> baseDataFiles;
+
+ private RetryResult(
+ String deltaManifestList,
+ String changelogManifestList,
+ String oldIndexManifest,
+ String newIndexManifest,
+ Snapshot latestSnapshot,
+ List<SimpleFileEntry> baseDataFiles) {
+ this.deltaManifestList = deltaManifestList;
+ this.changelogManifestList = changelogManifestList;
+ this.oldIndexManifest = oldIndexManifest;
+ this.newIndexManifest = newIndexManifest;
+ this.latestSnapshot = latestSnapshot;
+ this.baseDataFiles = baseDataFiles;
+ }
+
+ private void cleanAll() {
+ cleanUpReuseTmpManifests(
+ deltaManifestList, changelogManifestList,
oldIndexManifest, newIndexManifest);
+ }
+
+ @Override
+ public boolean isSuccess() {
+ return false;
+ }
+ }
}
diff --git a/paimon-core/src/main/java/org/apache/paimon/utils/ObjectsFile.java
b/paimon-core/src/main/java/org/apache/paimon/utils/ObjectsFile.java
index 190d29d62..3c261f410 100644
--- a/paimon-core/src/main/java/org/apache/paimon/utils/ObjectsFile.java
+++ b/paimon-core/src/main/java/org/apache/paimon/utils/ObjectsFile.java
@@ -122,7 +122,7 @@ public class ObjectsFile<T> implements SimpleFileReader<T> {
try {
return readWithIOException(fileName, fileSize, loadFilter,
readFilter);
} catch (IOException e) {
- throw new RuntimeException("Failed to read manifest list " +
fileName, e);
+ throw new RuntimeException("Failed to read " + fileName, e);
}
}
diff --git
a/paimon-core/src/test/java/org/apache/paimon/operation/FileDeletionTest.java
b/paimon-core/src/test/java/org/apache/paimon/operation/FileDeletionTest.java
index 6ae74c669..3a5ee93da 100644
---
a/paimon-core/src/test/java/org/apache/paimon/operation/FileDeletionTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/operation/FileDeletionTest.java
@@ -789,6 +789,7 @@ public class FileDeletionTest {
// commit
try (FileStoreCommitImpl commit = store.newCommit()) {
commit.tryCommitOnce(
+ null,
delete,
Collections.emptyList(),
Collections.emptyList(),
diff --git
a/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
b/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
index aeccafb85..77ee6b8e7 100644
---
a/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/operation/FileStoreCommitTest.java
@@ -238,6 +238,8 @@ public class FileStoreCommitTest {
testRandomConcurrent(
dataPerThread,
+ // overwrite cannot produce changelog
+ // so only enable it when changelog producer is none
changelogProducer == CoreOptions.ChangelogProducer.NONE,
failing,
changelogProducer);