diff --git a/core/includes/cache-install.inc b/core/includes/cache-install.inc
index ec46ae0..c8981f6 100644
--- a/core/includes/cache-install.inc
+++ b/core/includes/cache-install.inc
@@ -33,7 +33,7 @@ class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterfac
   /**
    * Overrides DrupalDatabaseCache::set().
    */
-  function set($cid, $data, $expire = CACHE_PERMANENT) {
+  function set($cid, $data, $expire = CACHE_PERMANENT, $tags = array()) {
   }
 
   /**
diff --git a/core/includes/cache.inc b/core/includes/cache.inc
index e8c7477..0304cea 100644
--- a/core/includes/cache.inc
+++ b/core/includes/cache.inc
@@ -24,16 +24,14 @@
 function cache($bin = 'cache') {
   // Temporary backwards compatibiltiy layer, allow old style prefixed cache
   // bin names to be passed as arguments.
-  $bin = str_replace('cache_', '', $bin);
+  $bin = $bin ? str_replace('cache_', '', $bin) : $bin;
 
   // We do not use drupal_static() here because we do not want to change the
   // 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', 'DrupalDatabaseCache');
-    }
+  $cache_backends = variable_get('cache_classes', array('cache' => 'DrupalDatabaseCache'));
+  if ($bin && !isset($cache_objects[$bin])) {
+    $class = isset($cache_backends[$bin]) ? $cache_backends[$bin] : $cache_backends['cache'];
     $cache_objects[$bin] = new $class($bin);
   }
   return $cache_objects[$bin];
@@ -145,8 +143,14 @@ interface DrupalCacheInterface {
    *     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 $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, $tags = array());
 
   /**
    * Deletes an item from the cache.
@@ -234,7 +238,7 @@ class DrupalNullCache implements DrupalCacheInterface {
   /**
    * Implements DrupalCacheInterface::set().
    */
-  function set($cid, $data, $expire = CACHE_PERMANENT) {}
+  function set($cid, $data, $expire = CACHE_PERMANENT, $tags = array()) {}
 
   /**
    * Implements DrupalCacheInterface::delete().
@@ -280,9 +284,11 @@ class DrupalNullCache implements DrupalCacheInterface {
  * This is Drupal's default cache implementation. It uses the database to store
  * cached data. Each cache bin corresponds to a database table by the same name.
  */
-class DrupalDatabaseCache implements DrupalCacheInterface {
+class DrupalDatabaseCache implements DrupalCacheInterface, DrupalCacheTagsInterface {
   protected $bin;
 
+  protected $tag_cache = array();
+
   /**
    * Constructs a new DrupalDatabaseCache object.
    */
@@ -295,6 +301,67 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
     $this->bin = $bin;
   }
 
+  protected function validTags($checksum, $tags) {
+    return $checksum == $this->checksumTags($tags);
+  }
+
+  /**
+   * Flatten a tags array into a numeric array suitable for string storage.
+   *
+   * @param $tags
+   *   Associative array of tags to flatten.
+   *
+   * @return
+   *   Numeric array of flattened tag identifiers.
+   */
+  protected function flattenTags($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;
+  }
+
+  public function invalidateTags($tags) {
+    foreach ($this->flattenTags($tags) as $tag) {
+      unset($this->tag_cache[$tag]);
+      db_merge('cache_tags')
+        ->key(array('tag' => $tag))
+        ->fields(array('invalidations' => 1))
+        ->expression('invalidations', 'invalidations + 1')
+        ->execute();
+    }
+  }
+
+  protected function checksumTags($tags) {
+    $checksum = 0;
+    $query_tags = array();
+
+    foreach ($this->flattenTags($tags) as $tag) {
+      if (isset($this->tag_cache[$tag])) {
+        $checksum += $this->tag_cache[$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->tag_cache = array_merge($this->tag_cache, $db_tags);
+        $checksum += array_sum($db_tags);
+      }
+    }
+    return $checksum;
+  }
+
   /**
    * Implements DrupalCacheInterface::get().
    */
@@ -319,7 +386,7 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
       // 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);
@@ -367,6 +434,14 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
       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 ($cache->serialized) {
       $cache->data = unserialize($cache->data);
     }
@@ -377,11 +452,13 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
   /**
    * Implements DrupalCacheInterface::set().
    */
-  function set($cid, $data, $expire = CACHE_PERMANENT) {
+  function set($cid, $data, $expire = CACHE_PERMANENT, $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);
@@ -510,3 +587,53 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
     return empty($result);
   }
 }
+
+/**
+ * Interface for cache backends that support tags.
+ *
+ * Each cache backend that supports tags should implement this interface.
+ *
+ * @see cache_invalidate()
+ * @see DrupalDatabaseCache
+ */
+interface DrupalCacheTagsInterface {
+  /**
+   * Invalidate each tag in the $tags array.
+   *
+   * @param $tags
+   *   Associative array of tags, in the same format that is passed to
+   *   DrupalCacheInterface::set().
+   *
+   * @see DrupalCacheInterface::set()
+   */
+  function invalidateTags($tags);
+}
+
+/**
+ * Invalidate 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 that implements DrupalCacheTagsInterface.
+ *
+ * @param $tags
+ *   The list of tags to invalidate cache items for.
+ */
+function cache_invalidate(Array $tags) {
+  foreach (cache_get_backends() as $bin => $class) {
+    if (in_array('DrupalCacheTagsInterface', class_implements($class))) {
+      cache($bin)->invalidateTags($tags);
+    }
+  }
+}
+
+/**
+ * Get the list of cache backends for this site.
+ */
+function cache_get_backends() {
+  return variable_get('cache_classes', array('cache' => 'DrupalDatabaseCache'));
+}
+
diff --git a/core/modules/simpletest/tests/cache.test b/core/modules/simpletest/tests/cache.test
index bca4e25..3e3c45e 100644
--- a/core/modules/simpletest/tests/cache.test
+++ b/core/modules/simpletest/tests/cache.test
@@ -277,6 +277,51 @@ class CacheClearCase extends CacheTestCase {
   }
 
   /**
+   * 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), t('Cache item was set in %bin.', array('%bin' => $bin)));
+    }
+    cache_invalidate(array('test_tag' => array(2)));
+    foreach ($bins as $bin) {
+      $this->assertFalse($this->checkCacheExists('test', $this->default_value, $bin), t('Tag expire affected item in %bin.', array('%bin' => $bin)));
+    }
+    $this->assertFalse($this->checkCacheExists('test_cid_clear2', $this->default_value), t('Cached items matching tag were cleared.'));
+    $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value), t('Cached items not matching tag were not cleared.'));
+  }
+
+  /**
    * Test clearing using an array.
    */
   function testClearArray() {
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 20e1dc1..79a87a2 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -673,6 +673,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'),
@@ -689,6 +701,25 @@ function system_schema() {
   $schema['cache_menu']['description'] = 'Cache table for the menu system to store router information as well as generated link trees for various menu/page/user combinations.';
   $schema['cache_path'] = $schema['cache'];
   $schema['cache_path']['description'] = 'Cache table for path alias lookup.';
+  $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['date_format_type'] = array(
     'description' => 'Stores configured date format types.',
@@ -1661,6 +1692,38 @@ function system_update_8002() {
 }
 
 /**
+ * Add the {cache_tags} table.
+ */
+function system_update_8003() {
+  $table = drupal_get_schema_unprocessed('system', 'cache_tags');
+  db_create_table('cache_tags', $table);
+}
+
+/**
+ * Modify existing cache tables, adding support for cache tags.
+ */
+function system_update_8004() {
+  foreach (module_list(TRUE) as $module) {
+    foreach (drupal_get_schema_unprocessed($module) as $table => $schema) {
+      if (($table == 'cache' || strpos($table, 'cache_') === 0) && isset($schema['fields']['cid'])) {
+        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.
  */
