You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by sj...@apache.org on 2021/01/25 16:08:50 UTC

[flink] 02/03: [FLINK-19465][runtime / statebackends] Wire checkpoint storage through the runtime

This is an automated email from the ASF dual-hosted git repository.

sjwiesman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git

commit 947a02c80e790810dc4c4ff1d9b18df0a46a3f57
Author: Seth Wiesman <sj...@gmail.com>
AuthorDate: Mon Oct 26 14:40:21 2020 -0500

    [FLINK-19465][runtime / statebackends] Wire checkpoint storage through the runtime
---
 .../src/test/resources/rest_api_v1.snapshot        |  3 +
 .../runtime/checkpoint/CheckpointCoordinator.java  | 70 ++++++++++++++++++----
 .../flink/runtime/checkpoint/Checkpoints.java      | 36 +++++++++--
 .../executiongraph/AccessExecutionGraph.java       |  7 +++
 .../executiongraph/ArchivedExecutionGraph.java     | 15 ++++-
 .../runtime/executiongraph/ExecutionGraph.java     | 10 ++++
 .../executiongraph/ExecutionGraphBuilder.java      | 36 +++++++++++
 .../jobgraph/tasks/JobCheckpointingSettings.java   | 12 ++++
 .../job/checkpoints/CheckpointConfigHandler.java   |  2 +
 .../messages/checkpoints/CheckpointConfigInfo.java |  9 +++
 .../CheckpointSettingsSerializableTest.java        | 26 ++++++++
 .../flink/runtime/dispatcher/DispatcherTest.java   | 10 ++--
 .../legacy/DefaultExecutionGraphCacheTest.java     |  3 +-
 .../utils/ArchivedExecutionGraphBuilder.java       |  3 +-
 .../checkpoints/CheckpointConfigInfoTest.java      |  1 +
 .../runtime/state/CheckpointStorageLoaderTest.java | 33 ++++------
 .../flink/streaming/api/graph/StreamConfig.java    | 20 +++++++
 .../flink/streaming/api/graph/StreamGraph.java     | 10 ++++
 .../streaming/api/graph/StreamGraphGenerator.java  |  4 ++
 .../api/graph/StreamingJobGraphGenerator.java      | 17 ++++++
 .../flink/streaming/runtime/tasks/StreamTask.java  | 22 ++++++-
 .../flink/table/planner/utils/ExecutorUtils.java   |  1 +
 22 files changed, 304 insertions(+), 46 deletions(-)

diff --git a/flink-runtime-web/src/test/resources/rest_api_v1.snapshot b/flink-runtime-web/src/test/resources/rest_api_v1.snapshot
index 173eed4..192a38a 100644
--- a/flink-runtime-web/src/test/resources/rest_api_v1.snapshot
+++ b/flink-runtime-web/src/test/resources/rest_api_v1.snapshot
@@ -1240,6 +1240,9 @@
         "state_backend" : {
           "type" : "string"
         },
+        "checkpoint_storage" : {
+          "type" : "string"
+        },
         "unaligned_checkpoints" : {
           "type" : "boolean"
         },
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
index daac97e..33d9e9d 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
@@ -36,6 +36,7 @@ import org.apache.flink.runtime.messages.checkpoint.AcknowledgeCheckpoint;
 import org.apache.flink.runtime.messages.checkpoint.DeclineCheckpoint;
 import org.apache.flink.runtime.operators.coordination.OperatorCoordinator;
 import org.apache.flink.runtime.operators.coordination.OperatorInfo;
+import org.apache.flink.runtime.state.CheckpointStorage;
 import org.apache.flink.runtime.state.CheckpointStorageCoordinatorView;
 import org.apache.flink.runtime.state.CheckpointStorageLocation;
 import org.apache.flink.runtime.state.CompletedCheckpointStorageLocation;
