|
30 | 30 | import org.apache.flink.metrics.MetricGroup; |
31 | 31 | import org.apache.flink.runtime.checkpoint.CheckpointOptions; |
32 | 32 | import org.apache.flink.runtime.checkpoint.CheckpointType; |
| 33 | +import org.apache.flink.runtime.checkpoint.SnapshotType; |
33 | 34 | import org.apache.flink.runtime.state.AbstractKeyedStateBackend; |
34 | 35 | import org.apache.flink.runtime.state.CheckpointStateOutputStream; |
35 | 36 | import org.apache.flink.runtime.state.CheckpointStorageLocationReference; |
|
67 | 68 | import org.apache.flink.state.changelog.restore.ChangelogRestoreTarget; |
68 | 69 | import org.apache.flink.state.changelog.restore.FunctionDelegationHelper; |
69 | 70 | import org.apache.flink.state.common.PeriodicMaterializationManager.MaterializationTarget; |
| 71 | +import org.apache.flink.util.concurrent.FutureUtils; |
70 | 72 |
|
71 | 73 | import org.apache.flink.shaded.guava31.com.google.common.io.Closer; |
72 | 74 |
|
|
89 | 91 | import java.util.TreeMap; |
90 | 92 | import java.util.concurrent.CompletableFuture; |
91 | 93 | import java.util.concurrent.ExecutionException; |
| 94 | +import java.util.concurrent.FutureTask; |
92 | 95 | import java.util.concurrent.RunnableFuture; |
93 | 96 | import java.util.concurrent.TimeUnit; |
94 | 97 | import java.util.concurrent.TimeoutException; |
@@ -379,6 +382,11 @@ public RunnableFuture<SnapshotResult<KeyedStateHandle>> snapshot( |
379 | 382 | @Nonnull CheckpointStreamFactory streamFactory, |
380 | 383 | @Nonnull CheckpointOptions checkpointOptions) |
381 | 384 | throws Exception { |
| 385 | + |
| 386 | + if (checkpointOptions.getCheckpointType().isSavepoint()) { |
| 387 | + return nativeSavepoint(checkpointId, timestamp, streamFactory, checkpointOptions); |
| 388 | + } |
| 389 | + |
382 | 390 | // The range to upload may overlap with the previous one(s). To reuse them, we could store |
383 | 391 | // the previous results either here in the backend or in the writer. However, |
384 | 392 | // materialization may truncate only a part of the previous result and the backend would |
@@ -416,6 +424,57 @@ public RunnableFuture<SnapshotResult<KeyedStateHandle>> snapshot( |
416 | 424 | .thenApply(this::castSnapshotResult)); |
417 | 425 | } |
418 | 426 |
|
| 427 | + private RunnableFuture<SnapshotResult<KeyedStateHandle>> nativeSavepoint( |
| 428 | + long checkpointId, |
| 429 | + long timestamp, |
| 430 | + @Nonnull CheckpointStreamFactory streamFactory, |
| 431 | + @Nonnull CheckpointOptions checkpointOptions) |
| 432 | + throws Exception { |
| 433 | + |
| 434 | + SnapshotType.SharingFilesStrategy sharingFilesStrategy = |
| 435 | + checkpointOptions.getCheckpointType().getSharingFilesStrategy(); |
| 436 | + if (sharingFilesStrategy != SnapshotType.SharingFilesStrategy.NO_SHARING) { |
| 437 | + throw new UnsupportedOperationException( |
| 438 | + "ChangelogKeyedStateBackend doesn't support native savepoint with SharingFilesStrategy: " |
| 439 | + + sharingFilesStrategy); |
| 440 | + } |
| 441 | + |
| 442 | + long materializationID = materializedId++; |
| 443 | + // For NO_SHARING native savepoint, trigger delegated one |
| 444 | + RunnableFuture<SnapshotResult<KeyedStateHandle>> delegatedSnapshotResult = |
| 445 | + keyedStateBackend.snapshot( |
| 446 | + materializationID, timestamp, streamFactory, checkpointOptions); |
| 447 | + |
| 448 | + materializationIdByCheckpointId.put(checkpointId, materializationID); |
| 449 | + return new FutureTask<SnapshotResult<KeyedStateHandle>>( |
| 450 | + () -> { |
| 451 | + SnapshotResult<KeyedStateHandle> result = |
| 452 | + FutureUtils.runIfNotDoneAndGet(delegatedSnapshotResult); |
| 453 | + return castSnapshotResult( |
| 454 | + buildSnapshotResult( |
| 455 | + checkpointId, |
| 456 | + SnapshotResult.empty(), |
| 457 | + new ChangelogSnapshotState( |
| 458 | + getMaterializedResult(result), materializationID))); |
| 459 | + }) { |
| 460 | + @Override |
| 461 | + public boolean cancel(boolean mayInterruptIfRunning) { |
| 462 | + return delegatedSnapshotResult.cancel(mayInterruptIfRunning) |
| 463 | + && super.cancel(mayInterruptIfRunning); |
| 464 | + } |
| 465 | + |
| 466 | + @Override |
| 467 | + public boolean isCancelled() { |
| 468 | + return delegatedSnapshotResult.isCancelled() && super.isCancelled(); |
| 469 | + } |
| 470 | + |
| 471 | + @Override |
| 472 | + public boolean isDone() { |
| 473 | + return delegatedSnapshotResult.isDone() && super.isDone(); |
| 474 | + } |
| 475 | + }; |
| 476 | + } |
| 477 | + |
419 | 478 | @SuppressWarnings("unchecked") |
420 | 479 | private SnapshotResult<KeyedStateHandle> castSnapshotResult(SnapshotResult<?> snapshotResult) { |
421 | 480 | return (SnapshotResult<KeyedStateHandle>) snapshotResult; |
@@ -1013,6 +1072,19 @@ private class ChangelogSnapshotState { |
1013 | 1072 | /** ID of this materialization corresponding to the nested backend checkpoint ID. */ |
1014 | 1073 | private final long materializationID; |
1015 | 1074 |
|
| 1075 | + /** |
| 1076 | + * Construct a ChangelogSnapshotState with empty non-materialized part, which could be used |
| 1077 | + * when triggering manual materialization. |
| 1078 | + */ |
| 1079 | + public ChangelogSnapshotState( |
| 1080 | + List<KeyedStateHandle> materializedSnapshot, long materializationID) { |
| 1081 | + this( |
| 1082 | + materializedSnapshot, |
| 1083 | + Collections.emptyList(), |
| 1084 | + SequenceNumber.of(Long.MAX_VALUE), |
| 1085 | + materializationID); |
| 1086 | + } |
| 1087 | + |
1016 | 1088 | public ChangelogSnapshotState( |
1017 | 1089 | List<KeyedStateHandle> materializedSnapshot, |
1018 | 1090 | List<ChangelogStateHandle> restoredNonMaterialized, |
|
0 commit comments