Skip to content

Commit 7857238

Browse files
authored
Flink: Backport DynamicCommitter jobId fix to v1.20 and v2.0 (#16648)
Backport commit #16011.
1 parent 696b93d commit 7857238

10 files changed

Lines changed: 784 additions & 86 deletions

File tree

flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/sink/dynamic/DynamicCommitter.java

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.IOException;
2222
import java.util.Arrays;
2323
import java.util.Collection;
24+
import java.util.Comparator;
2425
import java.util.List;
2526
import java.util.Map;
2627
import java.util.NavigableMap;
@@ -111,6 +112,17 @@ public void commit(Collection<CommitRequest<DynamicCommittable>> commitRequests)
111112
}
112113

113114
/*
115+
Group the incoming commit requests into a three-level structure before committing:
116+
117+
Map<TableKey, Map<JobOperatorKey, NavigableMap<Long, List<CommitRequest>>>>
118+
| | | |
119+
| | | +-- commit requests at that checkpoint
120+
| | +-- checkpointId, sorted ascending so older commits go first
121+
| +-- (jobId, operatorId) of the producing aggregator; deduplication against the
122+
| table's snapshot summaries is per (jobId, operatorId), so each group is
123+
| walked against the ancestor chain independently
124+
+-- (tableName, branch); we load the table and its ancestor chain once per TableKey
125+
114126
Each (table, branch, checkpoint) triplet must have only one commit request.
115127
There may be commit requests from previous checkpoints which have not been committed yet.
116128
@@ -119,43 +131,58 @@ public void commit(Collection<CommitRequest<DynamicCommittable>> commitRequests)
119131
DynamicWriteResultAggregator. Iceberg 1.12 will remove this, and users should upgrade to the 1.11 release first
120132
to migrate their state to a single commit request per checkpoint.
121133
*/
122-
Map<TableKey, NavigableMap<Long, List<CommitRequest<DynamicCommittable>>>> commitRequestMap =
123-
Maps.newHashMap();
134+
Map<TableKey, Map<JobOperatorKey, NavigableMap<Long, List<CommitRequest<DynamicCommittable>>>>>
135+
commitRequestMap = Maps.newHashMap();
124136
for (CommitRequest<DynamicCommittable> request : commitRequests) {
125-
NavigableMap<Long, List<CommitRequest<DynamicCommittable>>> committables =
126-
commitRequestMap.computeIfAbsent(
127-
new TableKey(request.getCommittable()), unused -> Maps.newTreeMap());
128-
committables
129-
.computeIfAbsent(request.getCommittable().checkpointId(), unused -> Lists.newArrayList())
137+
DynamicCommittable committable = request.getCommittable();
138+
commitRequestMap
139+
.computeIfAbsent(committable.key(), unused -> Maps.newHashMap())
140+
.computeIfAbsent(new JobOperatorKey(committable), unused -> Maps.newTreeMap())
141+
.computeIfAbsent(committable.checkpointId(), unused -> Lists.newArrayList())
130142
.add(request);
131143
}
132144