@@ -139,7 +140,7 @@ public class CheckpointCoordinator {
      * The root checkpoint state backend, which is responsible for initializing the checkpoint,
      * storing the metadata, and cleaning up the checkpoint.
      */
-    private final CheckpointStorageCoordinatorView checkpointStorage;
+    private final CheckpointStorageCoordinatorView checkpointStorageView;
 
     /** A list of recent checkpoint IDs, to identify late messages (vs invalid ones). */
     private final ArrayDeque<Long> recentPendingCheckpoints;
@@ -234,6 +235,46 @@ public class CheckpointCoordinator {
             Collection<OperatorCoordinatorCheckpointContext> coordinatorsToCheckpoint,
             CheckpointIDCounter checkpointIDCounter,
             CompletedCheckpointStore completedCheckpointStore,
+            CheckpointStorage checkpointStorage,
+            Executor executor,
+            CheckpointsCleaner checkpointsCleaner,
+            ScheduledExecutor timer,
+            SharedStateRegistryFactory sharedStateRegistryFactory,
+            CheckpointFailureManager failureManager) {
+
+        this(
+                job,
+                chkConfig,
+                tasksToTrigger,
+                tasksToWaitFor,
+                tasksToCommitTo,
+                coordinatorsToCheckpoint,
+                checkpointIDCounter,
+                completedCheckpointStore,
+                null,
+                executor,
+                checkpointsCleaner,
+                timer,
+                sharedStateRegistryFactory,
+                failureManager,
+                SystemClock.getInstance());
+    }
+
+    /**
+     * @deprecated Please pass a {@link CheckpointStorage} object directly. This constructor only
+     *     exists for to keep coordinator tests passing and may be dropped once concrete checkpoint
+     *     storage classes are implemented.
+     */
+    @Deprecated
+    public CheckpointCoordinator(
+            JobID job,
+            CheckpointCoordinatorConfiguration chkConfig,
+            ExecutionVertex[] tasksToTrigger,
+            ExecutionVertex[] tasksToWaitFor,
+            ExecutionVertex[] tasksToCommitTo,
+            Collection<OperatorCoordinatorCheckpointContext> coordinatorsToCheckpoint,
+            CheckpointIDCounter checkpointIDCounter,
+            CompletedCheckpointStore completedCheckpointStore,
             StateBackend checkpointStateBackend,
             Executor executor,
             CheckpointsCleaner checkpointsCleaner,
@@ -250,7 +291,7 @@ public class CheckpointCoordinator {
                 coordinatorsToCheckpoint,
                 checkpointIDCounter,
                 completedCheckpointStore,
-                checkpointStateBackend,
+                asCheckpointStorage(checkpointStateBackend),
                 executor,
                 checkpointsCleaner,
                 timer,
@@ -269,7 +310,7 @@ public class CheckpointCoordinator {
             Collection<OperatorCoordinatorCheckpointContext> coordinatorsToCheckpoint,
             CheckpointIDCounter checkpointIDCounter,
             CompletedCheckpointStore completedCheckpointStore,
-            StateBackend checkpointStateBackend,
+            CheckpointStorage checkpointStorage,
             Executor executor,
             CheckpointsCleaner checkpointsCleaner,
             ScheduledExecutor timer,
@@ -278,7 +319,7 @@ public class CheckpointCoordinator {
             Clock clock) {
 
         // sanity checks
-        checkNotNull(checkpointStateBackend);
+        checkNotNull(checkpointStorage);
 
         // max "in between duration" can be one year - this is to prevent numeric overflows
         long minPauseBetweenCheckpoints = chkConfig.getMinPauseBetweenCheckpoints();
@@ -325,8 +366,8 @@ public class CheckpointCoordinator {
                 CheckpointProperties.forCheckpoint(chkConfig.getCheckpointRetentionPolicy());
 
         try {
-            this.checkpointStorage = checkpointStateBackend.createCheckpointStorage(job);
-            checkpointStorage.initializeBaseLocations();
+            this.checkpointStorageView = checkpointStorage.createCheckpointStorage(job);
+            checkpointStorageView.initializeBaseLocations();
         } catch (IOException e) {
             throw new FlinkRuntimeException(
                     "Failed to create checkpoint storage at checkpoint coordinator side.", e);
@@ -683,9 +724,9 @@ public class CheckpointCoordinator {
 
                         CheckpointStorageLocation checkpointStorageLocation =
                                 props.isSavepoint()
-                                        ? checkpointStorage.initializeLocationForSavepoint(
+                                        ? checkpointStorageView.initializeLocationForSavepoint(
                                                 checkpointID, externalSavepointLocation)
-                                        : checkpointStorage.initializeLocationForCheckpoint(
+                                        : checkpointStorageView.initializeLocationForCheckpoint(
                                                 checkpointID);
 
                         return new CheckpointIdAndStorageLocation(
@@ -1627,7 +1668,7 @@ public class CheckpointCoordinator {
                 (allowNonRestored ? "allowing non restored state" : ""));
 
         final CompletedCheckpointStorageLocation checkpointLocation =
-                checkpointStorage.resolveCheckpoint(savepointPointer);
+                checkpointStorageView.resolveCheckpoint(savepointPointer);
 
         // Load the savepoint as a checkpoint into the system
         CompletedCheckpoint savepoint =
@@ -1682,7 +1723,7 @@ public class CheckpointCoordinator {
     }
 
     public CheckpointStorageCoordinatorView getCheckpointStorage() {
-        return checkpointStorage;
+        return checkpointStorageView;
     }
 
     public CompletedCheckpointStore getCheckpointStore() {
@@ -2108,4 +2149,13 @@ public class CheckpointCoordinator {
         /** Coordinators are not restored during this checkpoint restore. */
         SKIP;
     }
+
+    private static CheckpointStorage asCheckpointStorage(StateBackend backend) {
+        if (backend instanceof CheckpointStorage) {
+            return (CheckpointStorage) backend;
+        }
+
+        throw new IllegalStateException(
+                "Provided state backend does not implement checkpoint storage");
+    }
 }
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/Checkpoints.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/Checkpoints.java
index 2ce4a47..0729281 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/Checkpoints.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/Checkpoints.java
@@ -28,6 +28,8 @@ import org.apache.flink.runtime.checkpoint.metadata.MetadataV3Serializer;
 import org.apache.flink.runtime.executiongraph.ExecutionJobVertex;
 import org.apache.flink.runtime.jobgraph.JobVertexID;
 import org.apache.flink.runtime.jobgraph.OperatorID;
+import org.apache.flink.runtime.state.CheckpointStorage;
+import org.apache.flink.runtime.state.CheckpointStorageLoader;
 import org.apache.flink.runtime.state.CompletedCheckpointStorageLocation;
 import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.runtime.state.StateBackendLoader;
@@ -231,15 +233,15 @@ public class Checkpoints {
     // ------------------------------------------------------------------------
 
     public static void disposeSavepoint(
-            String pointer, StateBackend stateBackend, ClassLoader classLoader)
+            String pointer, CheckpointStorage checkpointStorage, ClassLoader classLoader)
             throws IOException, FlinkException {
 
         checkNotNull(pointer, "location");
-        checkNotNull(stateBackend, "stateBackend");
+        checkNotNull(checkpointStorage, "stateBackend");
         checkNotNull(classLoader, "classLoader");
 
         final CompletedCheckpointStorageLocation checkpointLocation =
-                stateBackend.resolveCheckpoint(pointer);
+                checkpointStorage.resolveCheckpoint(pointer);
 
         final StreamStateHandle metadataHandle = checkpointLocation.getMetadataHandle();
 
@@ -293,9 +295,9 @@ public class Checkpoints {
         checkNotNull(configuration, "configuration");
         checkNotNull(classLoader, "classLoader");
 
-        StateBackend backend = loadStateBackend(configuration, classLoader, logger);
+        CheckpointStorage storage = loadCheckpointStorage(configuration, classLoader, logger);
 
-        disposeSavepoint(pointer, backend, classLoader);
+        disposeSavepoint(pointer, storage, classLoader);
     }
 
     @Nonnull
@@ -331,6 +333,30 @@ public class Checkpoints {
         return backend;
     }
 
+    @Nonnull
+    public static CheckpointStorage loadCheckpointStorage(
+            Configuration configuration, ClassLoader classLoader, @Nullable Logger logger) {
+        StateBackend backend = loadStateBackend(configuration, classLoader, logger);
+
+        if (logger != null) {
+            logger.info("Attempting to load configured checkpoint storage for savepoint disposal");
+        }
+
+        CheckpointStorage checkpointStorage = null;
+        try {
+            checkpointStorage =
+                    CheckpointStorageLoader.load(null, backend, configuration, classLoader, null);
+        } catch (Throwable t) {
+            // catches exceptions and errors (like linking errors)
+            if (logger != null) {
+                logger.info("Could not load configured state backend.");
+                logger.debug("Detailed exception:", t);
+            }
+        }
+
+        return checkpointStorage;
+    }
+
     // ------------------------------------------------------------------------
 
     /** This class contains only static utility methods and is not meant to be instantiated. */
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionGraph.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionGraph.java
index 5586b7d..5274988 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionGraph.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionGraph.java
@@ -172,4 +172,11 @@ public interface AccessExecutionGraph extends JobStatusProvider {
      * @return The state backend name, or an empty Optional in the case of batch jobs
      */
     Optional<String> getStateBackendName();
+
+    /**
+     * Returns the checkpoint storage name for this ExecutionGraph.
+     *
+     * @return The checkpoint storage name, or an empty Optional in the case of batch jobs
+     */
+    Optional<String> getCheckpointStorageName();
 }
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraph.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraph.java
index 906fa1c..6a9753b 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraph.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraph.java
@@ -95,6 +95,8 @@ public class ArchivedExecutionGraph implements AccessExecutionGraph, Serializabl
 
     @Nullable private final String stateBackendName;
 
+    @Nullable private final String checkpointStorageName;
+
     public ArchivedExecutionGraph(
             JobID jobID,
             String jobName,
@@ -110,7 +112,8 @@ public class ArchivedExecutionGraph implements AccessExecutionGraph, Serializabl
             boolean isStoppable,
             @Nullable CheckpointCoordinatorConfiguration jobCheckpointingConfiguration,
             @Nullable CheckpointStatsSnapshot checkpointStatsSnapshot,
-            @Nullable String stateBackendName) {
+            @Nullable String stateBackendName,
+            @Nullable String checkpointStorageName) {
 
         this.jobID = Preconditions.checkNotNull(jobID);
         this.jobName = Preconditions.checkNotNull(jobName);
@@ -127,6 +130,7 @@ public class ArchivedExecutionGraph implements AccessExecutionGraph, Serializabl
         this.jobCheckpointingConfiguration = jobCheckpointingConfiguration;
         this.checkpointStatsSnapshot = checkpointStatsSnapshot;
         this.stateBackendName = stateBackendName;
+        this.checkpointStorageName = checkpointStorageName;
     }
 
     // --------------------------------------------------------------------------------------------
@@ -257,6 +261,11 @@ public class ArchivedExecutionGraph implements AccessExecutionGraph, Serializabl
         return Optional.ofNullable(stateBackendName);
     }
 
+    @Override
+    public Optional<String> getCheckpointStorageName() {
+        return Optional.ofNullable(checkpointStorageName);
+    }
+
     class AllVerticesIterator implements Iterator<ArchivedExecutionVertex> {
 
         private final Iterator<ArchivedExecutionJobVertex> jobVertices;
@@ -346,7 +355,8 @@ public class ArchivedExecutionGraph implements AccessExecutionGraph, Serializabl
                 executionGraph.isStoppable(),
                 executionGraph.getCheckpointCoordinatorConfiguration(),
                 executionGraph.getCheckpointStatsSnapshot(),
-                executionGraph.getStateBackendName().orElse(null));
+                executionGraph.getStateBackendName().orElse(null),
+                executionGraph.getCheckpointStorageName().orElse(null));
     }
 
     /**
@@ -395,6 +405,7 @@ public class ArchivedExecutionGraph implements AccessExecutionGraph, Serializabl
                 false,
                 null,
                 null,
+                null,
                 null);
     }
 }
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java
index f7ac7b7..eb4004a 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java
@@ -67,6 +67,7 @@ import org.apache.flink.runtime.scheduler.strategy.SchedulingExecutionVertex;
 import org.apache.flink.runtime.scheduler.strategy.SchedulingResultPartition;
 import org.apache.flink.runtime.scheduler.strategy.SchedulingTopology;
 import org.apache.flink.runtime.shuffle.ShuffleMaster;
+import org.apache.flink.runtime.state.CheckpointStorage;
 import org.apache.flink.runtime.state.SharedStateRegistry;
 import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.runtime.taskmanager.DispatcherThreadFactory;
@@ -266,6 +267,8 @@ public class ExecutionGraph implements AccessExecutionGraph {
     // ------ Fields that are only relevant for archived execution graphs ------------
     @Nullable private String stateBackendName;
 
+    @Nullable private String checkpointStorageName;
+
     private String jsonPlan;
 
     /** Shuffle master to register partitions for task deployment. */
@@ -388,6 +391,11 @@ public class ExecutionGraph implements AccessExecutionGraph {
         return Optional.ofNullable(stateBackendName);
     }
 
+    @Override
+    public Optional<String> getCheckpointStorageName() {
+        return Optional.ofNullable(checkpointStorageName);
+    }
+
     public void enableCheckpointing(
             CheckpointCoordinatorConfiguration chkConfig,
             List<ExecutionJobVertex> verticesToTrigger,
@@ -397,6 +405,7 @@ public class ExecutionGraph implements AccessExecutionGraph {
             CheckpointIDCounter checkpointIDCounter,
             CompletedCheckpointStore checkpointStore,
             StateBackend checkpointStateBackend,
+            CheckpointStorage checkpointStorage,
             CheckpointStatsTracker statsTracker,
             CheckpointsCleaner checkpointsCleaner) {
 
@@ -478,6 +487,7 @@ public class ExecutionGraph implements AccessExecutionGraph {
         }
 
         this.stateBackendName = checkpointStateBackend.getClass().getSimpleName();
+        this.checkpointStorageName = checkpointStorageName.getClass().getSimpleName();
     }
 
     @Nullable
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java
index 9c8ca03..2acc9b4 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java
@@ -48,6 +48,8 @@ import org.apache.flink.runtime.jobgraph.jsonplan.JsonPlanGenerator;
 import org.apache.flink.runtime.jobgraph.tasks.CheckpointCoordinatorConfiguration;
 import org.apache.flink.runtime.jobgraph.tasks.JobCheckpointingSettings;
 import org.apache.flink.runtime.shuffle.ShuffleMaster;
+import org.apache.flink.runtime.state.CheckpointStorage;
+import org.apache.flink.runtime.state.CheckpointStorageLoader;
 import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.runtime.state.StateBackendLoader;
 import org.apache.flink.util.DynamicCodeLoadingException;
@@ -241,6 +243,39 @@ public class ExecutionGraphBuilder {
                         jobId, "Could not instantiate configured state backend", e);
             }
 
+            // load the checkpoint storage from the application settings
+            final CheckpointStorage applicationConfiguredStorage;
+            final SerializedValue<CheckpointStorage> serializedAppConfiguredStorage =
+                    snapshotSettings.getDefaultCheckpointStorage();
+
+            if (serializedAppConfiguredStorage == null) {
+                applicationConfiguredStorage = null;
+            } else {
+                try {
+                    applicationConfiguredStorage =
+                            serializedAppConfiguredStorage.deserializeValue(classLoader);
+                } catch (IOException | ClassNotFoundException e) {
+                    throw new JobExecutionException(
+                            jobId,
+                            "Could not deserialize application-defined checkpoint storage.",
+                            e);
+                }
+            }
+
+            final CheckpointStorage rootStorage;
+            try {
+                rootStorage =
+                        CheckpointStorageLoader.load(
+                                applicationConfiguredStorage,
+                                rootBackend,
+                                jobManagerConfig,
+                                classLoader,
+                                log);
+            } catch (IllegalConfigurationException | DynamicCodeLoadingException e) {
+                throw new JobExecutionException(
+                        jobId, "Could not instantiate configured checkpoint storage", e);
+            }
+
             // instantiate the user-defined checkpoint hooks
 
             final SerializedValue<MasterTriggerRestoreHook.Factory[]> serializedHooks =
@@ -284,6 +319,7 @@ public class ExecutionGraphBuilder {
                     checkpointIdCounter,
                     completedCheckpointStore,
                     rootBackend,
+                    rootStorage,
                     checkpointStatsTracker,
                     checkpointsCleaner);
         }
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/JobCheckpointingSettings.java b/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/JobCheckpointingSettings.java
index 28d5e24..ece6213 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/JobCheckpointingSettings.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/JobCheckpointingSettings.java
@@ -20,6 +20,7 @@ package org.apache.flink.runtime.jobgraph.tasks;
 
 import org.apache.flink.runtime.checkpoint.MasterTriggerRestoreHook;
 import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.apache.flink.runtime.state.CheckpointStorage;
 import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.util.Preconditions;
 import org.apache.flink.util.SerializedValue;
@@ -52,6 +53,9 @@ public class JobCheckpointingSettings implements Serializable {
     /** The default state backend, if configured by the user in the job */
     @Nullable private final SerializedValue<StateBackend> defaultStateBackend;
 
+    /** The default checkpoint storage, if configured by the user in the job */
+    @Nullable private final SerializedValue<CheckpointStorage> defaultCheckpointStorage;
+
     /** (Factories for) hooks that are executed on the checkpoint coordinator */
     @Nullable private final SerializedValue<MasterTriggerRestoreHook.Factory[]> masterHooks;
 
@@ -68,6 +72,7 @@ public class JobCheckpointingSettings implements Serializable {
                 verticesToConfirm,
                 checkpointCoordinatorConfiguration,
                 defaultStateBackend,
+                null,
                 null);
     }
 
@@ -77,6 +82,7 @@ public class JobCheckpointingSettings implements Serializable {
             List<JobVertexID> verticesToConfirm,
             CheckpointCoordinatorConfiguration checkpointCoordinatorConfiguration,
             @Nullable SerializedValue<StateBackend> defaultStateBackend,
+            @Nullable SerializedValue<CheckpointStorage> defaultCheckpointStorage,
             @Nullable SerializedValue<MasterTriggerRestoreHook.Factory[]> masterHooks) {
 
         this.verticesToTrigger = requireNonNull(verticesToTrigger);
@@ -85,6 +91,7 @@ public class JobCheckpointingSettings implements Serializable {
         this.checkpointCoordinatorConfiguration =
                 Preconditions.checkNotNull(checkpointCoordinatorConfiguration);
         this.defaultStateBackend = defaultStateBackend;
+        this.defaultCheckpointStorage = defaultCheckpointStorage;
         this.masterHooks = masterHooks;
     }
 
@@ -112,6 +119,11 @@ public class JobCheckpointingSettings implements Serializable {
     }
 
     @Nullable
+    public SerializedValue<CheckpointStorage> getDefaultCheckpointStorage() {
+        return defaultCheckpointStorage;
+    }
+
+    @Nullable
     public SerializedValue<MasterTriggerRestoreHook.Factory[]> getMasterHooks() {
         return masterHooks;
     }
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/job/checkpoints/CheckpointConfigHandler.java b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/job/checkpoints/CheckpointConfigHandler.java
index d005793..597bcb5 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/job/checkpoints/CheckpointConfigHandler.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/job/checkpoints/CheckpointConfigHandler.java
@@ -116,6 +116,7 @@ public class CheckpointConfigHandler
                             retentionPolicy != CheckpointRetentionPolicy.RETAIN_ON_CANCELLATION);
 
             String stateBackendName = executionGraph.getStateBackendName().orElse(null);
+            String checkpointStorageName = executionGraph.getCheckpointStorageName().orElse(null);
 
             return new CheckpointConfigInfo(
                     checkpointCoordinatorConfiguration.isExactlyOnce()
@@ -127,6 +128,7 @@ public class CheckpointConfigHandler
                     checkpointCoordinatorConfiguration.getMaxConcurrentCheckpoints(),
                     externalizedCheckpointInfo,
                     stateBackendName,
+                    checkpointStorageName,
                     checkpointCoordinatorConfiguration.isUnalignedCheckpointsEnabled(),
                     checkpointCoordinatorConfiguration.getTolerableCheckpointFailureNumber());
         }
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/messages/checkpoints/CheckpointConfigInfo.java b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/messages/checkpoints/CheckpointConfigInfo.java
index 6358546..93aa501 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/messages/checkpoints/CheckpointConfigInfo.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/messages/checkpoints/CheckpointConfigInfo.java
@@ -53,6 +53,8 @@ public class CheckpointConfigInfo implements ResponseBody {
 
     public static final String FIELD_NAME_STATE_BACKEND = "state_backend";
 
+    public static final String FIELD_NAME_CHECKPOINT_STORAGE = "checkpoint_storage";
+
     public static final String FIELD_NAME_UNALIGNED_CHECKPOINTS = "unaligned_checkpoints";
 
     public static final String FIELD_NAME_TOLERABLE_FAILED_CHECKPOINTS =
@@ -79,6 +81,9 @@ public class CheckpointConfigInfo implements ResponseBody {
     @JsonProperty(FIELD_NAME_STATE_BACKEND)
     private final String stateBackend;
 
+    @JsonProperty(FIELD_NAME_CHECKPOINT_STORAGE)
+    private final String checkpointStorage;
+
     @JsonProperty(FIELD_NAME_UNALIGNED_CHECKPOINTS)
     private final boolean unalignedCheckpoints;
 
@@ -95,6 +100,7 @@ public class CheckpointConfigInfo implements ResponseBody {
             @JsonProperty(FIELD_NAME_EXTERNALIZED_CHECKPOINT_CONFIG)
                     ExternalizedCheckpointInfo externalizedCheckpointInfo,
             @JsonProperty(FIELD_NAME_STATE_BACKEND) String stateBackend,
+            @JsonProperty(FIELD_NAME_CHECKPOINT_STORAGE) String checkpointStorage,
             @JsonProperty(FIELD_NAME_UNALIGNED_CHECKPOINTS) boolean unalignedCheckpoints,
             @JsonProperty(FIELD_NAME_TOLERABLE_FAILED_CHECKPOINTS) int tolerableFailedCheckpoints) {
         this.processingMode = Preconditions.checkNotNull(processingMode);
@@ -104,6 +110,7 @@ public class CheckpointConfigInfo implements ResponseBody {
         this.maxConcurrentCheckpoints = maxConcurrentCheckpoints;
         this.externalizedCheckpointInfo = Preconditions.checkNotNull(externalizedCheckpointInfo);
         this.stateBackend = Preconditions.checkNotNull(stateBackend);
+        this.checkpointStorage = Preconditions.checkNotNull(checkpointStorage);
         this.unalignedCheckpoints = unalignedCheckpoints;
         this.tolerableFailedCheckpoints = tolerableFailedCheckpoints;
     }
@@ -124,6 +131,7 @@ public class CheckpointConfigInfo implements ResponseBody {
                 && processingMode == that.processingMode
                 && Objects.equals(externalizedCheckpointInfo, that.externalizedCheckpointInfo)
                 && Objects.equals(stateBackend, that.stateBackend)
+                && Objects.equals(checkpointStorage, that.checkpointStorage)
                 && unalignedCheckpoints == that.unalignedCheckpoints
                 && tolerableFailedCheckpoints == that.tolerableFailedCheckpoints;
     }
@@ -138,6 +146,7 @@ public class CheckpointConfigInfo implements ResponseBody {
                 maxConcurrentCheckpoints,
                 externalizedCheckpointInfo,
                 stateBackend,
+                checkpointStorage,
                 unalignedCheckpoints,
                 tolerableFailedCheckpoints);
     }
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointSettingsSerializableTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointSettingsSerializableTest.java
index 54ce529..3c338ab 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointSettingsSerializableTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointSettingsSerializableTest.java
@@ -32,6 +32,7 @@ import org.apache.flink.runtime.jobgraph.tasks.CheckpointCoordinatorConfiguratio
 import org.apache.flink.runtime.jobgraph.tasks.JobCheckpointingSettings;
 import org.apache.flink.runtime.query.TaskKvStateRegistry;
 import org.apache.flink.runtime.state.AbstractKeyedStateBackend;
+import org.apache.flink.runtime.state.CheckpointStorage;
 import org.apache.flink.runtime.state.CheckpointStorageAccess;
 import org.apache.flink.runtime.state.CompletedCheckpointStorageLocation;
 import org.apache.flink.runtime.state.KeyGroupRange;
@@ -91,6 +92,8 @@ public class CheckpointSettingsSerializableTest extends TestLogger {
                                 false,
                                 0),
                         new SerializedValue<StateBackend>(new CustomStateBackend(outOfClassPath)),
+                        new SerializedValue<CheckpointStorage>(
+                                new CustomCheckpointStorage(outOfClassPath)),
                         serHooks);
 
         final JobGraph jobGraph = new JobGraph(new JobID(), "test job");
@@ -184,4 +187,27 @@ public class CheckpointSettingsSerializableTest extends TestLogger {
             throw new UnsupportedOperationException();
         }
     }
+
+    private static final class CustomCheckpointStorage implements CheckpointStorage {
+
+        private static final long serialVersionUID = -6107964383429395816L;
+        /** Simulate a custom option that is not in the normal classpath. */
+        @SuppressWarnings("unused")
+        private Serializable customOption;
+
+        public CustomCheckpointStorage(Serializable customOption) {
+            this.customOption = customOption;
+        }
+
+        @Override
+        public CompletedCheckpointStorageLocation resolveCheckpoint(String pointer)
+                throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public CheckpointStorageAccess createCheckpointStorage(JobID jobId) throws IOException {
+            return mock(CheckpointStorageAccess.class);
+        }
+    }
 }
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/dispatcher/DispatcherTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/dispatcher/DispatcherTest.java
index fadc123..706620d 100755
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/dispatcher/DispatcherTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/dispatcher/DispatcherTest.java
@@ -64,10 +64,10 @@ import org.apache.flink.runtime.rpc.RpcService;
 import org.apache.flink.runtime.rpc.RpcUtils;
 import org.apache.flink.runtime.rpc.TestingRpcService;
 import org.apache.flink.runtime.state.CheckpointMetadataOutputStream;
+import org.apache.flink.runtime.state.CheckpointStorage;
 import org.apache.flink.runtime.state.CheckpointStorageCoordinatorView;
 import org.apache.flink.runtime.state.CheckpointStorageLocation;
 import org.apache.flink.runtime.state.CompletedCheckpointStorageLocation;
-import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.runtime.testtasks.NoOpInvokable;
 import org.apache.flink.runtime.testutils.CommonTestUtils;
 import org.apache.flink.runtime.testutils.TestingJobGraphStore;
@@ -84,6 +84,7 @@ import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -568,6 +569,7 @@ public class DispatcherTest extends TestLogger {
 
     /** Tests that we can dispose a savepoint. */
     @Test
+    @Ignore("Ignored until MemoryStateBackend implements CheckpointStorage")
     public void testSavepointDisposal() throws Exception {
         final URI externalPointer = createTestingSavepoint();
         final Path savepointPath = Paths.get(externalPointer);
@@ -591,11 +593,11 @@ public class DispatcherTest extends TestLogger {
 
     @Nonnull
     private URI createTestingSavepoint() throws IOException, URISyntaxException {
-        final StateBackend stateBackend =
-                Checkpoints.loadStateBackend(
+        final CheckpointStorage storage =
+                Checkpoints.loadCheckpointStorage(
                         configuration, Thread.currentThread().getContextClassLoader(), log);
         final CheckpointStorageCoordinatorView checkpointStorage =
-                stateBackend.createCheckpointStorage(jobGraph.getJobID());
+                storage.createCheckpointStorage(jobGraph.getJobID());
         final File savepointFile = temporaryFolder.newFolder();
         final long checkpointId = 1L;
 
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/legacy/DefaultExecutionGraphCacheTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/legacy/DefaultExecutionGraphCacheTest.java
index 48b1867..65593bb 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/legacy/DefaultExecutionGraphCacheTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/legacy/DefaultExecutionGraphCacheTest.java
@@ -321,7 +321,8 @@ public class DefaultExecutionGraphCacheTest extends TestLogger {
                     false,
                     null,
                     null,
-                    "stateBackendName");
+                    "stateBackendName",
+                    "checkpointStorageName");
 
             jobStatus = super.getState();
         }
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/legacy/utils/ArchivedExecutionGraphBuilder.java b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/legacy/utils/ArchivedExecutionGraphBuilder.java
index 215bf91..6c4c7a8 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/legacy/utils/ArchivedExecutionGraphBuilder.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/handler/legacy/utils/ArchivedExecutionGraphBuilder.java
@@ -157,6 +157,7 @@ public class ArchivedExecutionGraphBuilder {
                 isStoppable,
                 null,
                 null,
-                "stateBackendName");
+                "stateBackendName",
+                "checkpointStorageName");
     }
 }
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/rest/messages/checkpoints/CheckpointConfigInfoTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/messages/checkpoints/CheckpointConfigInfoTest.java
index 97feef5..067eeaf 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/rest/messages/checkpoints/CheckpointConfigInfoTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/messages/checkpoints/CheckpointConfigInfoTest.java
@@ -41,6 +41,7 @@ public class CheckpointConfigInfoTest
                 4,
                 externalizedCheckpointInfo,
                 "stateBackendName",
+                "checkpointStorageName",
                 true,
                 3);
     }
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/state/CheckpointStorageLoaderTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/state/CheckpointStorageLoaderTest.java
index fa8745e..1ca401c 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/state/CheckpointStorageLoaderTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/state/CheckpointStorageLoaderTest.java
@@ -38,18 +38,15 @@ import org.junit.Test;
 import java.io.IOException;
 import java.util.Collection;
 
-import static org.junit.Assert.assertNull;
-
 /** This test validates that checkpoint storage is properly loaded from configuration. */
-public class CheckpointStorageLoadingTest {
+public class CheckpointStorageLoaderTest {
 
     private final ClassLoader cl = getClass().getClassLoader();
 
     @Test
     public void testNoCheckpointStorageDefined() throws Exception {
-        assertNull(
-                CheckpointStorageLoader.fromConfig(
-                        new Configuration(), cl, null));
+        Assert.assertFalse(
+                CheckpointStorageLoader.fromConfig(new Configuration(), cl, null).isPresent());
     }
 
     @Test
@@ -58,24 +55,22 @@ public class CheckpointStorageLoadingTest {
         CheckpointStorage storage = new MockStorage();
 
         CheckpointStorage configured =
-                CheckpointStorageLoader.fromApplicationOrConfigOrDefault(
-                        storage, legacy, new Configuration(), cl, null);
+                CheckpointStorageLoader.load(storage, legacy, new Configuration(), cl, null);
 
         Assert.assertEquals(
-                "Legacy state backends should always take precendence", legacy, configured);
+                "Legacy state backends should always take precedence", legacy, configured);
     }
 
     @Test
     public void testModernStateBackendDoesNotTakePrecedence() throws Exception {
-        StateBackend legacy = new ModernStateBackend();
+        StateBackend modern = new ModernStateBackend();
         CheckpointStorage storage = new MockStorage();
 
         CheckpointStorage configured =
-                CheckpointStorageLoader.fromApplicationOrConfigOrDefault(
-                        storage, legacy, new Configuration(), cl, null);
+                CheckpointStorageLoader.load(storage, modern, new Configuration(), cl, null);
 
         Assert.assertEquals(
-                "Modern state backends should never take precendence", storage, configured);
+                "Modern state backends should never take precedence", storage, configured);
     }
 
     @Test
@@ -84,8 +79,7 @@ public class CheckpointStorageLoadingTest {
 
         config.setString(CheckpointingOptions.CHECKPOINT_STORAGE, WorkingFactory.class.getName());
         CheckpointStorage storage =
-                CheckpointStorageLoader.fromApplicationOrConfigOrDefault(
-                        null, new ModernStateBackend(), config, cl, null);
+                CheckpointStorageLoader.load(null, new ModernStateBackend(), config, cl, null);
         Assert.assertThat(storage, Matchers.instanceOf(MockStorage.class));
     }
 
@@ -95,8 +89,7 @@ public class CheckpointStorageLoadingTest {
 
         config.setString(CheckpointingOptions.CHECKPOINT_STORAGE, "does.not.exist");
         try {
-            CheckpointStorageLoader.fromApplicationOrConfigOrDefault(
-                    null, new ModernStateBackend(), config, cl, null);
+            CheckpointStorageLoader.load(null, new ModernStateBackend(), config, cl, null);
             Assert.fail("should fail with exception");
         } catch (DynamicCodeLoadingException e) {
             // expected
@@ -105,8 +98,7 @@ public class CheckpointStorageLoadingTest {
         // try a class that is not a factory
         config.setString(CheckpointingOptions.CHECKPOINT_STORAGE, java.io.File.class.getName());
         try {
-            CheckpointStorageLoader.fromApplicationOrConfigOrDefault(
-                    null, new ModernStateBackend(), config, cl, null);
+            CheckpointStorageLoader.load(null, new ModernStateBackend(), config, cl, null);
             Assert.fail("should fail with exception");
         } catch (DynamicCodeLoadingException e) {
             // expected
@@ -115,8 +107,7 @@ public class CheckpointStorageLoadingTest {
         // try a factory that fails
         config.setString(CheckpointingOptions.CHECKPOINT_STORAGE, FailingFactory.class.getName());
         try {
-            CheckpointStorageLoader.fromApplicationOrConfigOrDefault(
-                    null, new ModernStateBackend(), config, cl, null);
+            CheckpointStorageLoader.load(null, new ModernStateBackend(), config, cl, null);
             Assert.fail("should fail with exception");
         } catch (IllegalConfigurationException e) {
             // expected
diff --git a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamConfig.java b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamConfig.java
index 67b43cc..fd4a291 100644
--- a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamConfig.java
+++ b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamConfig.java
@@ -27,6 +27,7 @@ import org.apache.flink.configuration.Configuration;
 import org.apache.flink.core.memory.ManagedMemoryUseCase;
 import org.apache.flink.runtime.jobgraph.OperatorID;
 import org.apache.flink.runtime.operators.util.CorruptConfigurationException;
+import org.apache.flink.runtime.state.CheckpointStorage;
 import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.runtime.util.ClassLoaderUtil;
 import org.apache.flink.runtime.util.config.memory.ManagedMemoryUtils;
@@ -94,6 +95,7 @@ public class StreamConfig implements Serializable {
     private static final String CHECKPOINTING_ENABLED = "checkpointing";
     private static final String CHECKPOINT_MODE = "checkpointMode";
 
+    private static final String CHECKPOINT_STORAGE = "checkpointstorage";
     private static final String STATE_BACKEND = "statebackend";
     private static final String TIMER_SERVICE_PROVIDER = "timerservice";
     private static final String STATE_PARTITIONER = "statePartitioner";
@@ -563,6 +565,24 @@ public class StreamConfig implements Serializable {
         }
     }
 
+    public void setCheckpointStorage(CheckpointStorage storage) {
+        if (storage != null) {
+            try {
+                InstantiationUtil.writeObjectToConfig(storage, config, CHECKPOINT_STORAGE);
+            } catch (Exception e) {
+                throw new StreamTaskException("Could not serialize checkpoint storage.", e);
+            }
+        }
+    }
+
+    public CheckpointStorage getCheckpointStorage(ClassLoader cl) {
+        try {
+            return InstantiationUtil.readObjectFromConfig(this.config, CHECKPOINT_STORAGE, cl);
+        } catch (Exception e) {
+            throw new StreamTaskException("Could not instantiate checkpoint storage.", e);
+        }
+    }
+
     public void setTimerServiceProvider(InternalTimeServiceManager.Provider timerServiceProvider) {
         if (timerServiceProvider != null) {
             try {
diff --git a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamGraph.java b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamGraph.java
index 93a2d98..dfcfe35 100644
--- a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamGraph.java
+++ b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamGraph.java
@@ -37,6 +37,7 @@ import org.apache.flink.runtime.jobgraph.JobGraph;
 import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings;
 import org.apache.flink.runtime.jobgraph.ScheduleMode;
 import org.apache.flink.runtime.jobgraph.tasks.AbstractInvokable;
+import org.apache.flink.runtime.state.CheckpointStorage;
 import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.streaming.api.TimeCharacteristic;
 import org.apache.flink.streaming.api.environment.CheckpointConfig;
@@ -113,6 +114,7 @@ public class StreamGraph implements Pipeline {
     protected Map<Integer, String> vertexIDtoBrokerID;
     protected Map<Integer, Long> vertexIDtoLoopTimeout;
     private StateBackend stateBackend;
+    private CheckpointStorage checkpointStorage;
     private Set<Tuple2<StreamNode, StreamNode>> iterationSourceSinkPairs;
     private InternalTimeServiceManager.Provider timerServiceProvider;
 
@@ -176,6 +178,14 @@ public class StreamGraph implements Pipeline {
         return this.stateBackend;
     }
 
+    public void setCheckpointStorage(CheckpointStorage checkpointStorage) {
+        this.checkpointStorage = checkpointStorage;
+    }
+
+    public CheckpointStorage getCheckpointStorage() {
+        return this.checkpointStorage;
+    }
+
     public InternalTimeServiceManager.Provider getTimerServiceProvider() {
         return timerServiceProvider;
     }
diff --git a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamGraphGenerator.java b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamGraphGenerator.java
index 0092517..09d6652 100644
--- a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamGraphGenerator.java
+++ b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamGraphGenerator.java
@@ -30,6 +30,7 @@ import org.apache.flink.configuration.ExecutionOptions;
 import org.apache.flink.configuration.ReadableConfig;
 import org.apache.flink.runtime.jobgraph.SavepointRestoreSettings;
 import org.apache.flink.runtime.jobgraph.ScheduleMode;
+import org.apache.flink.runtime.state.CheckpointStorage;
 import org.apache.flink.runtime.state.KeyGroupRangeAssignment;
 import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.streaming.api.TimeCharacteristic;
@@ -137,6 +138,8 @@ public class StreamGraphGenerator {
 
     private StateBackend stateBackend;
 
+    private CheckpointStorage checkpointStorage;
+
     private boolean chaining = true;
 
     private Collection<Tuple2<String, DistributedCache.DistributedCacheEntry>> userArtifacts;
@@ -300,6 +303,7 @@ public class StreamGraphGenerator {
             setBatchStateBackendAndTimerService(graph);
         } else {
             graph.setStateBackend(stateBackend);
+            graph.setCheckpointStorage(checkpointStorage);
             graph.setScheduleMode(ScheduleMode.EAGER);
 
             if (checkpointConfig.isApproximateLocalRecoveryEnabled()) {
diff --git a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java
index 28b8075..3b54523 100644
--- a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java
+++ b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java
@@ -49,6 +49,7 @@ import org.apache.flink.runtime.jobmanager.scheduler.CoLocationGroupImpl;
 import org.apache.flink.runtime.jobmanager.scheduler.SlotSharingGroup;
 import org.apache.flink.runtime.operators.coordination.OperatorCoordinator;
 import org.apache.flink.runtime.operators.util.TaskConfig;
+import org.apache.flink.runtime.state.CheckpointStorage;
 import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.runtime.util.config.memory.ManagedMemoryUtils;
 import org.apache.flink.streaming.api.CheckpointingMode;
@@ -1283,6 +1284,21 @@ public class StreamingJobGraphGenerator {
             }
         }
 
+        // because the checkpoint storage can have user-defined code, it needs to be stored as
+        // eagerly serialized value
+        final SerializedValue<CheckpointStorage> serializedCheckpointStorage;
+        if (streamGraph.getCheckpointStorage() == null) {
+            serializedCheckpointStorage = null;
+        } else {
+            try {
+                serializedCheckpointStorage =
+                        new SerializedValue<>(streamGraph.getCheckpointStorage());
+            }
+            catch (IOException e) {
+                throw new FlinkRuntimeException("State backend is not serializable", e);
+            }
+        }
+
         //  --- done, put it all together ---
 
         JobCheckpointingSettings settings =
@@ -1305,6 +1321,7 @@ public class StreamingJobGraphGenerator {
                                 .setAlignmentTimeout(cfg.getAlignmentTimeout())
                                 .build(),
                         serializedStateBackend,
+                        serializedCheckpointStorage,
                         serializedHooks);
 
         jobGraph.setSnapshotSettings(settings);
diff --git a/flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java b/flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
index d97150c..bb84e14 100644
--- a/flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
+++ b/flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
@@ -52,6 +52,8 @@ import org.apache.flink.runtime.metrics.groups.TaskIOMetricGroup;
 import org.apache.flink.runtime.operators.coordination.OperatorEvent;
 import org.apache.flink.runtime.plugable.SerializationDelegate;
 import org.apache.flink.runtime.security.FlinkSecurityManager;
+import org.apache.flink.runtime.state.CheckpointStorage;
+import org.apache.flink.runtime.state.CheckpointStorageLoader;
 import org.apache.flink.runtime.state.CheckpointStorageWorkerView;
 import org.apache.flink.runtime.state.StateBackend;
 import org.apache.flink.runtime.state.StateBackendLoader;
@@ -183,9 +185,12 @@ public abstract class StreamTask<OUT, OP extends StreamOperator<OUT>> extends Ab
     /** The configuration of this streaming task. */
     protected final StreamConfig configuration;
 
-    /** Our state backend. We use this to create checkpoint streams and a keyed state backend. */
+    /** Our state backend. We use this to create a keyed state backend. */
     protected final StateBackend stateBackend;
 
+    /** Our checkpoint storage. We use this to create checkpoint streams. */
+    protected final CheckpointStorage checkpointStorage;
+
     private final SubtaskCheckpointCoordinator subtaskCheckpointCoordinator;
 
     /**
@@ -315,10 +320,11 @@ public abstract class StreamTask<OUT, OP extends StreamOperator<OUT>> extends Ab
                         new ExecutorThreadFactory("AsyncOperations", uncaughtExceptionHandler));
 
         this.stateBackend = createStateBackend();
+        this.checkpointStorage = createCheckpointStorage(stateBackend);
 
         this.subtaskCheckpointCoordinator =
                 new SubtaskCheckpointCoordinatorImpl(
-                        stateBackend.createCheckpointStorage(getEnvironment().getJobID()),
+                        checkpointStorage.createCheckpointStorage(getEnvironment().getJobID()),
                         getName(),
                         actionExecutor,
                         getCancelables(),
@@ -1141,6 +1147,18 @@ public abstract class StreamTask<OUT, OP extends StreamOperator<OUT>> extends Ab
                 LOG);
     }
 
+
+    private CheckpointStorage createCheckpointStorage(StateBackend backend) throws Exception {
+        final CheckpointStorage fromApplication = configuration.getCheckpointStorage(getUserCodeClassLoader());
+
+        return CheckpointStorageLoader.load(
+                fromApplication,
+                backend,
+                getEnvironment().getTaskManagerInfo().getConfiguration(),
+                getUserCodeClassLoader(),
+                LOG);
+    }
+
     /**
      * Returns the {@link TimerService} responsible for telling the current processing time and
      * registering actual timers.
diff --git a/flink-table/flink-table-planner-blink/src/main/java/org/apache/flink/table/planner/utils/ExecutorUtils.java b/flink-table/flink-table-planner-blink/src/main/java/org/apache/flink/table/planner/utils/ExecutorUtils.java
index e6a7e0c..02bd028 100644
--- a/flink-table/flink-table-planner-blink/src/main/java/org/apache/flink/table/planner/utils/ExecutorUtils.java
+++ b/flink-table/flink-table-planner-blink/src/main/java/org/apache/flink/table/planner/utils/ExecutorUtils.java
@@ -69,6 +69,7 @@ public class ExecutorUtils {
         streamGraph.setAllVerticesInSameSlotSharingGroupByDefault(false);
         streamGraph.setScheduleMode(ScheduleMode.LAZY_FROM_SOURCES_WITH_BATCH_SLOT_REQUEST);
         streamGraph.setStateBackend(null);
+        streamGraph.setCheckpointStorage(null);
         if (streamGraph.getCheckpointConfig().isCheckpointingEnabled()) {
             throw new IllegalArgumentException("Checkpoint is not supported for batch jobs.");
         }