Skip to content

Commit 85f32d6

Browse files
[hotfix][state-changelog] Don't trigger new materialization unless the previous one is confirmed/failed/cancelled
Co-authored-by: Roman <khachatryan.roman@gmail.com>
1 parent bde0951 commit 85f32d6

3 files changed

Lines changed: 111 additions & 6 deletions

File tree

flink-state-backends/flink-statebackend-changelog/src/main/java/org/apache/flink/state/changelog/ChangelogKeyedStateBackend.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ public class ChangelogKeyedStateBackend<K>
191191

192192
private long lastConfirmedMaterializationId = -1L;
193193

194+
/** last failed or cancelled materialization. */
195+
private long lastFailedMaterializationId = -1L;
196+
194197
private final ChangelogTruncateHelper changelogTruncateHelper;
195198

196199
public ChangelogKeyedStateBackend(
@@ -729,6 +732,7 @@ private ChangelogSnapshotState completeRestore(
729732
materializationId = Math.max(materializationId, h.getMaterializationID());
730733
}
731734
}
735+
this.lastConfirmedMaterializationId = materializationId;
732736
this.materializedId = materializationId + 1;
733737

734738
if (!localMaterialized.isEmpty() || !localRestoredNonMaterialized.isEmpty()) {
@@ -759,6 +763,18 @@ private ChangelogSnapshotState completeRestore(
759763
*/
760764
@Override
761765
public Optional<MaterializationRunnable> initMaterialization() throws Exception {
766+
if (lastConfirmedMaterializationId < materializedId - 1
767+
&& lastFailedMaterializationId < materializedId - 1) {
768+
// SharedStateRegistry potentially requires that the checkpoint's dependency on the
769+
// shared file be continuous, it will be broken if we trigger a new materialization
770+
// before the previous one has either confirmed or failed. See discussion in
771+
// https://github.com/apache/flink/pull/22669#issuecomment-1593370772 .
772+
LOG.info(
773+
"materialization:{} not confirmed or failed or cancelled, skip trigger new one.",
774+
materializedId - 1);
775+
return Optional.empty();
776+
}
777+
762778
SequenceNumber upTo = stateChangelogWriter.nextSequenceNumber();
763779
SequenceNumber lastMaterializedTo = changelogSnapshotState.lastMaterializedTo();
764780

@@ -833,6 +849,18 @@ public void handleMaterializationResult(
833849
changelogTruncateHelper.materialized(upTo);
834850
}
835851

852+
@Override
853+
public void handleMaterializationFailureOrCancellation(
854+
long materializationID, SequenceNumber upTo, Throwable cause) {
855+
856+
LOG.info(
857+
"Task {} failed or cancelled materialization:{} which is upTo:{}",
858+
subtaskName,
859+
materializationID,
860+
upTo);
861+
lastFailedMaterializationId = Math.max(lastFailedMaterializationId, materializationID);
862+
}
863+
836864
// TODO: this method may change after the ownership PR
837865
private List<KeyedStateHandle> getMaterializedResult(
838866
@Nonnull SnapshotResult<KeyedStateHandle> materializedSnapshot) {

flink-state-backends/flink-statebackend-changelog/src/test/java/org/apache/flink/state/changelog/ChangelogKeyedStateBackendTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,21 @@
3838
import org.apache.flink.runtime.state.ttl.mock.MockKeyedStateBackend.MockSnapshotSupplier;
3939
import org.apache.flink.runtime.state.ttl.mock.MockKeyedStateBackendBuilder;
4040
import org.apache.flink.state.changelog.ChangelogStateBackendTestUtils.DummyCheckpointingStorageAccess;
41+
import org.apache.flink.state.common.PeriodicMaterializationManager.MaterializationRunnable;
4142

4243
import org.junit.Test;
4344
import org.junit.runner.RunWith;
4445
import org.junit.runners.Parameterized;
4546
import org.junit.runners.Parameterized.Parameter;
4647

48+
import java.io.IOException;
49+
import java.util.Optional;
4750
import java.util.concurrent.RunnableFuture;
4851

4952
import static java.util.Collections.emptyList;
5053
import static org.junit.Assert.assertEquals;
54+
import static org.junit.Assert.assertFalse;
55+
import static org.junit.Assert.assertTrue;
5156

5257
/** {@link ChangelogKeyedStateBackend} test. */
5358
@RunWith(Parameterized.class)
@@ -85,6 +90,57 @@ public void testCheckpointConfirmation() throws Exception {
8590
}
8691
}
8792

93+
@Test
94+
public void testInitMaterialization() throws Exception {
95+
MockKeyedStateBackend<Integer> delegatedBackend = createMock();
96+
ChangelogKeyedStateBackend<Integer> backend = createChangelog(delegatedBackend);
97+
98+
try {
99+
Optional<MaterializationRunnable> runnable;
100+
101+
appendMockStateChange(backend); // ensure there is non-materialized changelog
102+
103+
runnable = backend.initMaterialization();
104+
// 1. should trigger first materialization
105+
assertTrue("first materialization should be trigger.", runnable.isPresent());
106+
107+
appendMockStateChange(backend); // ensure there is non-materialized changelog
108+
109+
// 2. should not trigger new one until the previous one has been confirmed or failed
110+
assertFalse(backend.initMaterialization().isPresent());
111+
112+
backend.handleMaterializationFailureOrCancellation(
113+
runnable.get().getMaterializationID(),
114+
runnable.get().getMaterializedTo(),
115+
null);
116+
runnable = backend.initMaterialization();
117+
// 3. should trigger new one after previous one failed
118+
assertTrue(runnable.isPresent());
119+
120+
appendMockStateChange(backend); // ensure there is non-materialized changelog
121+
122+
// 4. should not trigger new one until the previous one has been confirmed or failed
123+
assertFalse(backend.initMaterialization().isPresent());
124+
125+
backend.handleMaterializationResult(
126+
SnapshotResult.empty(),
127+
runnable.get().getMaterializationID(),
128+
runnable.get().getMaterializedTo());
129+
checkpoint(backend, checkpointId).get().discardState();
130+
backend.notifyCheckpointComplete(checkpointId);
131+
// 5. should trigger new one after previous one has been confirmed
132+
assertTrue(backend.initMaterialization().isPresent());
133+
} finally {
134+
backend.close();
135+
backend.dispose();
136+
}
137+
}
138+
139+
private void appendMockStateChange(ChangelogKeyedStateBackend changelogKeyedBackend)
140+
throws IOException {
141+
changelogKeyedBackend.getChangelogWriter().append(0, new byte[] {'s'});
142+
}
143+
88144
private MockKeyedStateBackend<Integer> createMock() {
89145
return new MockKeyedStateBackendBuilder<>(
90146
new KvStateRegistry().createTaskRegistry(new JobID(), new JobVertexID()),

flink-state-backends/flink-statebackend-common/src/main/java/org/apache/flink/state/common/PeriodicMaterializationManager.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.slf4j.Logger;
3434
import org.slf4j.LoggerFactory;
3535

36+
import javax.annotation.concurrent.NotThreadSafe;
37+
3638
import java.io.Closeable;
3739
import java.util.Optional;
3840
import java.util.concurrent.CancellationException;
@@ -51,30 +53,31 @@
5153
public class PeriodicMaterializationManager implements Closeable {
5254

5355
/** {@link MaterializationRunnable} provider and consumer, i.e. state backend. */
56+
@NotThreadSafe
5457
public interface MaterializationTarget {
5558

5659
/**
5760
* Initialize state materialization so that materialized data can be persisted durably and
5861
* included into the checkpoint.
5962
*
60-
* <p>This method is not thread safe. It should be called either under a lock or through
61-
* task mailbox executor.
62-
*
6363
* @return a tuple of - future snapshot result from the underlying state backend - a {@link
6464
* SequenceNumber} identifying the latest change in the changelog
6565
*/
6666
Optional<MaterializationRunnable> initMaterialization() throws Exception;
6767

6868
/**
69-
* This method is not thread safe. It should be called either under a lock or through task
70-
* mailbox executor.
69+
* Implementations should not trigger materialization until the previous one has been
70+
* confirmed or failed.
7171
*/
7272
void handleMaterializationResult(
7373
SnapshotResult<KeyedStateHandle> materializedSnapshot,
7474
long materializationID,
7575
SequenceNumber upTo)
7676
throws Exception;
7777

78+
void handleMaterializationFailureOrCancellation(
79+
long materializationID, SequenceNumber upTo, Throwable cause);
80+
7881
MaterializationTarget NO_OP =
7982
new MaterializationTarget() {
8083
@Override
@@ -87,6 +90,10 @@ public void handleMaterializationResult(
8790
SnapshotResult<KeyedStateHandle> materializedSnapshot,
8891
long materializationID,
8992
SequenceNumber upTo) {}
93+
94+
@Override
95+
public void handleMaterializationFailureOrCancellation(
96+
long materializationID, SequenceNumber upTo, Throwable cause) {}
9097
};
9198
}
9299

@@ -268,9 +275,11 @@ private void asyncMaterializationPhase(
268275
} else if (throwable instanceof CancellationException) {
269276
// can happen e.g. due to task cancellation
270277
LOG.info("materialization cancelled", throwable);
278+
notifyFailureOrCancellation(materializationID, upTo, throwable);
271279
scheduleNextMaterialization();
272280
} else {
273281
// if failed
282+
notifyFailureOrCancellation(materializationID, upTo, throwable);
274283
metrics.reportFailedMaterialization();
275284
int retryTime = numberOfConsecutiveFailures.incrementAndGet();
276285

@@ -295,6 +304,18 @@ private void asyncMaterializationPhase(
295304
});
296305
}
297306

307+
private void notifyFailureOrCancellation(
308+
long materializationId, SequenceNumber upTo, Throwable cause) {
309+
mailboxExecutor.execute(
310+
() ->
311+
target.handleMaterializationFailureOrCancellation(
312+
materializationId, upTo, cause),
313+
"Task {} materialization:{},upTo:{} failed or canceled.",
314+
subtaskName,
315+
materializationId,
316+
upTo);
317+
}
318+
298319
private CompletableFuture<SnapshotResult<KeyedStateHandle>> uploadSnapshot(
299320
RunnableFuture<SnapshotResult<KeyedStateHandle>> materializedRunnableFuture) {
300321

@@ -385,7 +406,7 @@ RunnableFuture<SnapshotResult<KeyedStateHandle>> getMaterializationRunnable() {
385406
return materializationRunnable;
386407
}
387408

388-
SequenceNumber getMaterializedTo() {
409+
public SequenceNumber getMaterializedTo() {
389410
return materializedTo;
390411
}
391412

0 commit comments

Comments
 (0)