diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index e7da6ec..708c793 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2505,11 +2505,23 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
     $container
       ->register('config.storage.staging', 'Drupal\Core\Config\FileStorage')
       ->addArgument(config_get_config_directory(CONFIG_STAGING_DIRECTORY));
+    $container
+      ->register('state.storage', 'Drupal\Core\KeyValueStore\DatabaseStorage')
+      ->addArgument('state');
   }
   return $container;
 }
 
 /**
+ * Returns the state storage service.
+ *
+ * @return Drupal\Core\KeyValueStore\KeyValueStoreInterface
+ */
+function state() {
+  return drupal_container()->get('state.storage');
+}
+
+/**
  * Returns the test prefix if this is an internal request from SimpleTest.
  *
  * @return
diff --git a/core/includes/common.inc b/core/includes/common.inc
index d21b9ce..57d5d10 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3176,7 +3176,7 @@ function drupal_pre_render_styles($elements) {
 function drupal_build_css_cache($css) {
   $data = '';
   $uri = '';
-  $map = variable_get('drupal_css_cache_files', array());
+  $map = state()->get('drupal_css_cache_files') ?: array();
   // Create a new array so that only the file names are used to create the hash.
   // This prevents new aggregates from being created unnecessarily.
   $css_data = array();
@@ -3244,7 +3244,7 @@ function drupal_build_css_cache($css) {
     }
     // Save the updated map.
     $map[$key] = $uri;
-    variable_set('drupal_css_cache_files', $map);
+    state()->set('drupal_css_cache_files', $map);
   }
   return $uri;
 }
@@ -3409,7 +3409,7 @@ function _drupal_load_stylesheet($matches) {
  * Deletes old cached CSS files.
  */
 function drupal_clear_css_cache() {
-  variable_del('drupal_css_cache_files');
+  state()->delete('drupal_css_cache_files');
   file_scan_directory('public://css', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
 }
 
@@ -4719,7 +4719,7 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro
 function drupal_build_js_cache($files) {
   $contents = '';
   $uri = '';
-  $map = variable_get('drupal_js_cache_files', array());
+  $map = state()->get('drupal_js_cache_files') ?: array();
   // Create a new array so that only the file names are used to create the hash.
   // This prevents new aggregates from being created unnecessarily.
   $js_data = array();
@@ -4764,7 +4764,7 @@ function drupal_build_js_cache($files) {
       }
     }
     $map[$key] = $uri;
-    variable_set('drupal_js_cache_files', $map);
+    state()->set('drupal_js_cache_files', $map);
   }
   return $uri;
 }
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 4a37bc2..3f6798d 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -311,7 +311,9 @@ function install_begin_request(&$install_state) {
     $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
       ->addArgument(new Reference('config.storage'))
       ->addArgument(new Reference('dispatcher'));
-
+    $container
+      ->register('state.storage', 'Drupal\Core\KeyValueStore\DatabaseStorage')
+      ->addArgument('state');
     drupal_container($container);
   }
 
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 8574dbe..4aeda61 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -451,7 +451,7 @@ function menu_get_item($path = NULL, $router_item = NULL) {
   if (!isset($router_items[$path])) {
     // Rebuild if we know it's needed, or if the menu masks are missing which
     // occurs rarely, likely due to a race condition of multiple rebuilds.
-    if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
+    if (state()->get('menu_rebuild_needed') || !variable_get('menu_masks', array())) {
       menu_router_rebuild();
     }
     $original_map = arg(NULL, $path);
@@ -2666,7 +2666,7 @@ function menu_router_rebuild() {
     menu_cache_clear_all();
     _menu_clear_page_cache();
     // Indicate that the menu has been successfully rebuilt.
-    variable_del('menu_rebuild_needed');
+    state()->delete('menu_rebuild_needed');
   }
   catch (Exception $e) {
     $transaction->rollback();
diff --git a/core/includes/path.inc b/core/includes/path.inc
index 07aeee5..71c10a3 100644
--- a/core/includes/path.inc
+++ b/core/includes/path.inc
@@ -72,7 +72,7 @@ function drupal_lookup_path($action, $path = '', $langcode = NULL) {
 
   // Retrieve the path alias whitelist.
   if (!isset($cache['whitelist'])) {
-    $cache['whitelist'] = variable_get('path_alias_whitelist', NULL);
+    $cache['whitelist'] = state()->get('path_alias_whitelist');
     if (!isset($cache['whitelist'])) {
       $cache['whitelist'] = drupal_path_alias_whitelist_rebuild();
     }
@@ -391,7 +391,7 @@ function drupal_path_alias_whitelist_rebuild($source = NULL) {
   // When paths are inserted, only rebuild the whitelist if the system path
   // has a top level component which is not already in the whitelist.
   if (!empty($source)) {
-    $whitelist = variable_get('path_alias_whitelist', NULL);
+    $whitelist = state()->get('path_alias_whitelist');
     if (isset($whitelist[strtok($source, '/')])) {
       return $whitelist;
     }
@@ -404,7 +404,7 @@ function drupal_path_alias_whitelist_rebuild($source = NULL) {
   foreach ($result as $row) {
     $whitelist[$row->path] = TRUE;
   }
-  variable_set('path_alias_whitelist', $whitelist);
+  state()->set('path_alias_whitelist', $whitelist);
   return $whitelist;
 }
 
diff --git a/core/includes/update.inc b/core/includes/update.inc
index 9dd8456..c6d6569 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -130,6 +130,36 @@ function update_prepare_d8_bootstrap() {
     update_extra_requirements($requirements);
 
     if ($has_required_schema) {
+      if (!db_table_exists('keyvalue')) {
+        $specs = array(
+          'description' => 'Generic key-value storage table.',
+          'fields' => array(
+            'name' => array(
+              'description' => 'The key.',
+              'type' => 'varchar',
+              'length' => 128,
+              'not null' => TRUE,
+              'default' => '',
+            ),
+            'collection' => array(
+              'description' => 'The collection of the variable.',
+              'type' => 'varchar',
+              'length' => 128,
+              'not null' => TRUE,
+              'default' => '',
+            ),
+            'value' => array(
+              'description' => 'The value.',
+              'type' => 'blob',
+              'not null' => TRUE,
+              'size' => 'big',
+              'translatable' => TRUE,
+            ),
+          ),
+          'primary key' => array('collection', 'name'),
+        );
+        db_create_table('keyvalue', $specs);
+      }
       // Bootstrap variables so we can update theme while preparing the update
       // process.
       drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
diff --git a/core/lib/Drupal/Core/KeyValueStore/AbstractStorage.php b/core/lib/Drupal/Core/KeyValueStore/AbstractStorage.php
new file mode 100644
index 0000000..a1ba2e4
--- /dev/null
+++ b/core/lib/Drupal/Core/KeyValueStore/AbstractStorage.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\KeyValueStore\AbstractStorage.
+ */
+
+namespace Drupal\Core\KeyValueStore;
+
+abstract class AbstractStorage implements KeyValueStoreInterface {
+
+  /**
+   * @var string
+   */
+  protected $collection;
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::__construct().
+   */
+  public function __construct($collection, array $options = array()) {
+    $this->collection = $collection;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getCollectionName().
+   */
+  public function getCollectionName() {
+    return $this->collection;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::get().
+   */
+  public function get($key) {
+    $values = $this->getMultiple(array($key));
+    return reset($values);
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::setMultiple().
+   */
+  public function setMultiple(array $data) {
+    foreach ($data as $key => $value) {
+      $this->set($key, $value);
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::delete().
+   */
+  public function delete($key) {
+    $this->deleteMultiple(array($key));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
new file mode 100644
index 0000000..ecf0763
--- /dev/null
+++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\KeyValueStore\DatabaseStorage.
+ */
+
+namespace Drupal\Core\KeyValueStore;
+
+/**
+ * Defines a default key/value store implementation.
+ *
+ * This is Drupal's default key/value store implementation. It uses the database
+ * to store key/value data.
+ */
+class DatabaseStorage extends AbstractStorage {
+
+  /**
+   * Overrides Drupal\Core\KeyValueStore\AbstractStorage::__construct().
+   */
+  public function __construct($collection, array $options = array()) {
+    parent::__construct($collection, $options);
+    $this->table = isset($options['table']) ? $options['table'] : 'keyvalue';
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getMultiple().
+   */
+  public function getMultiple(array $keys) {
+    try {
+      $result = db_query('SELECT name, value FROM {' . db_escape_table($this->table) . '} WHERE name IN (:keys) AND collection = :collection', array(':keys' => $keys, ':collection' => $this->collection))->fetchAllAssoc('name');
+      $values = array();
+      foreach ($keys as $key) {
+        if (isset($result[$key])) {
+          $values[$key] =  unserialize($result[$key]->value);
+        }
+      }
+      return $values;
+    }
+    catch (\Exception $e) {
+      // If the database is never going to be available, key/value requests should
+      // return FALSE in order to allow exception handling to occur.
+      return array();
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getAll().
+   */
+  public function getAll() {
+    $result = db_query('SELECT name, value FROM {' . db_escape_table($this->table) . '} WHERE collection = :collection', array(':collection' => $this->collection));
+    $values = array();
+
+    foreach ($result as $item) {
+      if ($item) {
+        $values[$item->name] = unserialize($item->value);
+      }
+    }
+    return $values;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::set().
+   */
+  public function set($key, $value) {
+    db_merge($this->table)
+      ->key(array(
+        'name' => $key,
+        'collection' => $this->collection,
+      ))
+      ->fields(array('value' => serialize($value)))
+      ->execute();
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::deleteMultiple().
+   */
+  public function deleteMultiple(array $keys) {
+    // Delete in chunks when a large array is passed.
+    do {
+      db_delete($this->table)
+        ->condition('name', array_splice($keys, 0, 1000))
+        ->condition('collection', $this->collection)
+        ->execute();
+    }
+    while (count($keys));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php
new file mode 100644
index 0000000..769888b
--- /dev/null
+++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\KeyValueStore\KeyValueStoreInterface.
+ */
+
+namespace Drupal\Core\KeyValueStore;
+
+/**
+ * Defines the interface for key/value store implementations.
+ */
+interface KeyValueStoreInterface {
+
+  /**
+   * Constructs a new key/value collection.
+   *
+   * @param string $collection
+   *   The collection for which the object is created.
+   * @param array $options
+   *   An associative array of options for the key/value storage collection.
+   */
+  public function __construct($collection, array $options = array());
+
+  /**
+   * Returns the name of this collection.
+   *
+   * @return string
+   *   The name of this collection.
+   */
+  public function getCollectionName();
+
+  /**
+   * Returns the stored value for a given key.
+   *
+   * @param string $key
+   *   The key of the data to retrieve.
+   *
+   * @return mixed
+   *   The stored value, or FALSE if no value exists.
+   */
+  public function get($key);
+
+  /**
+   * Returns the stored key/value pairs for a given set of keys.
+   *
+   * @param array $keys
+   *   A list of keys to retrieve.
+   *
+   * @return array
+   *   An associative array of items successfully returned, indexed by key.
+   *
+   * @todo What's returned for non-existing keys?
+   */
+  public function getMultiple(array $keys);
+
+  /**
+   * Returns all stored key/value pairs in the collection.
+   *
+   * @return array
+   *   An associative array containing all stored items in the collection.
+   */
+  public function getAll();
+
+  /**
+   * Saves a value for a given key.
+   *
+   * @param string $key
+   *   The key of the data to store.
+   * @param mixed $value
+   *   The data to store.
+   */
+  public function set($key, $value);
+
+  /**
+   * Saves key/value pairs.
+   *
+   * @param array $data
+   *   An associative array of key/value pairs.
+   */
+  public function setMultiple(array $data);
+
+  /**
+   * Deletes an item from the key/value store.
+   *
+   * @param string $key
+   *   The item name to delete.
+   */
+  public function delete($key);
+
+  /**
+   * Deletes multiple items from the key/value store.
+   *
+   * @param array $keys
+   *   A list of item names to delete.
+   */
+  public function deleteMultiple(array $keys);
+
+}
diff --git a/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php b/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php
new file mode 100644
index 0000000..12a4874
--- /dev/null
+++ b/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\KeyValueStore\MemoryStorage.
+ */
+
+namespace Drupal\Core\KeyValueStore;
+
+/**
+ * Defines a default key/value store implementation.
+ *
+ * For performance reasons, this implementation is not based on AbstractStorage.
+ */
+class MemoryStorage implements KeyValueStoreInterface {
+
+  /**
+   * @var array
+   */
+  protected $data = array();
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::__construct().
+   */
+  public function __construct($collection, array $options = array()) {
+    $this->collection = $collection;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getCollectionName().
+   */
+  public function getCollectionName() {
+    return $this->collection;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::get().
+   */
+  public function get($key) {
+    return array_key_exists($key, $this->data) ? $this->data[$key] : FALSE;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getMultiple().
+   */
+  public function getMultiple(array $keys) {
+    return array_intersect_key($this->data, array_flip($keys));
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getAll().
+   */
+  public function getAll() {
+    return $this->data;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::set().
+   */
+  public function set($key, $value) {
+    $this->data[$key] = $value;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::setMultiple().
+   */
+  public function setMultiple(array $data) {
+    $this->data = $data + $this->data;
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::delete().
+   */
+  public function delete($key) {
+    unset($this->data[$key]);
+  }
+
+  /**
+   * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::deleteMultiple().
+   */
+  public function deleteMultiple(array $keys) {
+    foreach ($keys as $key) {
+      unset($this->data[$key]);
+    }
+  }
+
+}
diff --git a/core/modules/color/lib/Drupal/color/Tests/ColorTest.php b/core/modules/color/lib/Drupal/color/Tests/ColorTest.php
index 8902d4c..0c8c2a2 100644
--- a/core/modules/color/lib/Drupal/color/Tests/ColorTest.php
+++ b/core/modules/color/lib/Drupal/color/Tests/ColorTest.php
@@ -108,7 +108,7 @@ class ColorTest extends WebTestBase {
     $config->set('preprocess.css', 1);
     $config->save();
     $this->drupalGet('<front>');
-    $stylesheets = variable_get('drupal_css_cache_files', array());
+    $stylesheets = state()->get('drupal_css_cache_files') ?: array();
     $stylesheet_content = '';
     foreach ($stylesheets as $key => $uri) {
       $stylesheet_content .= join("\n", file(drupal_realpath($uri)));
diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index 73c4a83..5bff879 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -1553,7 +1553,7 @@ function hook_field_available_languages_alter(&$langcodes, $context) {
 function hook_field_attach_create_bundle($entity_type, $bundle) {
   // When a new bundle is created, the menu needs to be rebuilt to add the
   // Field UI menu item tabs.
-  variable_set('menu_rebuild_needed', TRUE);
+  state()->set('menu_rebuild_needed', TRUE);
 }
 
 /**
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index bde3f35..63cf18b 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -309,7 +309,7 @@ function field_ui_element_info() {
 function field_ui_field_attach_create_bundle($entity_type, $bundle) {
   // When a new bundle is created, the menu needs to be rebuilt to add our
   // menu item tabs.
-  variable_set('menu_rebuild_needed', TRUE);
+  state()->set('menu_rebuild_needed', TRUE);
 }
 
 /**
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 775de2d..7516006 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -253,7 +253,7 @@ function image_form_system_file_system_settings_alter(&$form, &$form_state) {
  */
 function image_system_file_system_settings_submit($form, &$form_state) {
   if ($form['file_public_path']['#default_value'] !== $form_state['values']['file_public_path']) {
-    variable_set('menu_rebuild_needed', TRUE);
+    state()->set('menu_rebuild_needed', TRUE);
   }
 }
 
diff --git a/core/modules/search/search.admin.inc b/core/modules/search/search.admin.inc
index 9168510..57e5d6b 100644
--- a/core/modules/search/search.admin.inc
+++ b/core/modules/search/search.admin.inc
@@ -175,7 +175,7 @@ function search_admin_settings_submit($form, &$form_state) {
   if ($config->get('active_modules') != $new_modules) {
     $config->set('active_modules', $new_modules);
     drupal_set_message(t('The active search modules have been changed.'));
-    variable_set('menu_rebuild_needed', TRUE);
+    state()->set('menu_rebuild_needed', TRUE);
   }
   $config->save();
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
index ba7a48b..f72d384 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
@@ -346,11 +346,11 @@ class JavaScriptTest extends WebTestBase {
     ));
 
     // Store the expected key for the first item in the cache.
-    $cache = array_keys(variable_get('drupal_js_cache_files', array()));
+    $cache = array_keys(state()->get('drupal_js_cache_files') ?: array());
     $expected_key = $cache[0];
 
     // Reset variables and add a file in a different scope first.
-    variable_del('drupal_js_cache_files');
+    state()->delete('drupal_js_cache_files');
     drupal_static_reset('drupal_add_js');
     drupal_add_library('system', 'drupal');
     drupal_add_js('some/custom/javascript_file.js', array('scope' => 'footer'));
@@ -365,7 +365,7 @@ class JavaScriptTest extends WebTestBase {
     ));
 
     // Compare the expected key for the first file to the current one.
-    $cache = array_keys(variable_get('drupal_js_cache_files', array()));
+    $cache = array_keys(state()->get('drupal_js_cache_files') ?: array());
     $key = $cache[0];
     $this->assertEqual($key, $expected_key, 'JavaScript aggregation is not affected by ordering in different scopes.');
   }
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageTest.php
new file mode 100644
index 0000000..2994251
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageTest.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\KeyValueStore\DatabaseStorageTest.
+ */
+
+namespace Drupal\system\Tests\KeyValueStore;
+
+/**
+ * Tests the key-value database storage.
+ */
+class DatabaseStorageTest extends StorageTestBase {
+
+  protected $storageClass = 'Drupal\Core\KeyValueStore\DatabaseStorage';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Database storage',
+      'description' => 'Tests the key-value database storage.',
+      'group' => 'Key-value store',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+    module_load_install('system');
+    $schema = system_schema();
+    db_create_table('keyvalue', $schema['keyvalue']);
+  }
+
+  protected function tearDown() {
+    db_drop_table('keyvalue');
+    parent::tearDown();
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/MemoryStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/MemoryStorageTest.php
new file mode 100644
index 0000000..f593fd8
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/MemoryStorageTest.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\KeyValueStore\MemoryStorageTest.
+ */
+
+namespace Drupal\system\Tests\KeyValueStore;
+
+/**
+ * Tests the key-value memory storage.
+ */
+class MemoryStorageTest extends StorageTestBase {
+
+  protected $storageClass = 'Drupal\Core\KeyValueStore\MemoryStorage';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Memory storage',
+      'description' => 'Tests the key-value memory storage.',
+      'group' => 'Key-value store',
+    );
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php
new file mode 100644
index 0000000..3e321f3
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\KeyValueStore\StorageTestBase.
+ */
+
+namespace Drupal\system\Tests\KeyValueStore;
+
+use Drupal\simpletest\UnitTestBase;
+
+/**
+ * Base class for testing key-value storages.
+ */
+abstract class StorageTestBase extends UnitTestBase {
+
+  /**
+   * The fully qualified class name of the key-value storage to test.
+   *
+   * @var string
+   */
+  protected $storageClass;
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->collection1 = 'first';
+    $this->collection2 = 'second';
+
+    $this->store1 = new $this->storageClass($this->collection1);
+    $this->store2 = new $this->storageClass($this->collection2);
+  }
+
+  /**
+   * Tests CRUD operations.
+   */
+  public function testCRUD() {
+    // Verify that each store returns its own collection name.
+    $this->assertEqual($this->store1->getCollectionName(), $this->collection1);
+    $this->assertEqual($this->store2->getCollectionName(), $this->collection2);
+
+    // Verify that an item can be stored.
+    $this->store1->set('foo', 'bar');
+    $this->assertEqual('bar', $this->store1->get('foo'));
+    // Verify that the other collection is not affected.
+    $this->assertFalse($this->store2->get('foo'));
+
+    // Verify that an item can be updated.
+    $this->store1->set('foo', 'baz');
+    $this->assertEqual('baz', $this->store1->get('foo'));
+    // Verify that the other collection is still not affected.
+    $this->assertFalse($this->store2->get('foo'));
+
+    // Verify that a collection/name pair is unique.
+    $this->store2->set('foo', 'other');
+    $this->assertEqual('baz', $this->store1->get('foo'));
+    $this->assertEqual('other', $this->store2->get('foo'));
+
+    // Verify that an item can be deleted.
+    $this->store1->delete('foo');
+    $this->assertFalse($this->store1->get('foo'));
+
+    // Verify that the other collection is not affected.
+    $this->assertEqual('other', $this->store2->get('foo'));
+    $this->store2->delete('foo');
+    $this->assertFalse($this->store2->get('foo'));
+
+    // Verify that multiple items can be stored.
+    $values = array(
+      'foo' => 'bar',
+      'baz' => 'qux',
+    );
+    $this->store1->setMultiple($values);
+
+    // Verify that multiple items can be retrieved.
+    $result = $this->store1->getMultiple(array('foo', 'baz'));
+    $this->assertEqual($values, $result);
+
+    // Verify that the other collection was not affected.
+    $this->assertFalse($this->store2->get('foo'));
+    $this->assertFalse($this->store2->get('baz'));
+
+    // Verify that all items in a collection can be retrieved.
+    // Ensure that an item with the same name exists in the other collection.
+    $this->store2->set('foo', 'other');
+    $result = $this->store1->getAll();
+    // Not using assertIdentical(), since the order is not defined for getAll().
+    $this->assertEqual(count($result), count($values));
+    foreach ($result as $key => $value) {
+      $this->assertEqual($values[$key], $value);
+    }
+    // Verify that all items in the other collection are different.
+    $result = $this->store2->getAll();
+    $this->assertEqual($result, array('foo' => 'other'));
+
+    // Verify that multiple items can be deleted.
+    $this->store1->deleteMultiple(array_keys($values));
+    $this->assertFalse($this->store1->get('foo'));
+    $this->assertFalse($this->store1->get('bar'));
+    $this->assertFalse($this->store1->getMultiple(array('foo', 'baz')));
+    // Verify that the item in the other collection still exists.
+    $this->assertEqual('other', $this->store2->get('foo'));
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/RebuildTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/RebuildTest.php
index b6e0ec2..9cf1080 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/RebuildTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/RebuildTest.php
@@ -38,7 +38,7 @@ class RebuildTest extends WebTestBase {
 
     // Now we enable the rebuild variable and send a request to rebuild the menu
     // item. Now 'admin' should exist.
-    variable_set('menu_rebuild_needed', TRUE);
+    state()->set('menu_rebuild_needed', TRUE);
     // The request should trigger the rebuild.
     $this->drupalGet('<front>');
     $admin_exists = db_query('SELECT path from {menu_router} WHERE path = :path', array(':path' => 'admin'))->fetchField();
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 484b384..085773b 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -866,6 +866,34 @@ function system_schema() {
     ),
   );
 
+  $schema['keyvalue'] = array(
+    'description' => 'Generic key-value storage table.',
+    'fields' => array(
+      'name' => array(
+        'description' => 'The key.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'collection' => array(
+        'description' => 'The collection of the variable.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'value' => array(
+        'description' => 'The value.',
+        'type' => 'blob',
+        'not null' => TRUE,
+        'size' => 'big',
+        'translatable' => TRUE,
+      ),
+    ),
+    'primary key' => array('collection', 'name'),
+  );
+
   $schema['menu_router'] = array(
     'description' => 'Maps paths to various callbacks (access, page and title)',
     'fields' => array(
