diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
index 1b3f60ff4a78b70f02a259df526e7bc63512ef3d..b2d1d291671d4a900ad60c1fc268e5091cafdd98 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -17,6 +17,7 @@
 use Drupal\Core\Field\FieldException;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Site\Settings;
 
 /**
  * Defines a schema handler that supports revisionable, translatable entities.
@@ -95,6 +96,13 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
    */
   protected $installedStorageSchema;
 
+  /**
+   * The key-value collection for tracking entity update backup repository.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $updateBackupRepository;
+
   /**
    * The deleted fields repository.
    *
@@ -163,6 +171,23 @@ protected function deletedFieldsRepository() {
     return $this->deletedFieldsRepository;
   }
 
+  /**
+   * Gets the key/value collection for tracking the entity update backups.
+   *
+   * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   *   A key/value collection.
+   *
+   * @todo Inject this dependency in the constructor once this class can be
+   *   instantiated as a regular entity handler.
+   *   @see https://www.drupal.org/node/2332857
+   */
+  protected function updateBackupRepository() {
+    if (!isset($this->updateBackupRepository)) {
+      $this->updateBackupRepository = \Drupal::keyValue('entity.update_backup');
+    }
+    return $this->updateBackupRepository;
+  }
+
   /**
    * Refreshes the table mapping with updated definitions.
    *
@@ -432,7 +457,11 @@ protected function preUpdateEntityTypeSchema(EntityTypeInterface $entity_type, E
     $sandbox['temporary_table_mapping'] = $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions, $temporary_prefix);
     $sandbox['new_table_mapping'] = $this->storage->getCustomTableMapping($entity_type, $field_storage_definitions);
     $sandbox['original_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions);
-    $sandbox['backup_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions, 'old_');
+
+    $backup_prefix = static::getTemporaryTableMappingPrefix($original, $original_field_storage_definitions, 'old_');
+    $sandbox['backup_table_mapping'] = $this->storage->getCustomTableMapping($original, $original_field_storage_definitions, $backup_prefix);
+    $sandbox['backup_prefix_key'] = substr($backup_prefix, 4);
+    $sandbox['backup_request_time'] = \Drupal::time()->getRequestTime();
 
     // Create temporary tables based on the new entity type and field storage
     // definitions.
@@ -554,11 +583,22 @@ protected function postUpdateEntityTypeSchema(EntityTypeInterface $entity_type,
     }
 
     // At this point the update process either finished successfully or any
-    // error has been thrown already, so we can drop the backup entity tables.
-    // @todo Decide whether we should keep these tables around.
-    //   @see https://www.drupal.org/project/drupal/issues/3024728
-    foreach ($backup_table_names as $original_table_name => $backup_table_name) {
-      $this->database->schema()->dropTable($backup_table_name);
+    // error has been thrown already. We can either keep the backup tables in
+    // place or drop them.
+    if (Settings::get('entity_update_backup', TRUE)) {
+      $backup_key = $sandbox['backup_prefix_key'];
+      $backup = [
+        'entity_type' => $original,
+        'field_storage_definitions' => $original_field_storage_definitions,
+        'table_mapping' => $backup_table_mapping,
+        'request_time' => $sandbox['backup_request_time'],
+      ];
+      $this->updateBackupRepository()->set("{$original->id()}.$backup_key", $backup);
+    }
+    else {
+      foreach ($backup_table_names as $original_table_name => $backup_table_name) {
+        $this->database->schema()->dropTable($backup_table_name);
+      }
     }
   }
 
@@ -582,13 +622,15 @@ protected function handleEntityTypeSchemaUpdateExceptionOnDataCopy(EntityTypeInt
    *   An entity type definition.
    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_storage_definitions
    *   An array of field storage definitions.
+   * @param string $first_prefix_part
+   *   (optional) The first part of the prefix. Defaults to 'tmp_'.
    *
    * @return string
    *   A temporary table mapping prefix.
    *
    * @internal
    */
