diff --git a/core/includes/cache.inc b/core/includes/cache.inc
index d3c3414..603a4d1 100644
--- a/core/includes/cache.inc
+++ b/core/includes/cache.inc
@@ -32,16 +32,43 @@ function cache($bin = 'cache') {
   // storage of a cache bin mid-request.
   static $cache_objects;
   if (!isset($cache_objects[$bin])) {
-    $class = variable_get('cache_class_' . $bin);
-    if (!isset($class)) {
-      $class = variable_get('cache_default_class', 'Drupal\Core\Cache\DatabaseBackend');
-    }
+    $cache_backends = cache_get_backends();
+    $class = isset($cache_backends[$bin]) ? $cache_backends[$bin] : $cache_backends['cache'];
     $cache_objects[$bin] = new $class($bin);
   }
   return $cache_objects[$bin];
 }
 
 /**
+ * Invalidates the items associated with given list of tags.
+ *
+ * Many sites have more than one active cache backend, and each backend my use
+ * a different strategy for storing tags against cache items, and invalidating
+ * cache items associated with a given tag.
+ *
+ * When invalidating a given list of tags, we iterate over each cache backend,
+ * and call invalidate on each.
+ *
+ * @param array $tags
+ *   The list of tags to invalidate cache items for.
+ */
+function cache_invalidate(array $tags) {
+  foreach (cache_get_backends() as $bin => $class) {
+    cache($bin)->invalidateTags($tags);
+  }
+}
+
+/**
+ * Returns a list of cache backends for this site.
+ *
+ * @return
+ *   An associative array with cache bins as keys, and backend classes as value.
+ */
+function cache_get_backends() {
+  return variable_get('cache_classes', array('cache' => 'Drupal\Core\Cache\DatabaseBackend'));
+}
+
+/**
  * Expires data from the block and page caches.
  */
 function cache_clear_all() {
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 07e25a0..0a1c1be 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -297,7 +297,7 @@ function install_begin_request(&$install_state) {
   // because any data put in the cache during the installer is inherently
   // suspect, due to the fact that Drupal is not fully set up yet.
   require_once DRUPAL_ROOT . '/core/includes/cache.inc';
-  $conf['cache_default_class'] = 'Drupal\Core\Cache\InstallBackend';
+  $conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\InstallBackend');
 
   // Prepare for themed output. We need to run this at the beginning of the
   // page request to avoid a different theme accidentally getting set. (We also
diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
index b4c21ce..e22c3ba 100644
--- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
+++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
@@ -99,8 +99,14 @@ interface CacheBackendInterface {
    *     general cache wipe.
    *   - A Unix timestamp: Indicates that the item should be kept at least until
    *     the given time, after which it behaves like CACHE_TEMPORARY.
+   * @param array $tags
+   *   An array of tags to be stored with the cache item. These should normally
+   *   identify objects used to build the cache item, which should trigger
+   *   cache invalidation when updated. For example if a cached item represents
+   *   a node, both the node ID and the author's user ID might be passed in as
+   *   tags. For example array('node' => array(123), 'user' => array(92)).*
    */
-  function set($cid, $data, $expire = CACHE_PERMANENT);
+  function set($cid, $data, $expire = CACHE_PERMANENT, array $tags = array());
 
   /**
    * Deletes an item from the cache.
@@ -137,6 +143,17 @@ interface CacheBackendInterface {
   function expire();
 
   /**
+   * Invalidates each tag in the $tags array.
+   *
+   * @param array $tags
+   *   Associative array of tags, in the same format that is passed to
+   *   CacheBackendInterface::set().
+   *
+   * @see CacheBackendInterface::set()
+   */
+  function invalidateTags(array $tags);
+
+  /**
    * Performs garbage collection on a cache bin.
    */
   function garbageCollection();
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
index 44a4111..abfce2a 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
@@ -23,6 +23,11 @@ class DatabaseBackend implements CacheBackendInterface {
   protected $bin;
 
   /**
+   * A static cache of all tags checked during the request.
+   */
+  protected static $tagCache;
+
+  /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::__construct().
    */
   function __construct($bin) {
@@ -58,7 +63,7 @@ class DatabaseBackend implements CacheBackendInterface {
       // is used here only due to the performance overhead we would incur
       // otherwise. When serving an uncached page, the overhead of using
       // db_select() is a much smaller proportion of the request.
-      $result = db_query('SELECT cid, data, created, expire, serialized FROM {' . db_escape_table($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids));
+      $result = db_query('SELECT cid, data, created, expire, serialized, tags, checksum FROM {' . db_escape_table($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids));
       $cache = array();
       foreach ($result as $item) {
         $item = $this->prepareItem($item);
@@ -104,6 +109,14 @@ class DatabaseBackend implements CacheBackendInterface {
       return FALSE;
     }
 
+    // The cache data is invalid if any of its tags have been cleared since.
+    if ($cache->tags) {
+      $cache->tags = explode(' ', $cache->tags);
+      if (!$this->validTags($cache->checksum, $cache->tags)) {
+        return FALSE;
+      }
+    }
+
     // If the data is permanent or not subject to a minimum cache lifetime,
     // unserialize and return the cached data.
     if ($cache->serialized) {
@@ -116,11 +129,13 @@ class DatabaseBackend implements CacheBackendInterface {
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::set().
    */
-  function set($cid, $data, $expire = CACHE_PERMANENT) {
+  function set($cid, $data, $expire = CACHE_PERMANENT, array $tags = array()) {
     $fields = array(
       'serialized' => 0,
       'created' => REQUEST_TIME,
       'expire' => $expire,
+      'tags' => implode(' ', $this->flattenTags($tags)),
+      'checksum' => $this->checksumTags($tags),
     );
     if (!is_string($data)) {
       $fields['data'] = serialize($data);
@@ -154,7 +169,7 @@ class DatabaseBackend implements CacheBackendInterface {
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::deleteMultiple().
    */
-  function deleteMultiple(Array $cids) {
+  function deleteMultiple(array $cids) {
     // Delete in chunks when a large array is passed.
     do {
       db_delete($this->bin)
@@ -254,6 +269,94 @@ class DatabaseBackend implements CacheBackendInterface {
   }
 
   /**
+   * Compares two checksums of tags. Used to determine whether to serve a cached
+   * item or treat it as invalidated.
+   *
+   * @param integer @checksum
+   *   The initial checksum to compare against.
+   * @param array @tags
+   *   An array of tags to calculate a checksum for.
+   *
+   * @return boolean
+   *   TRUE if the checksums match, FALSE otherwise.
+   */
+  protected function validTags($checksum, array $tags) {
+    return $checksum == $this->checksumTags($tags);
+  }
+
+  /**
+   * Flattens a tags array into a numeric array suitable for string storage.
+   *
+   * @param array $tags
+   *   Associative array of tags to flatten.
+   *
+   * @return
+   *   Numeric array of flattened tag identifiers.
+   */
+  protected function flattenTags(array $tags) {
+    if (isset($tags[0])) {
+      return $tags;
+    }
+
+    $flat_tags = array();
+    foreach ($tags as $namespace => $values) {
+      if (is_array($values)) {
+        foreach ($values as $value) {
+          $flat_tags[] = "$namespace:$value";
+        }
+      }
+      else {
+        $flat_tags[] = "$namespace:$values";
+      }
+    }
+    return $flat_tags;
+  }
+
+  /**
+   * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
+   */
+  public function invalidateTags(array $tags) {
+    foreach ($this->flattenTags($tags) as $tag) {
+      unset($this::tagCache[$tag]);
+      db_merge('cache_tags')
+        ->key(array('tag' => $tag))
+        ->fields(array('invalidations' => 1))
+        ->expression('invalidations', 'invalidations + 1')
+        ->execute();
+    }
+  }
+
+  /**
+   * Returns the sum total of validations for a given set of tags.
+   *
+   * @param array $tags
+   *   Associative array of tags.
+   *
+   * @return integer
+   *   Sum of all invalidations.
+   */
+  protected function checksumTags($tags) {
+    $checksum = 0;
+    $query_tags = array();
+
+    foreach ($this->flattenTags($tags) as $tag) {
+      if (isset($this::tagCache[$tag])) {
+        $checksum += $this::tagCache[$tag];
+      }
+      else {
+        $query_tags[] = $tag;
+      }
+   }
+    if ($query_tags) {
+      if ($db_tags = db_query('SELECT tag, invalidations FROM {cache_tags} WHERE tag IN (:tags)', array(':tags' => $query_tags))->fetchAllKeyed()) {
+        $this::tagCache = array_merge($this->tag_cache, $db_tags);
+        $checksum += array_sum($db_tags);
+      }
+    }
+    return $checksum;
+  }
+
+  /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::isEmpty().
    */
   function isEmpty() {
diff --git a/core/lib/Drupal/Core/Cache/InstallBackend.php b/core/lib/Drupal/Core/Cache/InstallBackend.php
index 53f9b23..f332fcc 100644
--- a/core/lib/Drupal/Core/Cache/InstallBackend.php
+++ b/core/lib/Drupal/Core/Cache/InstallBackend.php
@@ -50,7 +50,7 @@ class InstallBackend extends DatabaseBackend {
   /**
    * Overrides Drupal\Core\Cache\CacheBackendInterface::set().
    */
-  function set($cid, $data, $expire = CACHE_PERMANENT) {}
+  function set($cid, $data, $expire = CACHE_PERMANENT, array $tags = array()) {}
 
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::delete().
@@ -88,6 +88,15 @@ class InstallBackend extends DatabaseBackend {
     catch (Exception $e) {}
   }
 
+  function invalidateTags(array $tags) {
+    try {
+      if (class_exists('Database')) {
+        parent::invalidateTags($tags);
+      }
+    }
+    catch (Exception $e) {}
+  }
+
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::flush().
    */
diff --git a/core/modules/simpletest/tests/cache.test b/core/modules/simpletest/tests/cache.test
index 7040aef..e5d4435 100644
--- a/core/modules/simpletest/tests/cache.test
+++ b/core/modules/simpletest/tests/cache.test
@@ -370,6 +370,51 @@ class CacheClearCase extends CacheTestCase {
     $cached = cache('page')->get($data);
     $this->assertFalse($cached, 'Cached item was invalidated');
   }
+
+  /**
+   * Test clearing using cache tags.
+   */
+  function testClearTags() {
+    $cache = cache($this->default_bin);
+    $cache->set('test_cid_clear1', $this->default_value, CACHE_PERMANENT, array('test_tag' => array(1)));
+    $cache->set('test_cid_clear2', $this->default_value, CACHE_PERMANENT, array('test_tag' => array(1)));
+    $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
+                      && $this->checkCacheExists('test_cid_clear2', $this->default_value),
+                      t('Two cache items were created.'));
+    cache_invalidate(array('test_tag' => array(1)));
+    $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value)
+                      || $this->checkCacheExists('test_cid_clear2', $this->default_value),
+                      t('Two caches removed after clearing a cache tag.'));
+
+    $cache->set('test_cid_clear1', $this->default_value, CACHE_PERMANENT, array('test_tag' => array(1)));
+    $cache->set('test_cid_clear2', $this->default_value, CACHE_PERMANENT, array('test_tag' => array(2)));
+    $cache->set('test_cid_clear3', $this->default_value, CACHE_PERMANENT, array('test_tag_foo' => array(3)));
+    $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
+                      && $this->checkCacheExists('test_cid_clear2', $this->default_value)
+                      && $this->checkCacheExists('test_cid_clear3', $this->default_value),
+                      t('Two cached items were created.'));
+    cache_invalidate(array('test_tag_foo' => array(3)));
+    $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
+                      && $this->checkCacheExists('test_cid_clear2', $this->default_value),
+                      t('Cached items not matching the tag were not cleared.'));
+
+    $this->assertFalse($this->checkCacheExists('test_cid_clear3', $this->default_value),
+                      t('Cached item matching the tag was removed.'));
+
+    // For our next trick, we will attempt to clear data in multiple bins.
+    $tags = array('test_tag' => array(1, 2, 3));
+    $bins = array('cache', 'cache_page', 'cache_bootstrap');
+    foreach ($bins as $bin) {
+      cache($bin)->set('test', $this->default_value, CACHE_PERMANENT, $tags);
+      $this->assertTrue($this->checkCacheExists('test', $this->default_value, $bin), 'Cache item was set in bin.');
+    }
+    cache_invalidate(array('test_tag' => array(2)));
+    foreach ($bins as $bin) {
+      $this->assertFalse($this->checkCacheExists('test', $this->default_value, $bin), 'Tag expire affected item in bin.');
+    }
+    $this->assertFalse($this->checkCacheExists('test_cid_clear2', $this->default_value), 'Cached items matching tag were cleared.');
+    $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value), 'Cached items not matching tag were not cleared.');
+  }
 }
 
 /**
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 905d11d..e3517d7 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -645,6 +645,26 @@ function system_schema() {
     'primary key' => array('iid'),
   );
 
+  $schema['cache_tags'] = array(
+    'description' => 'Cache table for tracking cache tags related to the cache bin.',
+    'fields' => array(
+      'tag' => array(
+        'description' => 'Namespace-prefixed tag string.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'invalidations' => array(
+        'description' => 'Number incremented when the tag is invalidated.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('tag'),
+  );
+
   $schema['cache'] = array(
     'description' => 'Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.',
     'fields' => array(
@@ -680,6 +700,18 @@ function system_schema() {
         'not null' => TRUE,
         'default' => 0,
       ),
+      'tags' => array(
+        'description' => 'Space-separated list of cache tags for this entry.',
+        'type' => 'text',
+        'size' => 'big',
+        'not null' => FALSE,
+      ),
+      'checksum' => array(
+        'description' => 'The tag invalidation sum when this entry was saved.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
     ),
     'indexes' => array(
       'expire' => array('expire'),
@@ -1761,6 +1793,59 @@ function system_update_8005() {
   db_drop_field('session', 'cache');
 }
 
+ /**
+ * Adds the {cache_tags} table.
+ */
+function system_update_8006() {
+  $table = array(
+    'description' => 'Cache table for tracking cache tags related to the cache bin.',
+    'fields' => array(
+      'tag' => array(
+        'description' => 'Namespace-prefixed tag string.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'invalidations' => array(
+        'description' => 'Number incremented when the tag is invalidated.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('tag'),
+  );
+  db_create_table('cache_tags', $table);
+}
+
+/**
+ * Modifies existing cache tables, adding support for cache tags.
+ */
+function system_update_8007() {
+  // Find all potential cache tables.
+  $tables = db_find_tables(Database::getConnection()->prefixTables('{cache}') . '%');
+
+  foreach ($tables as $table) {
+    // Assume we have a valid cache table if there is both 'cid' and 'data'
+    // columns.
+    if (db_field_exists($table, 'cid') && db_field_exists($table, 'data')) {
+      db_add_field($table, 'tags', array(
+        'description' => 'Space-separated list of cache tags for this entry.',
+        'type' => 'text',
+        'size' => 'big',
+        'not null' => FALSE,
+      ));
+      db_add_field($table, 'checksum', array(
+        'description' => 'The tag invalidation sum when this entry was saved.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ));
+    }
+  }
+}
+
 /**
  * @} End of "defgroup updates-7.x-to-8.x"
  * The next series of updates should start at 9000.