133-
for (Map.Entry<TableKey, NavigableMap<Long, List<CommitRequest<DynamicCommittable>>>> entry :
134-
commitRequestMap.entrySet()) {
135-
Table table = catalog.loadTable(TableIdentifier.parse(entry.getKey().tableName()));
136-
DynamicCommittable last = entry.getValue().lastEntry().getValue().get(0).getCommittable();
137-
Snapshot latestSnapshot = table.snapshot(entry.getKey().branch());
145+
for (Map.Entry<
146+
TableKey,
147+
Map<JobOperatorKey, NavigableMap<Long, List<CommitRequest<DynamicCommittable>>>>>
148+
tableEntry : commitRequestMap.entrySet()) {
149+
TableKey tableKey = tableEntry.getKey();
150+
Table table = catalog.loadTable(TableIdentifier.parse(tableKey.tableName()));
151+
Snapshot latestSnapshot = table.snapshot(tableKey.branch());
138152
Iterable<Snapshot> ancestors =
139153
latestSnapshot != null
140154
? SnapshotUtil.ancestorsOf(latestSnapshot.snapshotId(), table::snapshot)
141155
: List.of();
142-
long maxCommittedCheckpointId =
143-
getMaxCommittedCheckpointId(ancestors, last.jobId(), last.operatorId());
144-
145-
NavigableMap<Long, List<CommitRequest<DynamicCommittable>>> skippedCommitRequests =
146-
entry.getValue().headMap(maxCommittedCheckpointId, true);
147-
LOG.debug(
148-
"Skipping {} commit requests: {}", skippedCommitRequests.size(), skippedCommitRequests);
149-
// Mark the already committed FilesCommittable(s) as finished
150-
skippedCommitRequests
151-
.values()
152-
.forEach(list -> list.forEach(CommitRequest::signalAlreadyCommitted));
153-
154-
NavigableMap<Long, List<CommitRequest<DynamicCommittable>>> uncommitted =
155-
entry.getValue().tailMap(maxCommittedCheckpointId, false);
156-
if (!uncommitted.isEmpty()) {
157-
commitPendingRequests(
158-
table, entry.getKey().branch(), uncommitted, last.jobId(), last.operatorId());
156+
157+
List<Map.Entry<JobOperatorKey, NavigableMap<Long, List<CommitRequest<DynamicCommittable>>>>>
158+
jobEntries = Lists.newArrayList(tableEntry.getValue().entrySet());
159+
// Preserve checkpoint order across groups so that older-jobId commits land before newer-jobId
160+
// ones when the batch mixes committables from different jobIds (e.g. state replay after a
161+
// restart). Within a (jobId, operatorId) group, checkpoint order is already guaranteed by
162+
// the inner NavigableMap.
163+
jobEntries.sort(Comparator.comparingLong(entry -> entry.getValue().firstKey()));
164+
165+
for (Map.Entry<JobOperatorKey, NavigableMap<Long, List<CommitRequest<DynamicCommittable>>>>
166+
jobEntry : jobEntries) {
167+
JobOperatorKey jobKey = jobEntry.getKey();
168+
long maxCommittedCheckpointId =
169+
getMaxCommittedCheckpointId(ancestors, jobKey.jobId(), jobKey.operatorId());
170+
171+
NavigableMap<Long, List<CommitRequest<DynamicCommittable>>> skippedCommitRequests =
172+
jobEntry.getValue().headMap(maxCommittedCheckpointId, true);
173+
LOG.debug(
174+
"Skipping {} commit requests: {}", skippedCommitRequests.size(), skippedCommitRequests);
175+
// Mark the already committed FilesCommittable(s) as finished
176+
skippedCommitRequests
177+
.values()
178+
.forEach(list -> list.forEach(CommitRequest::signalAlreadyCommitted));
179+
180+
NavigableMap<Long, List<CommitRequest<DynamicCommittable>>> uncommitted =
181+
jobEntry.getValue().tailMap(maxCommittedCheckpointId, false);
182+
if (!uncommitted.isEmpty()) {
183+
commitPendingRequests(
184+
table, tableKey.branch(), uncommitted, jobKey.jobId(), jobKey.operatorId());
185+
}
159186
}
160187
}
161188
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.iceberg.flink.sink.dynamic;
20+
21+
import java.util.Objects;
22+
23+
class JobOperatorKey {
24+
private final String jobId;
25+
private final String operatorId;
26+
27+
JobOperatorKey(DynamicCommittable committable) {
28+
this.jobId = committable.jobId();
29+
this.operatorId = committable.operatorId();
30+
}
31+
32+
String jobId() {
33+
return jobId;
34+
}
35+
36+
String operatorId() {
37+
return operatorId;
38+
}
39+
40+
@Override
41+
public boolean equals(Object other) {
42+
if (this == other) {
43+
return true;
44+
}
45+
46+
if (!(other instanceof JobOperatorKey that)) {
47+
return false;
48+
}
49+
50+
return Objects.equals(jobId, that.jobId) && Objects.equals(operatorId, that.operatorId);
51+
}
52+
53+
@Override
54+
public int hashCode() {
55+
return Objects.hash(jobId, operatorId);
56+
}
57+
}

flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/sink/dynamic/TableKey.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@ class TableKey {
3333
this.branch = branch;
3434
}
3535

36-
TableKey(DynamicCommittable committable) {
37-
this.tableName = committable.key().tableName();
38-
this.branch = committable.key().branch();
39-
}
40-
4136
String tableName() {
4237
return tableName;
4338
}

0 commit comments

Comments
 (0)