-  public static function getTemporaryTableMappingPrefix(EntityTypeInterface $entity_type, array $field_storage_definitions) {
+  public static function getTemporaryTableMappingPrefix(EntityTypeInterface $entity_type, array $field_storage_definitions, $first_prefix_part = 'tmp_') {
     // Construct a unique prefix based on the contents of the entity type and
     // field storage definitions.
     $prefix_parts[] = spl_object_hash($entity_type);
@@ -598,7 +640,7 @@ public static function getTemporaryTableMappingPrefix(EntityTypeInterface $entit
     $prefix_parts[] = \Drupal::time()->getRequestTime();
     $hash = hash('sha256', implode('', $prefix_parts));
 
-    return 'tmp_' . substr($hash, 0, 6);
+    return $first_prefix_part . substr($hash, 0, 6);
   }
 
   /**
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/FieldableEntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/FieldableEntityDefinitionUpdateTest.php
index d3beb19f73a3299fa6db29c908c9800bd75bf368..d9a50feb65b009e1faeb5ea61280a84baa1075e7 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/FieldableEntityDefinitionUpdateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/FieldableEntityDefinitionUpdateTest.php
@@ -165,6 +165,9 @@ public function testFieldableEntityTypeUpdates($initial_rev, $initial_mul, $new_
     // Check that we can still save new entities after the schema has been
     // updated.
     $this->insertData($new_rev, $new_mul);
+
+    // Check that the backup tables have been kept in place.
+    $this->assertBackupTables();
   }
 
   /**
@@ -569,10 +572,25 @@ protected function assertNonRevisionableAndNonTranslatable() {
     $this->assertFalse($database_schema->tableExists($entity_type->getRevisionDataTable()));
   }
 
+  /**
+   * Asserts that the backup tables have been kept after a successful update.
+   */
+  protected function assertBackupTables() {
+    $backups = \Drupal::keyValue('entity.update_backup')->getAll();
+    $backup = reset($backups);
+
+    $schema = $this->database->schema();
+    foreach ($backup['table_mapping']->getTableNames() as $table_name) {
+      $this->assertTrue($schema->tableExists($table_name));
+    }
+  }
+
   /**
    * Tests that a failed entity schema update preserves the existing data.
    */
   public function testFieldableEntityTypeUpdatesErrorHandling() {
+    $schema = $this->database->schema();
+
     // First, convert the entity type to be translatable for better coverage and
     // insert some initial data.
     $entity_type = $this->getUpdatedEntityTypeDefinition(FALSE, TRUE);
@@ -581,6 +599,12 @@ public function testFieldableEntityTypeUpdatesErrorHandling() {
     $this->assertEntityTypeSchema(FALSE, TRUE);
     $this->insertData(FALSE, TRUE);
 
+    $tables = $schema->findTables('old_%');
+    $this->assertCount(3, $tables);
+    foreach ($tables as $table) {
+      $schema->dropTable($table);
+    }
+
     $original_entity_type = $this->lastInstalledSchemaRepository->getLastInstalledDefinition('entity_test_update');
     $original_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update');
 
@@ -640,14 +664,19 @@ public function testFieldableEntityTypeUpdatesErrorHandling() {
       $this->assertEquals($original_field_schema_data, $new_field_schema_data);
 
       // Check that temporary tables have been removed.
-      $schema = $this->database->schema();
-      $temporary_table_names = $storage->getCustomTableMapping($new_entity_type, $new_storage_definitions, 'tmp_')->getTableNames();
-      $current_table_names = $storage->getCustomTableMapping($new_entity_type, $new_storage_definitions)->getTableNames();
-      foreach (array_combine($temporary_table_names, $current_table_names) as $temp_table_name => $table_name) {
+      $tables = $schema->findTables('tmp_%');
+      $this->assertCount(0, $tables);
+
+      $current_table_names = $storage->getCustomTableMapping($original_entity_type, $original_storage_definitions)->getTableNames();
+      foreach ($current_table_names as $table_name) {
         $this->assertTrue($schema->tableExists($table_name));
-        $this->assertFalse($schema->tableExists($temp_table_name));
       }
 
+      // Check that backup tables do not exist anymore, since they were
+      // restored/renamed.
+      $tables = $schema->findTables('old_%');
+      $this->assertCount(0, $tables);
+
       // Check that the original tables still exist and their data is intact.
       $this->assertTrue($schema->tableExists('entity_test_update'));
       $this->assertTrue($schema->tableExists('entity_test_update_data'));
@@ -723,4 +752,37 @@ public function testFieldableEntityTypeUpdatesErrorHandling() {
     }
   }
 
+  /**
+   * Tests the removal of the backup tables after a successful update.
+   */
+  public function testFieldableEntityTypeUpdatesRemoveBackupTables() {
+    $schema = $this->database->schema();
+
+    // Convert the entity type to be revisionable.
+    $entity_type = $this->getUpdatedEntityTypeDefinition(TRUE, FALSE);
+    $field_storage_definitions = $this->getUpdatedFieldStorageDefinitions(TRUE, FALSE);
+    $this->entityDefinitionUpdateManager->updateFieldableEntityType($entity_type, $field_storage_definitions);
+
+    // Check that backup tables are kept by default.
+    $tables = $schema->findTables('old_%');
+    $this->assertCount(3, $tables);
+    foreach ($tables as $table) {
+      $schema->dropTable($table);
+    }
+
+    // Make the entity update process drop the backup tables after a successful
+    // update.
+    $settings = Settings::getAll();
+    $settings['entity_update_backup'] = FALSE;
+    new Settings($settings);
+
+    $entity_type = $this->getUpdatedEntityTypeDefinition(TRUE, TRUE);
+    $field_storage_definitions = $this->getUpdatedFieldStorageDefinitions(TRUE, TRUE);
+    $this->entityDefinitionUpdateManager->updateFieldableEntityType($entity_type, $field_storage_definitions);
+
+    // Check that backup tables have been dropped.
+    $tables = $schema->findTables('old_%');
+    $this->assertCount(0, $tables);
+  }
+
 }
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index ae64d2ffe89aa6f57eaccbb9567d46859fda2c48..5922cb1ea265a803b85f820c5ab24cfc0d5b3dc2 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -754,6 +754,15 @@
  */
 $settings['entity_update_batch_size'] = 50;
 
+/**
+ * Entity update backup.
+ *
+ * This is used to inform the entity storage handler that the backup tables as
+ * well as the original entity type and field storage definitions should be
+ * retained after a successful entity update process.
+ */
+$settings['entity_update_backup'] = TRUE;
+
 /**
  * Load local development override configuration, if available.
  *