From 503e46bb6b477a93a2b4186c658d323ffaa0b0d4 Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Tue, 6 Jan 2015 13:25:59 +0000
Subject: [PATCH] Issue #918538 by Berdir, slashrsm, damiankloip, sun, tobiasb:
 Decouple cache tags from cache bins

---
 core/core.services.yml                        |  17 +-
 core/lib/Drupal/Core/Cache/ApcuBackend.php    | 104 +--------
 .../Drupal/Core/Cache/ApcuBackendFactory.php  |  14 +-
 core/lib/Drupal/Core/Cache/BackendChain.php   |  15 +-
 core/lib/Drupal/Core/Cache/Cache.php          |  32 +--
 .../Core/Cache/CacheBackendInterface.php      |  47 +---
 core/lib/Drupal/Core/Cache/CacheCollector.php |   2 +-
 .../Core/Cache/CacheTagsChecksumInterface.php |  62 ++++++
 .../Core/Cache/CacheTagsInvalidator.php       |  72 ++++++
 .../Cache/CacheTagsInvalidatorInterface.php   |  29 +++
 .../Drupal/Core/Cache/ChainedFastBackend.php  |  14 +-
 .../lib/Drupal/Core/Cache/DatabaseBackend.php | 210 +++---------------
 .../Core/Cache/DatabaseBackendFactory.php     |  15 +-
 .../Core/Cache/DatabaseCacheTagsChecksum.php  | 207 +++++++++++++++++
 core/lib/Drupal/Core/Cache/MemoryBackend.php  |  15 +-
 .../Core/Cache/MemoryBackendFactory.php       |  12 +-
 core/lib/Drupal/Core/Cache/NullBackend.php    |  10 -
 core/lib/Drupal/Core/Cache/PhpBackend.php     |  42 ++--
 .../Drupal/Core/Cache/PhpBackendFactory.php   |  19 +-
 core/lib/Drupal/Core/Config/CachedStorage.php |   6 +-
 core/lib/Drupal/Core/Entity/EntityManager.php |   4 +-
 .../Entity/Sql/SqlContentEntityStorage.php    |   3 +-
 .../MenuRouterRebuildSubscriber.php           |   2 +-
 .../Drupal/Core/Extension/ThemeHandler.php    |   2 +-
 .../Installer/InstallerServiceProvider.php    |   6 +
 .../Core/Plugin/DefaultPluginManager.php      |   2 +-
 core/lib/Drupal/Core/Utility/Token.php        |   3 +-
 core/modules/book/src/BookManager.php         |   4 +-
 .../src/Tests/Storage/CachedStorageTest.php   |   3 +-
 core/modules/locale/locale.module             |   2 +-
 .../src/Tests/LocaleTranslationUiTest.php     |   4 +-
 core/modules/simpletest/src/WebTestBase.php   |   7 +-
 core/modules/system/core.api.php              |   9 +-
 .../src/Tests/Cache/ApcuBackendUnitTest.php   |   2 +-
 .../src/Tests/Cache/BackendChainUnitTest.php  |   2 +
 .../Cache/ChainedFastBackendUnitTest.php      |  10 +-
 .../Tests/Cache/DatabaseBackendTagTest.php    |  24 --
 .../Tests/Cache/DatabaseBackendUnitTest.php   |   2 +-
 .../Cache/GenericCacheBackendUnitTestBase.php |  71 +-----
 .../src/Tests/Cache/MemoryBackendUnitTest.php |   4 +-
 .../src/Tests/Cache/PhpBackendUnitTest.php    |   3 +-
 .../src/Tests/ToolbarAdminMenuTest.php        |   4 +-
 core/modules/views/src/ViewsData.php          |   2 +-
 .../views/tests/src/Unit/ViewsDataTest.php    |  21 +-
 core/modules/views/views.module               |   6 +-
 .../BackendChainImplementationUnitTest.php    |   6 +-
 .../Tests/Core/Cache/CacheCollectorTest.php   |  64 +++---
 .../Core/Cache/CacheTagsInvalidatorTest.php   |  68 ++++++
 .../Drupal/Tests/Core/Cache/CacheTest.php     |  20 --
 .../Entity/ConfigEntityBaseUnitTest.php       |  11 +-
 .../Config/Entity/ConfigEntityStorageTest.php |  35 ++-
 .../Tests/Core/Entity/EntityManagerTest.php   | 102 +++++----
 .../Tests/Core/Entity/EntityUnitTest.php      |  17 +-
 .../KeyValueEntityStorageTest.php             |  11 +-
 .../Tests/Core/Extension/ThemeHandlerTest.php |   4 +-
 .../Core/Plugin/DefaultPluginManagerTest.php  |  11 +-
 core/tests/Drupal/Tests/UnitTestCase.php      |  21 +-
 57 files changed, 790 insertions(+), 726 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Cache/CacheTagsChecksumInterface.php
 create mode 100644 core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php
 create mode 100644 core/lib/Drupal/Core/Cache/CacheTagsInvalidatorInterface.php
 create mode 100644 core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php
 create mode 100644 core/tests/Drupal/Tests/Core/Cache/CacheTagsInvalidatorTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index fca639539695..9ab7af7e022c 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -32,6 +32,18 @@ services:
     class: Drupal\Core\Cache\TimeZoneCacheContext
     tags:
       - { name: cache.context}
+  cache_tags.invalidator:
+    parent: container.trait
+    class: Drupal\Core\Cache\CacheTagsInvalidator
+    calls:
+      - [setContainer, ['@service_container']]
+    tags:
+      - { name: service_collector, call: addInvalidator, tag: cache_tags_invalidator }
+  cache_tags.invalidator.checksum:
+    class: Drupal\Core\Cache\DatabaseCacheTagsChecksum
+    arguments: ['@database']
+    tags:
+      - { name: cache_tags_invalidator}
   cache.backend.chainedfast:
     class: Drupal\Core\Cache\ChainedFastBackendFactory
     arguments: ['@settings']
@@ -39,12 +51,13 @@ services:
       - [setContainer, ['@service_container']]
   cache.backend.database:
     class: Drupal\Core\Cache\DatabaseBackendFactory
-    arguments: ['@database']
+    arguments: ['@database', '@cache_tags.invalidator.checksum']
   cache.backend.apcu:
     class: Drupal\Core\Cache\ApcuBackendFactory
-    arguments: ['@app.root']
+    arguments: ['@app.root', '@cache_tags.invalidator.checksum']
   cache.backend.php:
     class: Drupal\Core\Cache\PhpBackendFactory
+    arguments: ['@cache_tags.invalidator.checksum']
   cache.bootstrap:
     class: Drupal\Core\Cache\CacheBackendInterface
     tags:
diff --git a/core/lib/Drupal/Core/Cache/ApcuBackend.php b/core/lib/Drupal/Core/Cache/ApcuBackend.php
index 50fb0fdfe3a7..9bac79e2b70b 100644
--- a/core/lib/Drupal/Core/Cache/ApcuBackend.php
+++ b/core/lib/Drupal/Core/Cache/ApcuBackend.php
@@ -36,29 +36,11 @@ class ApcuBackend implements CacheBackendInterface {
   protected $binPrefix;
 
   /**
-   * Prefix for keys holding invalidation cache tags.
+   * The cache tags checksum provider.
    *
-   * Includes the site-specific prefix in $sitePrefix.
-   *
-   * @var string
-   */
-  protected $invalidationsTagsPrefix;
-
-  /**
-   * Prefix for keys holding invalidation cache tags.
-   *
-   * Includes the site-specific prefix in $sitePrefix.
-   *
-   * @var string
+   * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
    */
-  protected $deletionsTagsPrefix;
-
-  /**
-   * A static cache of all tags checked during the request.
-   *
-   * @var array
-   */
-  protected static $tagCache = array('deletions' => array(), 'invalidations' => array());
+  protected $checksumProvider;
 
   /**
    * Constructs a new ApcuBackend instance.
@@ -67,13 +49,14 @@ class ApcuBackend implements CacheBackendInterface {
    *   The name of the cache bin.
    * @param string $site_prefix
    *   The prefix to use for all keys in the storage that belong to this site.
+   * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
+   *   The cache tags checksum provider.
    */
-  public function __construct($bin, $site_prefix) {
+  public function __construct($bin, $site_prefix, CacheTagsChecksumInterface $checksum_provider) {
     $this->bin = $bin;
     $this->sitePrefix = $site_prefix;
+    $this->checksumProvider = $checksum_provider;
     $this->binPrefix = $this->sitePrefix . '::' . $this->bin . '::';
-    $this->invalidationsTagsPrefix = $this->sitePrefix . '::itags::';
-    $this->deletionsTagsPrefix = $this->sitePrefix . '::dtags::';
   }
 
   /**
@@ -163,18 +146,12 @@ protected function prepareItem($cache, $allow_invalid) {
     }
 
     $cache->tags = $cache->tags ? explode(' ', $cache->tags) : array();
-    $checksum = $this->checksumTags($cache->tags);
-
-    // Check if deleteTags() has been called with any of the entry's tags.
-    if ($cache->checksum_deletions != $checksum['deletions']) {
-      return FALSE;
-    }
 
     // Check expire time.
     $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME;
 
     // Check if invalidateTags() has been called with any of the entry's tags.
-    if ($cache->checksum_invalidations != $checksum['invalidations']) {
+    if (!$this->checksumProvider->isValid($cache->checksum, $cache->tags)) {
       $cache->valid = FALSE;
     }
 
@@ -196,9 +173,7 @@ public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANEN
     $cache->created = round(microtime(TRUE), 3);
     $cache->expire = $expire;
     $cache->tags = implode(' ', $tags);
-    $checksum = $this->checksumTags($tags);
-    $cache->checksum_invalidations = $checksum['invalidations'];
-    $cache->checksum_deletions = $checksum['deletions'];
+    $cache->checksum = $this->checksumProvider->getCurrentChecksum($tags);
     // APC serializes/unserializes any structure itself.
     $cache->serialized = 0;
     $cache->data = $data;
@@ -283,65 +258,4 @@ public function invalidateAll() {
     }
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function deleteTags(array $tags) {
-    foreach ($tags as $tag) {
-      apc_inc($this->deletionsTagsPrefix . $tag, 1, $success);
-      if (!$success) {
-        apc_store($this->deletionsTagsPrefix . $tag, 1);
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function invalidateTags(array $tags) {
-    foreach ($tags as $tag) {
-      apc_inc($this->invalidationsTagsPrefix . $tag, 1, $success);
-      if (!$success) {
-        apc_store($this->invalidationsTagsPrefix . $tag, 1);
-      }
-    }
-  }
-
-  /**
-   * Returns the sum total of validations for a given set of tags.
-   *
-   * @param array $tags
-   *   Associative array of tags.
-   *
-   * @return int
-   *   Sum of all invalidations.
-   */
-  protected function checksumTags(array $tags) {
-    $checksum = array('invalidations' => 0, 'deletions' => 0);
-    $query_tags = array('invalidations' => array(), 'deletions' => array());
-
-    foreach ($tags as $tag) {
-      foreach (array('deletions', 'invalidations') as $type) {
-        if (isset(static::$tagCache[$type][$tag])) {
-          $checksum[$type] += static::$tagCache[$type][$tag];
-        }
-        else {
-          $query_tags[$type][] = $this->{$type . 'TagsPrefix'} . $tag;
-        }
-      }
-    }
-
-    foreach (array('deletions', 'invalidations') as $type) {
-      if ($query_tags[$type]) {
-        $result = apc_fetch($query_tags[$type]);
-        if ($result) {
-          static::$tagCache[$type] = array_merge(static::$tagCache[$type], $result);
-          $checksum[$type] += array_sum($result);
-        }
-      }
-    }
-
-    return $checksum;
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Cache/ApcuBackendFactory.php b/core/lib/Drupal/Core/Cache/ApcuBackendFactory.php
index ffe599387973..bbc4e3d01a34 100644
--- a/core/lib/Drupal/Core/Cache/ApcuBackendFactory.php
+++ b/core/lib/Drupal/Core/Cache/ApcuBackendFactory.php
@@ -18,14 +18,24 @@ class ApcuBackendFactory implements CacheFactoryInterface {
    */
   protected $sitePrefix;
 
+  /**
+   * The cache tags checksum provider.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
+   */
+  protected $checksumProvider;
+
   /**
    * Constructs an ApcuBackendFactory object.
    *
    * @param string $root
    *   The app root.
+   * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
+   *   The cache tags checksum provider.
    */
-  public function __construct($root) {
+  public function __construct($root, CacheTagsChecksumInterface $checksum_provider) {
     $this->sitePrefix = Crypt::hashBase64($root . '/' . conf_path());
+    $this->checksumProvider = $checksum_provider;
   }
 
   /**
@@ -38,7 +48,7 @@ public function __construct($root) {
    *   The cache backend object for the specified cache bin.
    */
   public function get($bin) {
-    return new ApcuBackend($bin, $this->sitePrefix);
+    return new ApcuBackend($bin, $this->sitePrefix, $this->checksumProvider);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Cache/BackendChain.php b/core/lib/Drupal/Core/Cache/BackendChain.php
index 0cea14251d29..f7681d9d0901 100644
--- a/core/lib/Drupal/Core/Cache/BackendChain.php
+++ b/core/lib/Drupal/Core/Cache/BackendChain.php
@@ -23,7 +23,7 @@
  * @ingroup cache
  */
 
-class BackendChain implements CacheBackendInterface {
+class BackendChain implements CacheBackendInterface, CacheTagsInvalidatorInterface {
 
   /**
    * Ordered list of CacheBackendInterface instances.
@@ -158,15 +158,6 @@ public function deleteMultiple(array $cids) {
     }
   }
 
-  /**
-   * Implements Drupal\Core\Cache\CacheBackendInterface::deleteTags().
-   */
-  public function deleteTags(array $tags) {
-    foreach ($this->backends as $backend) {
-      $backend->deleteTags($tags);
-    }
-  }
-
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll().
    */
@@ -199,7 +190,9 @@ public function invalidateMultiple(array $cids) {
    */
   public function invalidateTags(array $tags) {
     foreach ($this->backends as $backend) {
-      $backend->invalidateTags($tags);
+      if ($backend instanceof CacheTagsInvalidatorInterface) {
+        $backend->invalidateTags($tags);
+      }
     }
   }
 
diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php
index ddf444ee86e1..4ce361007977 100644
--- a/core/lib/Drupal/Core/Cache/Cache.php
+++ b/core/lib/Drupal/Core/Cache/Cache.php
@@ -93,44 +93,14 @@ public static function buildTags($prefix, array $suffixes) {
     return $tags;
   }
 
-  /**
-   * Deletes items from all bins with any of the specified tags.
-   *
-   * Many sites have more than one active cache backend, and each backend may
-   * use a different strategy for storing tags against cache items, and
-   * deleting cache items associated with a given tag.
-   *
-   * When deleting a given list of tags, we iterate over each cache backend, and
-   * and call deleteTags() on each.
-   *
-   * @param string[] $tags
-   *   The list of tags to delete cache items for.
-   */
-  public static function deleteTags(array $tags) {
-    static::validateTags($tags);
-    foreach (static::getBins() as $cache_backend) {
-      $cache_backend->deleteTags($tags);
-    }
-  }
-
   /**
    * Marks cache items from all bins with any of the specified tags as invalid.
    *
-   * 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 invalidateTags() on each.
-   *
    * @param string[] $tags
    *   The list of tags to invalidate cache items for.
    */
   public static function invalidateTags(array $tags) {
-    static::validateTags($tags);
-    foreach (static::getBins() as $cache_backend) {
-      $cache_backend->invalidateTags($tags);
-    }
+    \Drupal::service('cache_tags.invalidator')->invalidateTags($tags);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
index 39c8c2624de5..faf245931975 100644
--- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
+++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
@@ -136,7 +136,6 @@ public function setMultiple(array $items);
    *
    * @see \Drupal\Core\Cache\CacheBackendInterface::invalidate()
    * @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::deleteTags()
    * @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll()
    */
   public function delete($cid);
@@ -155,39 +154,16 @@ public function delete($cid);
    *
    * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple()
    * @see \Drupal\Core\Cache\CacheBackendInterface::delete()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::deleteTags()
    * @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll()
    */
   public function deleteMultiple(array $cids);
 
-  /**
-   * Deletes items with any of the specified tags.
-   *
-   * If the cache items are being deleted because they are no longer "fresh",
-   * you may consider using invalidateTags() instead. This allows callers to
-   * retrieve the invalid items by calling get() with $allow_invalid set to TRUE.
-   * In some cases an invalid item may be acceptable rather than having to
-   * rebuild the cache.
-   *
-   * @param array $tags
-   *   Associative array of tags, in the same format that is passed to
-   *   CacheBackendInterface::set().
-   *
-   * @see \Drupal\Core\Cache\CacheBackendInterface::set()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateTags()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::delete()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll()
-   */
-  public function deleteTags(array $tags);
-
   /**
    * Deletes all cache items in a bin.
    *
    * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
    * @see \Drupal\Core\Cache\CacheBackendInterface::delete()
    * @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::deleteTags()
    */
   public function deleteAll();
 
@@ -202,7 +178,6 @@ public function deleteAll();
    *
    * @see \Drupal\Core\Cache\CacheBackendInterface::delete()
    * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateTags()
    * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
    */
   public function invalidate($cid);
@@ -213,44 +188,24 @@ public function invalidate($cid);
    * Invalid items may be returned in later calls to get(), if the $allow_invalid
    * argument is TRUE.
    *
-   * @param string $cids
+   * @param string[] $cids
    *   An array of cache IDs to invalidate.
    *
    * @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
    * @see \Drupal\Core\Cache\CacheBackendInterface::invalidate()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateTags()
    * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
    */
   public function invalidateMultiple(array $cids);
 
-  /**
-   * Marks cache items with any of the specified tags as invalid.
-   *
-   * @param array $tags
-   *   Associative array of tags, in the same format that is passed to
-   *   CacheBackendInterface::set().
-   *
-   * @see \Drupal\Core\Cache\CacheBackendInterface::set()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::deleteTags()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::invalidate()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
-   */
-  public function invalidateTags(array $tags);
-
   /**
    * Marks all cache items as invalid.
    *
    * Invalid items may be returned in later calls to get(), if the $allow_invalid
    * argument is TRUE.
    *
-   * @param string $cids
-   *   An array of cache IDs to invalidate.
-   *
    * @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll()
    * @see \Drupal\Core\Cache\CacheBackendInterface::invalidate()
    * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple()
-   * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateTags()
    */
   public function invalidateAll();
 
diff --git a/core/lib/Drupal/Core/Cache/CacheCollector.php b/core/lib/Drupal/Core/Cache/CacheCollector.php
index d80da7de4df5..a6d8ab52daf2 100644
--- a/core/lib/Drupal/Core/Cache/CacheCollector.php
+++ b/core/lib/Drupal/Core/Cache/CacheCollector.php
@@ -280,7 +280,7 @@ public function reset() {
   public function clear() {
     $this->reset();
     if ($this->tags) {
-      Cache::deleteTags($this->tags);
+      Cache::invalidateTags($this->tags);
     }
     else {
       $this->cache->delete($this->getCid());
diff --git a/core/lib/Drupal/Core/Cache/CacheTagsChecksumInterface.php b/core/lib/Drupal/Core/Cache/CacheTagsChecksumInterface.php
new file mode 100644
index 000000000000..c9e3ff08fbb7
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheTagsChecksumInterface.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\CacheTagsChecksumInterface.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Provides checksums for cache tag invalidations.
+ *
+ * Cache backends can use this to check if any cache tag invalidations happened
+ * for a stored cache item.
+ *
+ * To do so, they can inject the cache_tags.invalidator.checksum service, and
+ * when a cache item is written, store cache tags together with the current
+ * checksum, calculated by getCurrentChecksum(). When a cache item is fetched,
+ * the checksum can be validated with isValid(). The service will return FALSE
+ * if any of those cache tags were invalidated in the meantime.
+ *
+ * @ingroup cache
+ */
+interface CacheTagsChecksumInterface {
+
+  /**
+   * Returns the sum total of validations for a given set of tags.
+   *
+   * Called by a backend when storing a cache item.
+   *
+   * @param string[] $tags
+   *   Array of cache tags.
+   *
+   * @return string
+   *   Cache tag invalidations checksum.
+   */
+  public function getCurrentChecksum(array $tags);
+
+  /**
+   * Returns whether the checksum is valid for the given cache tags.
+   *
+   * Used when retrieving a cache item in a cache backend, to verify that no
+   * cache tag based invalidation happened.
+   *
+   * @param int $checksum
+   *   The checksum that was stored together with the cache item.
+   * @param string[] $tags
+   *   The cache tags that were stored together with the cache item.
+   *
+   * @return bool
+   *   FALSE if cache tag invalidations happened for the passed in tags since
+   *   the cache item was stored, TRUE otherwise.
+   */
+  public function isValid($checksum, array $tags);
+
+  /**
+   * Reset statically cached tags.
+   *
+   * This is only used by tests.
+   */
+  public function reset();
+}
diff --git a/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php b/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php
new file mode 100644
index 000000000000..636ce773f347
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\CacheTagsInvalidator.
+ */
+
+namespace Drupal\Core\Cache;
+
+use Symfony\Component\DependencyInjection\ContainerAwareTrait;
+
+/**
+ * Passes cache tag events to classes that wish to respond to them.
+ */
+class CacheTagsInvalidator implements CacheTagsInvalidatorInterface {
+
+  use ContainerAwareTrait;
+
+  /**
+   * Holds an array of cache tags invalidators.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface[]
+   */
+  protected $invalidators = array();
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateTags(array $tags) {
+    // Validate the tags.
+    Cache::validateTags($tags);
+
+    // Notify all added cache tags invalidators.
+    foreach ($this->invalidators as $invalidator) {
+      $invalidator->invalidateTags($tags);
+    }
+
+    // Additionally, notify each cache bin if it implements the service.
+    foreach ($this->getInvalidatorCacheBins() as $bin) {
+      $bin->invalidateTags($tags);
+    }
+  }
+
+  /**
+   * Adds a cache tags invalidator.
+   *
+   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $invalidator
+   *   A cache invalidator.
+   */
+  public function addInvalidator(CacheTagsInvalidatorInterface $invalidator) {
+    $this->invalidators[] = $invalidator;
+  }
+
+  /**
+   * Returns all cache bins that need to be notified about invalidations.
+   *
+   * @return \Drupal\Core\Cache\CacheTagsInvalidatorInterface[]
+   *   An array of cache backend objects that implement the invalidator
+   *   interface, keyed by their cache bin.
+   */
+  protected function getInvalidatorCacheBins() {
+    $bins = array();
+    foreach ($this->container->getParameter('cache_bins') as $service_id => $bin) {
+      $service = $this->container->get($service_id);
+      if ($service instanceof CacheTagsInvalidatorInterface) {
+        $bins[$bin] = $service;
+      }
+    }
+    return $bins;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/CacheTagsInvalidatorInterface.php b/core/lib/Drupal/Core/Cache/CacheTagsInvalidatorInterface.php
new file mode 100644
index 000000000000..bf630411ebcf
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheTagsInvalidatorInterface.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\CacheTagsInvalidatorInterface
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines required methods for classes wanting to handle cache tag changes.
+ *
+ * Services that implement this interface must add the cache_tags_invalidator
+ * tag to be notified. Cache backends may implement this interface as well, they
+ * will be notified automatically.
+ *
+ * @ingroup cache
+ */
+interface CacheTagsInvalidatorInterface {
+
+  /**
+   * Marks cache items with any of the specified tags as invalid.
+   *
+   * @param string[] $tags
+   *   The list of tags for which to invalidate cache items.
+   */
+  public function invalidateTags(array $tags);
+
+}
diff --git a/core/lib/Drupal/Core/Cache/ChainedFastBackend.php b/core/lib/Drupal/Core/Cache/ChainedFastBackend.php
index 559cff78403c..ce5ddd22c625 100644
--- a/core/lib/Drupal/Core/Cache/ChainedFastBackend.php
+++ b/core/lib/Drupal/Core/Cache/ChainedFastBackend.php
@@ -41,7 +41,7 @@
  *
  * @ingroup cache
  */
-class ChainedFastBackend implements CacheBackendInterface {
+class ChainedFastBackend implements CacheBackendInterface, CacheTagsInvalidatorInterface {
 
   /**
    * Cache key prefix for the bin-specific entry to track the last write.
@@ -210,14 +210,6 @@ public function deleteMultiple(array $cids) {
     $this->markAsOutdated();
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function deleteTags(array $tags) {
-    $this->markAsOutdated();
-    $this->consistentBackend->deleteTags($tags);
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -245,7 +237,9 @@ public function invalidateMultiple(array $cids) {
    * {@inheritdoc}
    */
   public function invalidateTags(array $tags) {
-    $this->consistentBackend->invalidateTags($tags);
+    if ($this->consistentBackend instanceof CacheTagsInvalidatorInterface) {
+      $this->consistentBackend->invalidateTags($tags);
+    }
     $this->markAsOutdated();
   }
 
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
index 813c00a1e698..cc08cc5894ae 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
@@ -34,20 +34,30 @@ class DatabaseBackend implements CacheBackendInterface {
    */
   protected $connection;
 
+  /**
+   * The cache tags checksum provider.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
+   */
+  protected $checksumProvider;
+
   /**
    * Constructs a DatabaseBackend object.
    *
    * @param \Drupal\Core\Database\Connection $connection
    *   The database connection.
+   * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
+   *   The cache tags checksum provider.
    * @param string $bin
    *   The cache bin for which the object is created.
    */
-  public function __construct(Connection $connection, $bin) {
+  public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, $bin) {
     // All cache tables should be prefixed with 'cache_'.
     $bin = 'cache_' . $bin;
 
     $this->bin = $bin;
     $this->connection = $connection;
+    $this->checksumProvider = $checksum_provider;
   }
 
   /**
@@ -76,7 +86,7 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
     // ::select() is a much smaller proportion of the request.
     $result = array();
     try {
-      $result = $this->connection->query('SELECT cid, data, created, expire, serialized, tags, checksum_invalidations, checksum_deletions FROM {' . $this->connection->escapeTable($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => array_keys($cid_mapping)));
+      $result = $this->connection->query('SELECT cid, data, created, expire, serialized, tags, checksum FROM {' . $this->connection->escapeTable($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => array_keys($cid_mapping)));
     }
     catch (\Exception $e) {
       // Nothing to do.
@@ -116,18 +126,11 @@ protected function prepareItem($cache, $allow_invalid) {
 
     $cache->tags = $cache->tags ? explode(' ', $cache->tags) : array();
 
-    $checksum = $this->checksumTags($cache->tags);
-
-    // Check if deleteTags() has been called with any of the entry's tags.
-    if ($cache->checksum_deletions != $checksum['deletions']) {
-      return FALSE;
-    }
-
     // Check expire time.
     $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME;
 
-    // Check if invalidateTags() has been called with any of the entry's tags.
-    if ($cache->checksum_invalidations != $checksum['invalidations']) {
+    // Check if invalidateTags() has been called with any of the items's tags.
+    if (!$this->checksumProvider->isValid($cache->checksum, $cache->tags)) {
       $cache->valid = FALSE;
     }
 
@@ -174,27 +177,11 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array
    * Actually set the cache.
    */
   protected function doSet($cid, $data, $expire, $tags) {
-    $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array());
-    $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array());
-    // Remove tags that were already deleted or invalidated during this request
-    // from the static caches so that another deletion or invalidation can
-    // occur.
-    foreach ($tags as $tag) {
-      if (isset($deleted_tags[$tag])) {
-        unset($deleted_tags[$tag]);
-      }
-      if (isset($invalidated_tags[$tag])) {
-        unset($invalidated_tags[$tag]);
-      }
-    }
-    $checksum = $this->checksumTags($tags);
     $fields = array(
-      'serialized' => 0,
       'created' => round(microtime(TRUE), 3),
       'expire' => $expire,
       'tags' => implode(' ', $tags),
-      'checksum_invalidations' => $checksum['invalidations'],
-      'checksum_deletions' => $checksum['deletions'],
+      'checksum' => $this->checksumProvider->getCurrentChecksum($tags),
     );
     if (!is_string($data)) {
       $fields['data'] = serialize($data);
@@ -215,9 +202,6 @@ protected function doSet($cid, $data, $expire, $tags) {
    * {@inheritdoc}
    */
   public function setMultiple(array $items) {
-    $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array());
-    $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array());
-
     // Use a transaction so that the database can write the changes in a single
     // commit.
     $transaction = $this->connection->startTransaction();
@@ -229,7 +213,7 @@ public function setMultiple(array $items) {
 
       $query = $this->connection
         ->insert($this->bin)
-        ->fields(array('cid', 'data', 'expire', 'created', 'serialized', 'tags', 'checksum_invalidations', 'checksum_deletions'));
+        ->fields(array('cid', 'data', 'expire', 'created', 'serialized', 'tags', 'checksum'));
 
       foreach ($items as $cid => $item) {
         $item += array(
@@ -242,27 +226,12 @@ public function setMultiple(array $items) {
         // Sort the cache tags so that they are stored consistently in the DB.
         sort($item['tags']);
 
-        // Remove tags that were already deleted or invalidated during this
-        // request from the static caches so that another deletion or
-        // invalidation can occur.
-        foreach ($item['tags'] as $tag) {
-          if (isset($deleted_tags[$tag])) {
-            unset($deleted_tags[$tag]);
-          }
-          if (isset($invalidated_tags[$tag])) {
-            unset($invalidated_tags[$tag]);
-          }
-        }
-
-        $checksum = $this->checksumTags($item['tags']);
-
         $fields = array(
           'cid' => $cid,
           'expire' => $item['expire'],
           'created' => round(microtime(TRUE), 3),
           'tags' => implode(' ', $item['tags']),
-          'checksum_invalidations' => $checksum['invalidations'],
-          'checksum_deletions' => $checksum['deletions'],
+          'checksum' => $this->checksumProvider->getCurrentChecksum($item['tags']),
         );
 
         if (!is_string($item['data'])) {
@@ -316,32 +285,6 @@ public function deleteMultiple(array $cids) {
     }
   }
 
-  /**
-   * Implements Drupal\Core\Cache\CacheBackendInterface::deleteTags().
-   */
-  public function deleteTags(array $tags) {
-    $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
-    $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array());
-    foreach ($tags as $tag) {
-      // Only delete tags once per request unless they are written again.
-      if (isset($deleted_tags[$tag])) {
-        continue;
-      }
-      $deleted_tags[$tag] = TRUE;
-      unset($tag_cache[$tag]);
-      try {
-        $this->connection->merge('cachetags')
-          ->insertFields(array('deletions' => 1))
-          ->expression('deletions', 'deletions + 1')
-          ->key('tag', $tag)
-          ->execute();
-      }
-      catch (\Exception $e) {
-        $this->catchException($e, 'cachetags');
-      }
-    }
-  }
-
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll().
    */
@@ -385,32 +328,6 @@ public function invalidateMultiple(array $cids) {
     }
   }
 
-  /**
-   * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
-   */
-  public function invalidateTags(array $tags) {
-    try {
-      $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
-      $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array());
-      foreach ($tags as $tag) {
-        // Only invalidate tags once per request unless they are written again.
-        if (isset($invalidated_tags[$tag])) {
-          continue;
-        }
-        $invalidated_tags[$tag] = TRUE;
-        unset($tag_cache[$tag]);
-        $this->connection->merge('cachetags')
-          ->insertFields(array('invalidations' => 1))
-          ->expression('invalidations', 'invalidations + 1')
-          ->key('tag', $tag)
-          ->execute();
-      }
-    }
-    catch (\Exception $e) {
-      $this->catchException($e, 'cachetags');
-    }
-  }
-
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
    */
@@ -442,40 +359,6 @@ public function garbageCollection() {
     }
   }
 
-  /**
-   * Returns the sum total of validations for a given set of tags.
-   *
-   * @param array $tags
-   *   Array of cache tags.
-   *
-   * @return int
-   *   Sum of all invalidations.
-   */
-  protected function checksumTags(array $tags) {
-    $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array());
-
-    $checksum = array(
-      'invalidations' => 0,
-      'deletions' => 0,
-    );
-
-    $query_tags = array_diff($tags, array_keys($tag_cache));
-    if ($query_tags) {
-      $db_tags = $this->connection->query('SELECT tag, invalidations, deletions FROM {cachetags} WHERE tag IN (:tags)', array(':tags' => $query_tags))->fetchAllAssoc('tag', \PDO::FETCH_ASSOC);
-      $tag_cache += $db_tags;
-
-      // Fill static cache with empty objects for tags not found in the database.
-      $tag_cache += array_fill_keys(array_diff($query_tags, array_keys($db_tags)), $checksum);
-    }
-
-    foreach ($tags as $tag) {
-      $checksum['invalidations'] += $tag_cache[$tag]['invalidations'];
-      $checksum['deletions'] += $tag_cache[$tag]['deletions'];
-    }
-
-    return $checksum;
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -496,11 +379,7 @@ protected function ensureBinExists() {
       $database_schema = $this->connection->schema();
       if (!$database_schema->tableExists($this->bin)) {
         $schema_definition = $this->schemaDefinition();
-        $database_schema->createTable($this->bin, $schema_definition['bin']);
-        // If the bin doesn't exist, the cache tags table may also not exist.
-        if (!$database_schema->tableExists('cachetags')) {
-          $database_schema->createTable('cachetags', $schema_definition['cachetags']);
-        }
+        $database_schema->createTable($this->bin, $schema_definition);
         return TRUE;
       }
     }
@@ -516,14 +395,16 @@ protected function ensureBinExists() {
   /**
    * Act on an exception when cache might be stale.
    *
-   * If the {cachetags} table does not yet exist, that's fine but if the table
-   * exists and yet the query failed, then the cache is stale and the
-   * exception needs to propagate.
+   * If the table does not yet exist, that's fine, but if the table exists and
+   * yet the query failed, then the cache is stale and the exception needs to
+   * propagate.
    *
    * @param $e
    *   The exception.
    * @param string|null $table_name
-   *   The table name, defaults to $this->bin. Can be cachetags.
+   *   The table name. Defaults to $this->bin.
+   *
+   * @throws \Exception
    */
   protected function catchException(\Exception $e, $table_name = NULL) {
     if ($this->connection->schema()->tableExists($table_name ?: $this->bin)) {
@@ -552,10 +433,10 @@ protected function normalizeCid($cid) {
   }
 
   /**
-   * Defines the schema for the {cache_*} bin and {cachetags} tables.
+   * Defines the schema for the {cache_*} bin tables.
    */
   public function schemaDefinition() {
-    $schema['bin'] = array(
+    $schema = array(
       'description' => 'Storage for the cache API.',
       'fields' => array(
         'cid' => array(
@@ -599,17 +480,11 @@ public function schemaDefinition() {
           'size' => 'big',
           'not null' => FALSE,
         ),
-        'checksum_invalidations' => array(
-          'description' => 'The tag invalidation sum when this entry was saved.',
-          'type' => 'int',
-          'not null' => TRUE,
-          'default' => 0,
-        ),
-        'checksum_deletions' => array(
-          'description' => 'The tag deletion sum when this entry was saved.',
-          'type' => 'int',
+        'checksum' => array(
+          'description' => 'The tag invalidation checksum when this entry was saved.',
+          'type' => 'varchar',
+          'length' => 255,
           'not null' => TRUE,
-          'default' => 0,
         ),
       ),
       'indexes' => array(
@@ -617,31 +492,6 @@ public function schemaDefinition() {
       ),
       'primary key' => array('cid'),
     );
-    $schema['cachetags'] = 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,
-        ),
-        'deletions' => array(
-          'description' => 'Number incremented when the tag is deleted.',
-          'type' => 'int',
-          'not null' => TRUE,
-          'default' => 0,
-        ),
-      ),
-      'primary key' => array('tag'),
-    );
     return $schema;
   }
 }
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
index 1d701643262a..59b0b222172f 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
@@ -18,13 +18,24 @@ class DatabaseBackendFactory implements CacheFactoryInterface {
    */
   protected $connection;
 
+  /**
+   * The cache tags checksum provider.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
+   */
+  protected $checksumProvider;
+
   /**
    * Constructs the DatabaseBackendFactory object.
    *
    * @param \Drupal\Core\Database\Connection $connection
+   *   Database connection
+   * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
+   *   The cache tags checksum provider.
    */
-  function __construct(Connection $connection) {
+  function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider) {
     $this->connection = $connection;
+    $this->checksumProvider = $checksum_provider;
   }
 
   /**
@@ -37,7 +48,7 @@ function __construct(Connection $connection) {
    *   The cache backend object for the specified cache bin.
    */
   function get($bin) {
-    return new DatabaseBackend($this->connection, $bin);
+    return new DatabaseBackend($this->connection, $this->checksumProvider, $bin);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php b/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php
new file mode 100644
index 000000000000..cbfab041c40b
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\DatabaseCacheTagsChecksum.
+ */
+
+namespace Drupal\Core\Cache;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\SchemaObjectExistsException;
+
+/**
+ * Cache tags invalidations checksum implementation that uses the database.
+ */
+class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * Contains already loaded cache invalidations from the database.
+   *
+   * @var array
+   */
+  protected $tagCache = array();
+
+  /**
+   * A list of tags that have already been invalidated in this request.
+   *
+   * Used to prevent the invalidation of the same cache tag multiple times.
+   *
+   * @var array
+   */
+  protected $invalidatedTags = array();
+
+  /**
+   * Constructs a DatabaseCacheTagsChecksum object.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection.
+   */
+  public function __construct(Connection $connection) {
+    $this->connection = $connection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateTags(array $tags) {
+    try {
+      foreach ($tags as $tag) {
+        // Only invalidate tags once per request unless they are written again.
+        if (isset($this->invalidatedTags[$tag])) {
+          continue;
+        }
+        $this->invalidatedTags[$tag] = TRUE;
+        unset($this->tagCache[$tag]);
+        $this->connection->merge('cachetags')
+          ->insertFields(array('invalidations' => 1))
+          ->expression('invalidations', 'invalidations + 1')
+          ->key('tag', $tag)
+          ->execute();
+      }
+    }
+    catch (\Exception $e) {
+      // Create the cache table, which will be empty. This fixes cases during
+      // core install where cache tags are invalidated before the table is
+      // created.
+      if (!$this->ensureTableExists()) {
+        $this->catchException($e);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCurrentChecksum(array $tags) {
+    // Remove tags that were already invalidated during this request from the
+    // static caches so that another invalidation can occur later in the same
+    // request. Without that, written cache items would not be invalidated
+    // correctly.
+    foreach ($tags as $tag) {
+      unset($this->invalidatedTags[$tag]);
+    }
+    return $this->calculateChecksum($tags);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isValid($checksum, array $tags) {
+    return $checksum == $this->calculateChecksum($tags);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateChecksum(array $tags) {
+    $checksum = 0;
+
+    $query_tags = array_diff($tags, array_keys($this->tagCache));
+    if ($query_tags) {
+      $db_tags = array();
+      try {
+        $db_tags = $this->connection->query('SELECT tag, invalidations FROM {cachetags} WHERE tag IN (:tags)', array(':tags' => $query_tags))
+          ->fetchAllKeyed();
+        $this->tagCache += $db_tags;
+      }
+      catch (\Exception $e) {
+        // If the table does not exist yet, create.
+        if (!$this->ensureTableExists()) {
+          $this->catchException($e);
+        }
+      }
+      // Fill static cache with empty objects for tags not found in the database.
+      $this->tagCache += array_fill_keys(array_diff($query_tags, array_keys($db_tags)), 0);
+    }
+
+    foreach ($tags as $tag) {
+      $checksum += $this->tagCache[$tag];
+    }
+
+    return $checksum;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function reset() {
+    $this->tagCache = array();
+    $this->invalidatedTags = array();
+  }
+
+  /**
+   * Check if the cache tags table exists and create it if not.
+   */
+  protected function ensureTableExists() {
+    try {
+      $database_schema = $this->connection->schema();
+      // Create the cache tags table if it does not exist.
+      if (!$database_schema->tableExists('cachetags')) {
+        $schema_definition = $this->schemaDefinition();
+        $database_schema->createTable('cachetags', $schema_definition);
+
+        return TRUE;
+      }
+    }
+    // If another process has already created the cachetags table, attempting to
+    // recreate it will throw an exception. In this case just catch the
+    // exception and do nothing.
+    catch (SchemaObjectExistsException $e) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Defines the schema for the {cachetags} table.
+   */
+  public function schemaDefinition() {
+    $schema = array(
+      'description' => 'Cache table for tracking cache tag invalidations.',
+      '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'),
+    );
+    return $schema;
+  }
+
+  /**
+   * Act on an exception when cache might be stale.
+   *
+   * If the {cachetags} table does not yet exist, that's fine but if the table
+   * exists and yet the query failed, then the cache is stale and the
+   * exception needs to propagate.
+   *
+   * @param \Exception $e
+   *   The exception.
+   *
+   * @throws \Exception
+   */
+  protected function catchException(\Exception $e) {
+    if ($this->connection->schema()->tableExists('cachetags')) {
+      throw $e;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php
index 23fddc5afff3..5d7cfab8b9e2 100644
--- a/core/lib/Drupal/Core/Cache/MemoryBackend.php
+++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php
@@ -17,7 +17,7 @@
  *
  * @ingroup cache
  */
-class MemoryBackend implements CacheBackendInterface {
+class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterface {
 
   /**
    * Array to store cache objects.
@@ -143,17 +143,6 @@ public function deleteMultiple(array $cids) {
     $this->cache = array_diff_key($this->cache, array_flip($cids));
   }
 
-  /**
-   * Implements Drupal\Core\Cache\CacheBackendInterface::deleteTags().
-   */
-  public function deleteTags(array $tags) {
-    foreach ($this->cache as $cid => $item) {
-      if (array_intersect($tags, $item->tags)) {
-        unset($this->cache[$cid]);
-      }
-    }
-  }
-
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll().
    */
@@ -180,7 +169,7 @@ public function invalidateMultiple(array $cids) {
   }
 
   /**
-   * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
+   * {@inheritdoc}
    */
   public function invalidateTags(array $tags) {
     foreach ($this->cache as $cid => $item) {
diff --git a/core/lib/Drupal/Core/Cache/MemoryBackendFactory.php b/core/lib/Drupal/Core/Cache/MemoryBackendFactory.php
index c9e288d51229..53e5b07f6e4c 100644
--- a/core/lib/Drupal/Core/Cache/MemoryBackendFactory.php
+++ b/core/lib/Drupal/Core/Cache/MemoryBackendFactory.php
@@ -9,11 +9,21 @@
 
 class MemoryBackendFactory implements CacheFactoryInterface {
 
+  /**
+   * Instantiated memory cache bins.
+   *
+   * @var \Drupal\Core\Cache\MemoryBackend[]
+   */
+  protected $bins = array();
+
   /**
    * {@inheritdoc}
    */
   function get($bin) {
-    return new MemoryBackend($bin);
+    if (!isset($this->bins[$bin])) {
+      $this->bins[$bin] = new MemoryBackend($bin);
+    }
+    return $this->bins[$bin];
   }
 
 }
diff --git a/core/lib/Drupal/Core/Cache/NullBackend.php b/core/lib/Drupal/Core/Cache/NullBackend.php
index d27bc13f73fb..852da7b9ed12 100644
--- a/core/lib/Drupal/Core/Cache/NullBackend.php
+++ b/core/lib/Drupal/Core/Cache/NullBackend.php
@@ -69,11 +69,6 @@ public function deleteMultiple(array $cids) {}
    */
   public function deleteAll() {}
 
-  /**
-   * Implements Drupal\Core\Cache\CacheBackendInterface::deleteTags().
-   */
-  public function deleteTags(array $tags) {}
-
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::invalidate().
    */
@@ -84,11 +79,6 @@ public function invalidate($cid) {}
    */
   public function invalidateMultiple(array $cids) {}
 
-  /**
-   * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
-   */
-  public function invalidateTags(array $tags) {}
-
   /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
    */
diff --git a/core/lib/Drupal/Core/Cache/PhpBackend.php b/core/lib/Drupal/Core/Cache/PhpBackend.php
index e188ffbfb95f..b665e8693349 100644
--- a/core/lib/Drupal/Core/Cache/PhpBackend.php
+++ b/core/lib/Drupal/Core/Cache/PhpBackend.php
@@ -35,14 +35,24 @@ class PhpBackend implements CacheBackendInterface {
    */
   protected $cache = array();
 
+  /**
+   * The cache tags checksum provider.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
+   */
+  protected $checksumProvider;
+
   /**
    * Constructs a PhpBackend object.
    *
    * @param string $bin
    *   The cache bin for which the object is created.
+   * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
+   *   The cache tags checksum provider.
    */
-  public function __construct($bin) {
+  public function __construct($bin, CacheTagsChecksumInterface $checksum_provider) {
     $this->bin = 'cache_' . $bin;
+    $this->checksumProvider = $checksum_provider;
   }
 
   /**
@@ -122,6 +132,11 @@ protected function prepareItem($cache, $allow_invalid) {
     // Check expire time.
     $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME;
 
+    // Check if invalidateTags() has been called with any of the item's tags.
+    if (!$this->checksumProvider->isValid($cache->checksum, $cache->tags)) {
+      $cache->valid = FALSE;
+    }
+
     if (!$allow_invalid && !$cache->valid) {
       return FALSE;
     }
@@ -140,6 +155,7 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array
       'created' => round(microtime(TRUE), 3),
       'expire' => $expire,
       'tags' => array_unique($tags),
+      'checksum' => $this->checksumProvider->getCurrentChecksum($tags),
     );
     $this->writeItem($this->normalizeCid($cid), $item);
   }
@@ -160,18 +176,6 @@ public function deleteMultiple(array $cids) {
     }
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function deleteTags(array $tags) {
-    foreach ($this->storage()->listAll() as $cidhash) {
-      $item = $this->getByHash($cidhash);
-      if (is_object($item) && array_intersect($tags, $item->tags)) {
-        $this->delete($item->cid);
-      }
-    }
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -208,18 +212,6 @@ public function invalidateMultiple(array $cids) {
     }
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function invalidateTags(array $tags) {
-    foreach ($this->storage()->listAll() as $cidhash) {
-      $item = $this->getByHash($cidhash);
-      if ($item && array_intersect($tags, $item->tags)) {
-        $this->invalidate($item->cid);
-      }
-    }
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Cache/PhpBackendFactory.php b/core/lib/Drupal/Core/Cache/PhpBackendFactory.php
index 0801b72f5da8..dde93d488e13 100644
--- a/core/lib/Drupal/Core/Cache/PhpBackendFactory.php
+++ b/core/lib/Drupal/Core/Cache/PhpBackendFactory.php
@@ -9,6 +9,23 @@
 
 class PhpBackendFactory implements CacheFactoryInterface {
 
+  /**
+   * The cache tags checksum provider.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsChecksumInterface
+   */
+  protected $checksumProvider;
+
+  /**
+   * Constructs a PhpBackendFactory object.
+   *
+   * @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
+   *   The cache tags checksum provider.
+   */
+  public function __construct(CacheTagsChecksumInterface $checksum_provider) {
+    $this->checksumProvider = $checksum_provider;
+  }
+
   /**
    * Gets PhpBackend for the specified cache bin.
    *
@@ -19,7 +36,7 @@ class PhpBackendFactory implements CacheFactoryInterface {
    *   The cache backend object for the specified cache bin.
    */
   function get($bin) {
-    return new PhpBackend($bin);
+    return new PhpBackend($bin, $this->checksumProvider);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Config/CachedStorage.php b/core/lib/Drupal/Core/Config/CachedStorage.php
index b657f66e186a..5023c357ed68 100644
--- a/core/lib/Drupal/Core/Config/CachedStorage.php
+++ b/core/lib/Drupal/Core/Config/CachedStorage.php
@@ -131,7 +131,7 @@ public function write($name, array $data) {
       // While not all written data is read back, setting the cache instead of
       // just deleting it avoids cache rebuild stampedes.
       $this->cache->set($this->getCacheKey($name), $data);
-      Cache::deleteTags(array($this::FIND_BY_PREFIX_CACHE_TAG));
+      Cache::invalidateTags(array($this::FIND_BY_PREFIX_CACHE_TAG));
       $this->findByPrefixCache = array();
       return TRUE;
     }
@@ -146,7 +146,7 @@ public function delete($name) {
     // rebuilding the cache before the storage is gone.
     if ($this->storage->delete($name)) {
       $this->cache->delete($this->getCacheKey($name));
-      Cache::deleteTags(array($this::FIND_BY_PREFIX_CACHE_TAG));
+      Cache::invalidateTags(array($this::FIND_BY_PREFIX_CACHE_TAG));
       $this->findByPrefixCache = array();
       return TRUE;
     }
@@ -162,7 +162,7 @@ public function rename($name, $new_name) {
     if ($this->storage->rename($name, $new_name)) {
       $this->cache->delete($this->getCacheKey($name));
       $this->cache->delete($this->getCacheKey($new_name));
-      Cache::deleteTags(array($this::FIND_BY_PREFIX_CACHE_TAG));
+      Cache::invalidateTags(array($this::FIND_BY_PREFIX_CACHE_TAG));
       $this->findByPrefixCache = array();
       return TRUE;
     }
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 58f75b74f0e0..96365cc90118 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -686,7 +686,7 @@ public function clearCachedFieldDefinitions() {
     $this->fieldMapByFieldType = array();
     $this->displayModeInfo = array();
     $this->extraFields = array();
-    Cache::deleteTags(array('entity_field_info'));
+    Cache::invalidateTags(array('entity_field_info'));
     // The typed data manager statically caches prototype objects with injected
     // definitions, clear those as well.
     $this->typedDataManager->clearCachedDefinitions();
@@ -697,7 +697,7 @@ public function clearCachedFieldDefinitions() {
    */
   public function clearCachedBundles() {
     $this->bundleInfo = array();
-    Cache::deleteTags(array('entity_bundles'));
+    Cache::invalidateTags(array('entity_bundles'));
     // Entity bundles are exposed as data types, clear that cache too.
     $this->typedDataManager->clearCachedDefinitions();
   }
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
index e531d097bdf5..af14f6edc69c 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Entity\Sql;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Database;
@@ -574,7 +575,7 @@ public function resetCache(array $ids = NULL) {
     else {
       $this->entities = array();
       if ($this->entityType->isPersistentlyCacheable()) {
-        $this->cacheBackend->deleteTags(array($this->entityTypeId . '_values'));
+        Cache::invalidateTags(array($this->entityTypeId . '_values'));
       }
     }
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php
index 41b5a8785b30..57cc61897965 100644
--- a/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/MenuRouterRebuildSubscriber.php
@@ -57,7 +57,7 @@ public function __construct(LockBackendInterface $lock, MenuLinkManagerInterface
    */
   public function onRouterRebuild(Event $event) {
     $this->menuLinksRebuild();
-    Cache::deleteTags(array('local_task'));
+    Cache::invalidateTags(array('local_task'));
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index 8beb1849a344..89ad50319ebd 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -621,7 +621,7 @@ protected function resetSystem() {
 
     // @todo It feels wrong to have the requirement to clear the local tasks
     //   cache here.
-    Cache::deleteTags(array('local_task'));
+    Cache::invalidateTags(array('local_task'));
     $this->themeRegistryRebuild();
   }
 
diff --git a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php
index 6e9356136b1d..f9d86f0d9fb3 100644
--- a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php
+++ b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php
@@ -49,6 +49,12 @@ public function register(ContainerBuilder $container) {
     $container
       ->register('router.dumper', 'Drupal\Core\Routing\NullMatcherDumper');
 
+    // Remove the cache tags invalidator tag from the cache tags storage, so
+    // that we don't call it when cache tags are invalidated very early in the
+    // installer.
+    $container->getDefinition('cache_tags.invalidator.checksum')
+      ->clearTag('cache_tags_invalidator');
+
     // Replace the route builder with an empty implementation.
     // @todo Convert installer steps into routes; add an installer.routing.yml.
     $definition = $container->getDefinition('router.builder');
diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
index 5828d33cd008..95928e4e24ed 100644
--- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
+++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
@@ -161,7 +161,7 @@ public function clearCachedDefinitions() {
     if ($this->cacheBackend) {
       if ($this->cacheTags) {
         // Use the cache tags to clear the cache.
-        Cache::deleteTags($this->cacheTags);
+        Cache::invalidateTags($this->cacheTags);
       }
       else {
         $this->cacheBackend->delete($this->cacheKey);
diff --git a/core/lib/Drupal/Core/Utility/Token.php b/core/lib/Drupal/Core/Utility/Token.php
index 0f5d30e651e5..0bf3367516c3 100644
--- a/core/lib/Drupal/Core/Utility/Token.php
+++ b/core/lib/Drupal/Core/Utility/Token.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Utility;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\LanguageInterface;
@@ -346,7 +347,7 @@ public function setInfo(array $tokens) {
    */
   public function resetInfo() {
     $this->tokenInfo = NULL;
-    $this->cache->deleteTags(array(
+    Cache::invalidateTags(array(
       static::TOKEN_INFO_CACHE_TAG => TRUE,
     ));
   }
diff --git a/core/modules/book/src/BookManager.php b/core/modules/book/src/BookManager.php
index 376cc69b604f..cc6b8bb5017a 100644
--- a/core/modules/book/src/BookManager.php
+++ b/core/modules/book/src/BookManager.php
@@ -433,7 +433,7 @@ public function deleteFromBook($nid) {
     }
     $this->updateOriginalParent($original);
     $this->books = NULL;
-    \Drupal::cache('data')->deleteTags(array('bid:' . $original['bid']));
+    Cache::invalidateTags(array('bid:' . $original['bid']));
   }
 
   /**
@@ -763,7 +763,7 @@ public function saveBookLink(array $link, $new) {
     foreach ($affected_bids as $bid) {
       $cache_tags[] = 'bid:' . $bid;
     }
-    \Drupal::cache('data')->deleteTags($cache_tags);
+    Cache::invalidateTags($cache_tags);
     return $link;
   }
 
diff --git a/core/modules/config/src/Tests/Storage/CachedStorageTest.php b/core/modules/config/src/Tests/Storage/CachedStorageTest.php
index 11c35998e2ed..66f6225de98a 100644
--- a/core/modules/config/src/Tests/Storage/CachedStorageTest.php
+++ b/core/modules/config/src/Tests/Storage/CachedStorageTest.php
@@ -90,7 +90,8 @@ public function containerBuild(ContainerBuilder $container) {
     parent::containerBuild($container);
     // Use the regular database cache backend to aid testing.
     $container->register('cache_factory', 'Drupal\Core\Cache\DatabaseBackendFactory')
-      ->addArgument(new Reference('database'));
+      ->addArgument(new Reference('database'))
+      ->addArgument(new Reference('cache_tags.invalidator.checksum'));
   }
 
 }
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 82cff291f21d..9e09a4636fea 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -986,7 +986,7 @@ function _locale_refresh_translations($langcodes, $lids = array()) {
     }
   }
   // Clear locale cache.
-  Cache::deleteTags(array('locale'));
+  Cache::invalidateTags(array('locale'));
 }
 
 /**
diff --git a/core/modules/locale/src/Tests/LocaleTranslationUiTest.php b/core/modules/locale/src/Tests/LocaleTranslationUiTest.php
index 994eb3874264..83a967d831a5 100644
--- a/core/modules/locale/src/Tests/LocaleTranslationUiTest.php
+++ b/core/modules/locale/src/Tests/LocaleTranslationUiTest.php
@@ -136,8 +136,8 @@ public function testStringTranslation() {
     $this->assertRaw($translation_to_en, 'English translation properly saved.');
 
     // Reset the tag cache on the tester side in order to pick up the call to
-    // Cache::deleteTags() on the tested side.
-    drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache');
+    // Cache::invalidateTags() on the tested side.
+    \Drupal::service('cache_tags.invalidator.checksum')->reset();
 
     $this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, 't() works for non-English.');
     // Refresh the locale() cache to get fresh data from t() below. We are in
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 2bd33fc6ddd0..af19adab2339 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -1158,12 +1158,7 @@ protected function resetAll() {
    */
   protected function refreshVariables() {
     // Clear the tag cache.
-    // @todo Replace drupal_static() usage within classes and provide a
-    //   proper interface for invoking reset() on a cache backend:
-    //   https://www.drupal.org/node/2311945.
-    drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache');
-    drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::deletedTags');
-    drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::invalidatedTags');
+    \Drupal::service('cache_tags.invalidator.checksum')->reset();
     foreach (Cache::getBins() as $backend) {
       if (is_callable(array($backend, 'reset'))) {
         $backend->reset();
diff --git a/core/modules/system/core.api.php b/core/modules/system/core.api.php
index ad73fa0b5abe..78ffb9ba95a7 100644
--- a/core/modules/system/core.api.php
+++ b/core/modules/system/core.api.php
@@ -468,10 +468,10 @@
  * - An array of values. For example, the "node" tag indicates that particular
  *   node's data is present in the cache item, so its value is an array of node
  *   IDs.
- * Data that has been tagged can be deleted or invalidated as a group: no matter
+ * Data that has been tagged can be invalidated as a group: no matter
  * the Cache ID (cid) of the cache item, no matter in which cache bin a cache
  * item lives; as long as it is tagged with a certain cache tag, it will be
- * deleted or invalidated.
+ * invalidated.
  *
  * Because of that, cache tags are a solution to the cache invalidation problem:
  * - For caching to be effective, each cache item must only be invalidated when
@@ -495,8 +495,7 @@
  * );
  * \Drupal::cache()->set($cid, $data, CacheBackendInterface::CACHE_PERMANENT, $tags);
  *
- * // Delete or invalidate all cache items with certain tags.
- * \Drupal\Core\Cache\Cache::deleteTags(array('node:1'));
+ * // Invalidate all cache items with certain tags.
  * \Drupal\Core\Cache\Cache::invalidateTags(array('user:1'));
  * @endcode
  *
@@ -513,8 +512,6 @@
  * \Drupal\Core\Entity\Entity::invalidateTagsOnSave() and
  * \Drupal\Core\Entity\Entity::invalidateTagsOnDelete().
  *
- * @todo Update cache tag deletion in https://drupal.org/node/918538
- *
  * @section configuration Configuration
  *
  * By default cached data is stored in the database. This can be configured
diff --git a/core/modules/system/src/Tests/Cache/ApcuBackendUnitTest.php b/core/modules/system/src/Tests/Cache/ApcuBackendUnitTest.php
index da8f56554ecf..20fb3a48c80d 100644
--- a/core/modules/system/src/Tests/Cache/ApcuBackendUnitTest.php
+++ b/core/modules/system/src/Tests/Cache/ApcuBackendUnitTest.php
@@ -34,7 +34,7 @@ protected function checkRequirements() {
   }
 
   protected function createCacheBackend($bin) {
-    return new ApcuBackend($bin, $this->databasePrefix);
+    return new ApcuBackend($bin, $this->databasePrefix, \Drupal::service('cache_tags.invalidator.checksum'));
   }
 
   protected function tearDown() {
diff --git a/core/modules/system/src/Tests/Cache/BackendChainUnitTest.php b/core/modules/system/src/Tests/Cache/BackendChainUnitTest.php
index c19a8aea2f02..4c90701f54af 100644
--- a/core/modules/system/src/Tests/Cache/BackendChainUnitTest.php
+++ b/core/modules/system/src/Tests/Cache/BackendChainUnitTest.php
@@ -26,6 +26,8 @@ protected function createCacheBackend($bin) {
       ->prependBackend(new MemoryBackend('bar'))
       ->appendBackend(new MemoryBackend('baz'));
 
+    \Drupal::service('cache_tags.invalidator')->addInvalidator($chain);
+
     return $chain;
   }
 
diff --git a/core/modules/system/src/Tests/Cache/ChainedFastBackendUnitTest.php b/core/modules/system/src/Tests/Cache/ChainedFastBackendUnitTest.php
index 32488a6e44e5..c34563c76636 100644
--- a/core/modules/system/src/Tests/Cache/ChainedFastBackendUnitTest.php
+++ b/core/modules/system/src/Tests/Cache/ChainedFastBackendUnitTest.php
@@ -25,9 +25,13 @@ class ChainedFastBackendUnitTest extends GenericCacheBackendUnitTestBase {
    *   A new ChainedFastBackend object.
    */
   protected function createCacheBackend($bin) {
-    $consistent_backend = new DatabaseBackend($this->container->get('database'), $bin);
-    $fast_backend = new PhpBackend($bin);
-    return new ChainedFastBackend($consistent_backend, $fast_backend, $bin);
+    $consistent_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin);
+    $fast_backend = new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'));
+    $backend = new ChainedFastBackend($consistent_backend, $fast_backend, $bin);
+    // Explicitly register the cache bin as it can not work through the
+    // cache bin list in the container.
+    \Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
+    return $backend;
   }
 
 }
diff --git a/core/modules/system/src/Tests/Cache/DatabaseBackendTagTest.php b/core/modules/system/src/Tests/Cache/DatabaseBackendTagTest.php
index e2b9d374ecb9..24cef4d4fc48 100644
--- a/core/modules/system/src/Tests/Cache/DatabaseBackendTagTest.php
+++ b/core/modules/system/src/Tests/Cache/DatabaseBackendTagTest.php
@@ -62,28 +62,4 @@ public function testTagInvalidations() {
     $this->assertEqual($invalidations_after, $invalidations_before + 1, 'Only one addition cache tag invalidation has occurred after invalidating a tag used in multiple bins.');
   }
 
-  public function testTagDeletions() {
-    // Create cache entry in multiple bins.
-    $tags = array('test_tag:1', 'test_tag:2', 'test_tag:3');
-    $bins = array('data', 'bootstrap', 'render');
-    foreach ($bins as $bin) {
-      $bin = \Drupal::cache($bin);
-      $bin->set('test', 'value', Cache::PERMANENT, $tags);
-      $this->assertTrue($bin->get('test'), 'Cache item was set in bin.');
-    }
-
-    $deletions_before = intval(db_select('cachetags')->fields('cachetags', array('deletions'))->condition('tag', 'test_tag:2')->execute()->fetchField());
-    Cache::deleteTags(array('test_tag:2'));
-
-    // Test that cache entry has been deleted in multiple bins.
-    foreach ($bins as $bin) {
-      $bin = \Drupal::cache($bin);
-      $this->assertFalse($bin->get('test'), 'Tag invalidation affected item in bin.');
-    }
-
-    // Test that only one tag deletion has occurred.
-    $deletions_after = intval(db_select('cachetags')->fields('cachetags', array('deletions'))->condition('tag', 'test_tag:2')->execute()->fetchField());
-    $this->assertEqual($deletions_after, $deletions_before + 1, 'Only one addition cache tag deletion has occurred after deleting a tag used in multiple bins.');
-  }
-
 }
diff --git a/core/modules/system/src/Tests/Cache/DatabaseBackendUnitTest.php b/core/modules/system/src/Tests/Cache/DatabaseBackendUnitTest.php
index bd2952e9d4f5..e54198ba5cce 100644
--- a/core/modules/system/src/Tests/Cache/DatabaseBackendUnitTest.php
+++ b/core/modules/system/src/Tests/Cache/DatabaseBackendUnitTest.php
@@ -30,7 +30,7 @@ class DatabaseBackendUnitTest extends GenericCacheBackendUnitTestBase {
    *   A new DatabaseBackend object.
    */
   protected function createCacheBackend($bin) {
-    return new DatabaseBackend($this->container->get('database'), $bin);
+    return new DatabaseBackend($this->container->get('database'), $this->container->get('cache_tags.invalidator.checksum'), $bin);
   }
 
 }
diff --git a/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php b/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php
index d6f31bfcc28b..463be08d141b 100644
--- a/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php
+++ b/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php
@@ -472,66 +472,6 @@ public function testDeleteMultiple() {
     $this->assertFalse($backend->deleteMultiple(array()));
   }
 
-  /**
-   * Tests Drupal\Core\Cache\CacheBackendInterface::deleteTags().
-   */
-  function testDeleteTags() {
-    $backend = $this->getCacheBackend();
-
-    // Create two cache entries with the same tag and tag value.
-    $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag:2'));
-    $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag:2'));
-    $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.');
-
-    // Delete test_tag of value 1. This should delete both entries.
-    $backend->deleteTags(array('test_tag:2'));
-    $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two cache items invalidated after deleting a cache tag.');
-    $this->assertFalse($backend->get('test_cid_invalidate1', TRUE) || $backend->get('test_cid_invalidate2', TRUE), 'Two cache items deleted after deleting a cache tag.');
-
-    // Create two cache entries with the same tag and an array tag value.
-    $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag:1'));
-    $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag:1'));
-    $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.');
-
-    // Delete test_tag of value 1. This should delete both entries.
-    $backend->deleteTags(array('test_tag:1'));
-    $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two cache items invalidated after deleted a cache tag.');
-    $this->assertFalse($backend->get('test_cid_invalidate1', TRUE) || $backend->get('test_cid_invalidate2', TRUE), 'Two cache items deleted after deleting a cache tag.');
-
-    // Create three cache entries with a mix of tags and tag values.
-    $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag:1'));
-    $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag:2'));
-    $backend->set('test_cid_invalidate3', $this->defaultValue, Cache::PERMANENT, array('test_tag_foo:3'));
-    $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2') && $backend->get('test_cid_invalidate3'), 'Three cached items were created.');
-    $backend->deleteTags(array('test_tag_foo:3'));
-    $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Cached items not matching the tag were not deleted.');
-    $this->assertFalse($backend->get('test_cid_invalidated3', TRUE), 'Cache item matching the tag was deleted.');
-
-    // Create cache entry in multiple bins. Two cache entries
-    // (test_cid_invalidate1 and test_cid_invalidate2) still exist from previous
-    // tests.
-    $tags = array('test_tag:1', 'test_tag:2', 'test_tag:3');
-    $bins = array('path', 'bootstrap', 'page');
-    foreach ($bins as $bin) {
-      $this->getCacheBackend($bin)->set('test', $this->defaultValue, Cache::PERMANENT, $tags);
-      $this->assertTrue($this->getCacheBackend($bin)->get('test'), 'Cache item was set in bin.');
-    }
-
-    // Delete tag in mulitple bins.
-    foreach ($bins as $bin) {
-      $this->getCacheBackend($bin)->deleteTags(array('test_tag:2'));
-    }
-
-    // Test that cache entry has been deleted in multple bins.
-    foreach ($bins as $bin) {
-      $this->assertFalse($this->getCacheBackend($bin)->get('test', TRUE), 'Tag deletion affected item in bin.');
-    }
-    // Test that the cache entry with a matching tag has been invalidated.
-    $this->assertFalse($this->getCacheBackend($bin)->get('test_cid_invalidate2', TRUE), 'Cache items matching tag were deleted.');
-    // Test that the cache entry with without a matching tag still exists.
-    $this->assertTrue($this->getCacheBackend($bin)->get('test_cid_invalidate1'), 'Cache items not matching tag were not invalidated.');
-  }
-
   /**
    * Test Drupal\Core\Cache\CacheBackendInterface::deleteAll().
    */
@@ -596,7 +536,7 @@ function testInvalidateTags() {
     $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.');
 
     // Invalidate test_tag of value 1. This should invalidate both entries.
-    $backend->invalidateTags(array('test_tag:2'));
+    Cache::invalidateTags(array('test_tag:2'));
     $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two cache items invalidated after invalidating a cache tag.');
     $this->assertTrue($backend->get('test_cid_invalidate1', TRUE) && $backend->get('test_cid_invalidate2', TRUE), 'Cache items not deleted after invalidating a cache tag.');
 
@@ -606,7 +546,7 @@ function testInvalidateTags() {
     $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.');
 
     // Invalidate test_tag of value 1. This should invalidate both entries.
-    $backend->invalidateTags(array('test_tag:1'));
+    Cache::invalidateTags(array('test_tag:1'));
     $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two caches removed after invalidating a cache tag.');
     $this->assertTrue($backend->get('test_cid_invalidate1', TRUE) && $backend->get('test_cid_invalidate2', TRUE), 'Cache items not deleted after invalidating a cache tag.');
 
@@ -615,7 +555,7 @@ function testInvalidateTags() {
     $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag:2'));
     $backend->set('test_cid_invalidate3', $this->defaultValue, Cache::PERMANENT, array('test_tag_foo:3'));
     $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2') && $backend->get('test_cid_invalidate3'), 'Three cached items were created.');
-    $backend->invalidateTags(array('test_tag_foo:3'));
+    Cache::invalidateTags(array('test_tag_foo:3'));
     $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Cache items not matching the tag were not invalidated.');
     $this->assertFalse($backend->get('test_cid_invalidated3'), 'Cached item matching the tag was removed.');
 
@@ -629,10 +569,7 @@ function testInvalidateTags() {
       $this->assertTrue($this->getCacheBackend($bin)->get('test'), 'Cache item was set in bin.');
     }
 
-    // Invalidate tag in mulitple bins.
-    foreach ($bins as $bin) {
-      $this->getCacheBackend($bin)->invalidateTags(array('test_tag:2'));
-    }
+    Cache::invalidateTags(array('test_tag:2'));
 
     // Test that cache entry has been invalidated in multple bins.
     foreach ($bins as $bin) {
diff --git a/core/modules/system/src/Tests/Cache/MemoryBackendUnitTest.php b/core/modules/system/src/Tests/Cache/MemoryBackendUnitTest.php
index 85edfdea5291..0dfeae710ae7 100644
--- a/core/modules/system/src/Tests/Cache/MemoryBackendUnitTest.php
+++ b/core/modules/system/src/Tests/Cache/MemoryBackendUnitTest.php
@@ -23,6 +23,8 @@ class MemoryBackendUnitTest extends GenericCacheBackendUnitTestBase {
    *   A new MemoryBackend object.
    */
   protected function createCacheBackend($bin) {
-    return new MemoryBackend($bin);
+    $backend = new MemoryBackend($bin);
+    \Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
+    return $backend;
   }
 }
diff --git a/core/modules/system/src/Tests/Cache/PhpBackendUnitTest.php b/core/modules/system/src/Tests/Cache/PhpBackendUnitTest.php
index 57df19bb2e28..1751883b1e96 100644
--- a/core/modules/system/src/Tests/Cache/PhpBackendUnitTest.php
+++ b/core/modules/system/src/Tests/Cache/PhpBackendUnitTest.php
@@ -23,7 +23,8 @@ class PhpBackendUnitTest extends GenericCacheBackendUnitTestBase {
    *   A new MemoryBackend object.
    */
   protected function createCacheBackend($bin) {
-    return new PhpBackend($bin);
+    $backend = new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'));
+    return $backend;
   }
 
 }
diff --git a/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php b/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php
index e2adde7e5b09..c866e7901952 100644
--- a/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php
+++ b/core/modules/toolbar/src/Tests/ToolbarAdminMenuTest.php
@@ -265,9 +265,9 @@ function testCacheClearByCacheTag() {
     $cache = $toolbarCache->get('toolbar_' . $admin_user_2_id . ':' . 'fr');
     $this->assertEqual($cache->tags[2], 'user:' . $admin_user_2_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "fr".');
 
-    // Log in adminUser and clear the caches for this user using a tag.
+    // Log in the admin user and clear the caches for this user using a tag.
     $this->drupalLogin($this->adminUser);
-    Cache::deleteTags(array('user:' . $admin_user_id));
+    Cache::invalidateTags(array('user:' . $admin_user_id));
 
     // Assert that no toolbar cache exists for adminUser against the
     // language "en".
diff --git a/core/modules/views/src/ViewsData.php b/core/modules/views/src/ViewsData.php
index 796e94b3035d..c90c69e55f9e 100644
--- a/core/modules/views/src/ViewsData.php
+++ b/core/modules/views/src/ViewsData.php
@@ -322,6 +322,6 @@ public function clear() {
     $this->storage = array();
     $this->allStorage = array();
     $this->fullyLoaded = FALSE;
-    Cache::deleteTags(array('views_data'));
+    Cache::invalidateTags(array('views_data'));
   }
 }
diff --git a/core/modules/views/tests/src/Unit/ViewsDataTest.php b/core/modules/views/tests/src/Unit/ViewsDataTest.php
index 0476ffde16c2..900242038579 100644
--- a/core/modules/views/tests/src/Unit/ViewsDataTest.php
+++ b/core/modules/views/tests/src/Unit/ViewsDataTest.php
@@ -25,6 +25,13 @@ class ViewsDataTest extends UnitTestCase {
    */
   protected $cacheBackend;
 
+  /**
+   * The mocked cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheTagsInvalidator;
+
   /**
    * The mocked module handler.
    *
@@ -57,8 +64,9 @@ class ViewsDataTest extends UnitTestCase {
    * {@inheritdoc}
    */
   protected function setUp() {
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
     $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
-    $this->getContainerWithCacheBins($this->cacheBackend);
+    $this->getContainerWithCacheTagsInvalidator($this->cacheTagsInvalidator);
 
     $configs = array();
     $configs['views.settings']['skip_cache'] = FALSE;
@@ -250,20 +258,21 @@ public function testFullAndTableGetCache() {
     $this->cacheBackend->expects($this->at(3))
       ->method('set')
       ->with("views_data:$random_table_name:en", array());
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
+      ->with(['views_data']);
     $this->cacheBackend->expects($this->at(4))
-      ->method('deleteAll');
-    $this->cacheBackend->expects($this->at(5))
       ->method('get')
       ->with("views_data:en")
       ->will($this->returnValue(FALSE));
-    $this->cacheBackend->expects($this->at(6))
+    $this->cacheBackend->expects($this->at(5))
       ->method('set')
       ->with("views_data:en", $expected_views_data);
-    $this->cacheBackend->expects($this->at(7))
+    $this->cacheBackend->expects($this->at(6))
       ->method('get')
       ->with("views_data:$random_table_name:en")
       ->will($this->returnValue(FALSE));
-    $this->cacheBackend->expects($this->at(8))
+    $this->cacheBackend->expects($this->at(7))
       ->method('set')
       ->with("views_data:$random_table_name:en", array());
 
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index b84d983372aa..d291717d0192 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -470,7 +470,7 @@ function views_field_config_create(FieldConfigInterface $field) {
  * Implements hook_ENTITY_TYPE_update() for 'field_config'.
  */
 function views_field_config_update(FieldConfigInterface $field) {
-  Cache::deleteTags(array('extension' => 'views'));
+  Cache::invalidateTags(array('extension' => 'views'));
   \Drupal::cache('render')->deleteAll();
 }
 
@@ -478,7 +478,7 @@ function views_field_config_update(FieldConfigInterface $field) {
  * Implements hook_ENTITY_TYPE_delete() for 'field_config'.
  */
 function views_field_config_delete(FieldConfigInterface $field) {
-  Cache::deleteTags(array('extension' => 'views'));
+  Cache::invalidateTags(array('extension' => 'views'));
   \Drupal::cache('render')->deleteAll();
 }
 
@@ -487,7 +487,7 @@ function views_field_config_delete(FieldConfigInterface $field) {
  */
 function views_invalidate_cache() {
   // Clear Views' info cache entries.
-  Cache::deleteTags(array('extension' => 'views'));
+  Cache::invalidateTags(array('extension' => 'views'));
 
   // Set the menu as needed to be rebuilt.
   \Drupal::service('router.builder_indicator')->setRebuildNeeded();
diff --git a/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php b/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php
index f977695acff4..6ab09c42808c 100644
--- a/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php
@@ -229,7 +229,7 @@ public function testDeleteTagsPropagation() {
       'Two cache items were created in all backends.');
 
     // Invalidate test_tag of value 1. This should invalidate both entries.
-    $this->chain->deleteTags(array('test_tag:2'));
+    $this->chain->invalidateTags(array('test_tag:2'));
     $this->assertSame(FALSE, $this->firstBackend->get('test_cid_clear1')
       && $this->firstBackend->get('test_cid_clear2')
       && $this->secondBackend->get('test_cid_clear1')
@@ -250,7 +250,7 @@ public function testDeleteTagsPropagation() {
       'Two cache items were created in all backends.');
 
     // Invalidate test_tag of value 1. This should invalidate both entries.
-    $this->chain->deleteTags(array('test_tag:1'));
+    $this->chain->invalidateTags(array('test_tag:1'));
     $this->assertSame(FALSE, $this->firstBackend->get('test_cid_clear1')
       && $this->firstBackend->get('test_cid_clear2')
       && $this->secondBackend->get('test_cid_clear1')
@@ -274,7 +274,7 @@ public function testDeleteTagsPropagation() {
       && $this->thirdBackend->get('test_cid_clear3'),
       'Three cached items were created in all backends.');
 
-    $this->chain->deleteTags(array('test_tag_foo:3'));
+    $this->chain->invalidateTags(array('test_tag_foo:3'));
     $this->assertNotSame(FALSE, $this->firstBackend->get('test_cid_clear1')
       && $this->firstBackend->get('test_cid_clear2')
       && $this->secondBackend->get('test_cid_clear1')
diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheCollectorTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheCollectorTest.php
index cf5b3f1a0aa0..7e91fcb18668 100644
--- a/core/tests/Drupal/Tests/Core/Cache/CacheCollectorTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/CacheCollectorTest.php
@@ -19,9 +19,16 @@ class CacheCollectorTest extends UnitTestCase {
   /**
    * The cache backend that should be used.
    *
-   * @var \PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheBackend;
+
+  /**
+   * The cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $cache;
+  protected $cacheTagsInvalidator;
 
   /**
    * The lock backend that should be used.
@@ -48,12 +55,13 @@ class CacheCollectorTest extends UnitTestCase {
    * {@inheritdoc}
    */
   protected function setUp() {
-    $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
     $this->lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface');
     $this->cid = $this->randomMachineName();
-    $this->collector = new CacheCollectorHelper($this->cid, $this->cache, $this->lock);
+    $this->collector = new CacheCollectorHelper($this->cid, $this->cacheBackend, $this->lock);
 
-    $this->getContainerWithCacheBins($this->cache);
+    $this->getContainerWithCacheTagsInvalidator($this->cacheTagsInvalidator);
   }
 
 
@@ -90,7 +98,7 @@ public function testSetAndGetNull() {
     $key = $this->randomMachineName();
     $value = NULL;
 
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('invalidate')
       ->with($this->cid);
     $this->collector->set($key, $value);
@@ -115,7 +123,7 @@ public function testGetFromCache() {
       'data' => array($key => $value),
       'created' => REQUEST_TIME,
     );
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('get')
       ->with($this->cid)
       ->will($this->returnValue($cache));
@@ -137,7 +145,7 @@ public function testDelete() {
     $this->assertTrue($this->collector->has($key));
     $this->assertEquals($value, $this->collector->get($key));
 
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('invalidate')
       ->with($this->cid);
     $this->collector->delete($key);
@@ -151,7 +159,7 @@ public function testDelete() {
   public function testUpdateCacheNoChanges() {
     $this->lock->expects($this->never())
       ->method('acquire');
-    $this->cache->expects($this->never())
+    $this->cacheBackend->expects($this->never())
       ->method('set');
 
     // Destruct the object to trigger the update data process.
@@ -175,10 +183,10 @@ public function testUpdateCache() {
       ->method('acquire')
       ->with($this->cid . ':Drupal\Core\Cache\CacheCollector')
       ->will($this->returnValue(TRUE));
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('get')
       ->with($this->cid, FALSE);
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('set')
       ->with($this->cid, array($key => $value), Cache::PERMANENT, array());
     $this->lock->expects($this->once())
@@ -205,7 +213,7 @@ public function testUpdateCacheLockFail() {
       ->method('acquire')
       ->with($this->cid . ':Drupal\Core\Cache\CacheCollector')
       ->will($this->returnValue(FALSE));
-    $this->cache->expects($this->never())
+    $this->cacheBackend->expects($this->never())
       ->method('set');
 
     // Destruct the object to trigger the update data process.
@@ -223,12 +231,12 @@ public function testUpdateCacheInvalidatedConflict() {
       'data' => array($key => $value),
       'created' => REQUEST_TIME,
     );
-    $this->cache->expects($this->at(0))
+    $this->cacheBackend->expects($this->at(0))
       ->method('get')
       ->with($this->cid)
       ->will($this->returnValue($cache));
 
-    $this->cache->expects($this->at(1))
+    $this->cacheBackend->expects($this->at(1))
       ->method('invalidate')
       ->with($this->cid);
     $this->collector->set($key, 'new value');
@@ -244,11 +252,11 @@ public function testUpdateCacheInvalidatedConflict() {
       'data' => array($key => $value),
       'created' => REQUEST_TIME + 1,
     );
-    $this->cache->expects($this->at(0))
+    $this->cacheBackend->expects($this->at(0))
       ->method('get')
       ->with($this->cid)
       ->will($this->returnValue($cache));
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('delete')
       ->with($this->cid);
     $this->lock->expects($this->once())
@@ -280,11 +288,11 @@ public function testUpdateCacheMerge() {
       'data' => array('other key' => 'other value'),
       'created' => REQUEST_TIME + 1,
     );
-    $this->cache->expects($this->at(0))
+    $this->cacheBackend->expects($this->at(0))
       ->method('get')
       ->with($this->cid)
       ->will($this->returnValue($cache));
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('set')
       ->with($this->cid, array('other key' => 'other value', $key => $value), Cache::PERMANENT, array());
     $this->lock->expects($this->once())
@@ -306,7 +314,7 @@ public function testUpdateCacheDelete() {
       'data' => array($key => $value),
       'created' => REQUEST_TIME,
     );
-    $this->cache->expects($this->at(0))
+    $this->cacheBackend->expects($this->at(0))
       ->method('get')
       ->with($this->cid)
       ->will($this->returnValue($cache));
@@ -322,10 +330,10 @@ public function testUpdateCacheDelete() {
       ->will($this->returnValue(TRUE));
     // The second argument is set to TRUE because we triggered a cache
     // invalidation.
-    $this->cache->expects($this->at(0))
+    $this->cacheBackend->expects($this->at(0))
       ->method('get')
       ->with($this->cid, TRUE);
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('set')
       ->with($this->cid, array(), Cache::PERMANENT, array());
     $this->lock->expects($this->once())
@@ -373,11 +381,11 @@ public function testUpdateCacheClear() {
     $this->assertEquals(1, $this->collector->getCacheMisses());
 
     // Clear the collected cache, should call it again.
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('delete')
       ->with($this->cid);
-    $this->cache->expects($this->never())
-      ->method('deleteTags');
+    $this->cacheTagsInvalidator->expects($this->never())
+      ->method('invalidateTags');
     $this->collector->clear();
     $this->assertEquals($value, $this->collector->get($key));
     $this->assertEquals(2, $this->collector->getCacheMisses());
@@ -390,7 +398,7 @@ public function testUpdateCacheClearTags() {
     $key = $this->randomMachineName();
     $value = $this->randomMachineName();
     $tags = array($this->randomMachineName());
-    $this->collector = new CacheCollectorHelper($this->cid, $this->cache, $this->lock, $tags);
+    $this->collector = new CacheCollectorHelper($this->cid, $this->cacheBackend, $this->lock, $tags);
 
     // Set the data and request it.
     $this->collector->setCacheMissData($key, $value);
@@ -401,10 +409,10 @@ public function testUpdateCacheClearTags() {
     $this->assertEquals(1, $this->collector->getCacheMisses());
 
     // Clear the collected cache using the tags, should call it again.
-    $this->cache->expects($this->never())
+    $this->cacheBackend->expects($this->never())
       ->method('delete');
-    $this->cache->expects($this->once())
-      ->method('deleteTags')
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
       ->with($tags);
     $this->collector->clear();
     $this->assertEquals($value, $this->collector->get($key));
diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheTagsInvalidatorTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheTagsInvalidatorTest.php
new file mode 100644
index 000000000000..1424b1e1d910
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Cache/CacheTagsInvalidatorTest.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Cache\CacheTest.
+ */
+
+namespace Drupal\Tests\Core\Cache;
+
+use Drupal\Core\Cache\CacheTagsInvalidator;
+use Drupal\Core\DependencyInjection\Container;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Cache\CacheTagsInvalidator
+ * @group Cache
+ */
+class CacheTagsInvalidatorTest extends UnitTestCase {
+
+  /**
+   * @covers ::invalidateTags
+   *
+   * @expectedException \LogicException
+   * @expectedExceptionMessage Cache tags must be strings, array given.
+   */
+  public function testInvalidateTagsWithInvalidTags() {
+    $cache_tags_invalidator = new CacheTagsInvalidator();
+    $cache_tags_invalidator->invalidateTags(['node' => [2, 3, 5, 8, 13]]);
+  }
+
+  /**
+   * @covers ::invalidateTags
+   * @covers ::addInvalidator
+   * @covers ::getBins
+   */
+  public function testInvalidateTags() {
+    $cache_tags_invalidator = new CacheTagsInvalidator();
+
+    // This does not actually implement,
+    // \Drupal\Cache\Cache\CacheBackendInterface but we can not mock from two
+    // interfaces, we would need a test class for that.
+    $invalidator_cache_bin = $this->getMock('\Drupal\Core\Cache\CacheTagsInvalidator');
+    $invalidator_cache_bin->expects($this->once())
+      ->method('invalidateTags')
+      ->with(array('node:1'));
+
+    // We do not have to define that invalidateTags() is never called as the
+    // interface does not define that method, trying to call it would result in
+    // a fatal error.
+    $non_invalidator_cache_bin = $this->getMock('\Drupal\Core\Cache\CacheBackendInterface');
+
+    $container = new Container();
+    $container->set('cache.invalidator_cache_bin', $invalidator_cache_bin);
+    $container->set('cache.non_invalidator_cache_bin', $non_invalidator_cache_bin);
+    $container->setParameter('cache_bins', array('cache.invalidator_cache_bin' => 'invalidator_cache_bin', 'cache.non_invalidator_cache_bin' => 'non_invalidator_cache_bin'));
+    $cache_tags_invalidator->setContainer($container);
+
+    $invalidator = $this->getMock('\Drupal\Core\Cache\CacheTagsInvalidator');
+    $invalidator->expects($this->once())
+      ->method('invalidateTags')
+      ->with(array('node:1'));
+
+    $cache_tags_invalidator->addInvalidator($invalidator);
+
+    $cache_tags_invalidator->invalidateTags(array('node:1'));
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
index a42557219db7..ea30f1a4826d 100644
--- a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
@@ -117,24 +117,4 @@ public function testBuildTags($prefix, array $suffixes, array $expected) {
     $this->assertEquals($expected, Cache::buildTags($prefix, $suffixes));
   }
 
-  /**
-   * @covers ::deleteTags
-   *
-   * @expectedException \LogicException
-   * @expectedExceptionMessage Cache tags must be strings, array given.
-   */
-  public function testDeleteTags() {
-    Cache::deleteTags(['node' => [2, 3, 5, 8, 13]]);
-  }
-
-  /**
-   * @covers ::invalidateTags
-   *
-   * @expectedException \LogicException
-   * @expectedExceptionMessage Cache tags must be strings, array given.
-   */
-  public function testInvalidateTags() {
-    Cache::invalidateTags(['node' => [2, 3, 5, 8, 13]]);
-  }
-
 }
diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
index 2da84fe57256..0480f52ae10a 100644
--- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
@@ -79,9 +79,9 @@ class ConfigEntityBaseUnitTest extends UnitTestCase {
   /**
    * The mocked cache backend.
    *
-   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $cacheBackend;
+  protected $cacheTagsInvalidator;
 
   /**
    * The mocked typed config manager.
@@ -121,7 +121,7 @@ protected function setUp() {
       ->with('en')
       ->will($this->returnValue(new Language(array('id' => 'en'))));
 
-    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
 
     $this->typedConfigManager = $this->getMock('Drupal\Core\Config\TypedConfigManagerInterface');
 
@@ -129,9 +129,8 @@ protected function setUp() {
     $container->set('entity.manager', $this->entityManager);
     $container->set('uuid', $this->uuid);
     $container->set('language_manager', $this->languageManager);
-    $container->set('cache.test', $this->cacheBackend);
+    $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
     $container->set('config.typed', $this->typedConfigManager);
-    $container->setParameter('cache_bins', array('cache.test' => 'test'));
     \Drupal::setContainer($container);
 
     $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Config\Entity\ConfigEntityBase', array($values, $this->entityTypeId));
@@ -361,7 +360,7 @@ public function testEnable() {
    * @depends testSetStatus
    */
   public function testDisable() {
-    $this->cacheBackend->expects($this->once())
+    $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array($this->entityTypeId . ':' . $this->id));
 
diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
index 3f95ed916dd2..13438a8bf519 100644
--- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
@@ -85,9 +85,9 @@ class ConfigEntityStorageTest extends UnitTestCase {
   /**
    * The mocked cache backend.
    *
-   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $cacheBackend;
+  protected $cacheTagsInvalidator;
 
   /**
    * The mocked typed config manager.
@@ -154,7 +154,7 @@ protected function setUp() {
       ->with('test_entity_type')
       ->will($this->returnValue($this->entityType));
 
-    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
 
     $this->typedConfigManager = $this->getMock('Drupal\Core\Config\TypedConfigManagerInterface');
     $this->typedConfigManager->expects($this->any())
@@ -163,8 +163,7 @@ protected function setUp() {
     $container = new ContainerBuilder();
     $container->set('entity.manager', $this->entityManager);
     $container->set('config.typed', $this->typedConfigManager);
-    $container->set('cache.test', $this->cacheBackend);
-    $container->setParameter('cache_bins', array('cache.test' => 'test'));
+    $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
     \Drupal::setContainer($container);
 
   }
@@ -174,7 +173,7 @@ protected function setUp() {
    * @covers ::doCreate
    */
   public function testCreateWithPredefinedUuid() {
-    $this->cacheBackend->expects($this->never())
+    $this->cacheTagsInvalidator->expects($this->never())
       ->method('invalidateTags');
 
     $this->moduleHandler->expects($this->at(0))
@@ -199,7 +198,7 @@ public function testCreateWithPredefinedUuid() {
    * @return \Drupal\Core\Entity\EntityInterface
    */
   public function testCreate() {
-    $this->cacheBackend->expects($this->never())
+    $this->cacheTagsInvalidator->expects($this->never())
       ->method('invalidateTags');
 
     $this->moduleHandler->expects($this->at(0))
@@ -241,7 +240,7 @@ public function testSaveInsert(EntityInterface $entity) {
     $config_object->expects($this->once())
       ->method('save');
 
-    $this->cacheBackend->expects($this->once())
+    $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array(
         $this->entityTypeId . '_list', // List cache tag.
@@ -300,7 +299,7 @@ public function testSaveUpdate(EntityInterface $entity) {
     $config_object->expects($this->once())
       ->method('save');
 
-    $this->cacheBackend->expects($this->once())
+    $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array(
         $this->entityTypeId . ':foo', // Own cache tag.
@@ -360,7 +359,7 @@ public function testSaveRename(ConfigEntityInterface $entity) {
     $config_object->expects($this->once())
       ->method('save');
 
-    $this->cacheBackend->expects($this->once())
+    $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array(
         $this->entityTypeId .':bar', // Own cache tag.
@@ -404,7 +403,7 @@ public function testSaveRename(ConfigEntityInterface $entity) {
    * @expectedExceptionMessage The entity does not have an ID.
    */
   public function testSaveInvalid() {
-    $this->cacheBackend->expects($this->never())
+    $this->cacheTagsInvalidator->expects($this->never())
       ->method('invalidateTags');
 
     $entity = $this->getMockEntity();
@@ -429,7 +428,7 @@ public function testSaveDuplicate() {
     $config_object->expects($this->never())
       ->method('save');
 
-    $this->cacheBackend->expects($this->never())
+    $this->cacheTagsInvalidator->expects($this->never())
       ->method('invalidateTags');
 
     $this->configFactory->expects($this->once())
@@ -460,7 +459,7 @@ public function testSaveMismatch() {
     $config_object->expects($this->never())
       ->method('save');
 
-    $this->cacheBackend->expects($this->never())
+    $this->cacheTagsInvalidator->expects($this->never())
       ->method('invalidateTags');
 
     $this->configFactory->expects($this->once())
@@ -493,7 +492,7 @@ public function testSaveNoMismatch() {
     $config_object->expects($this->once())
       ->method('save');
 
-    $this->cacheBackend->expects($this->once())
+    $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array(
         $this->entityTypeId . '_list', // List cache tag.
@@ -543,7 +542,7 @@ public function testSaveChangedUuid() {
         array('id', 'foo'),
       )));
 
-    $this->cacheBackend->expects($this->never())
+    $this->cacheTagsInvalidator->expects($this->never())
       ->method('invalidateTags');
 
     $this->configFactory->expects($this->at(1))
@@ -700,7 +699,7 @@ public function testLoadRevision() {
    * @covers ::deleteRevision
    */
   public function testDeleteRevision() {
-    $this->cacheBackend->expects($this->never())
+    $this->cacheTagsInvalidator->expects($this->never())
       ->method('invalidateTags');
 
     $this->assertSame(NULL, $this->entityStorage->deleteRevision(1));
@@ -726,7 +725,7 @@ public function testDelete() {
       $config_map[] = array("the_config_prefix.$id", $config_object);
     }
 
-    $this->cacheBackend->expects($this->once())
+    $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array(
         $this->entityTypeId . ':bar', // Own cache tag.
@@ -776,7 +775,7 @@ public function testDeleteNothing() {
     $this->configFactory->expects($this->never())
       ->method('get');
 
-    $this->cacheBackend->expects($this->never())
+    $this->cacheTagsInvalidator->expects($this->never())
       ->method('invalidateTags');
 
     $this->entityStorage->delete(array());
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
index b7e73f9b1213..8c159ecb6fbe 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
@@ -62,7 +62,14 @@ class EntityManagerTest extends UnitTestCase {
    *
    * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $cache;
+  protected $cacheBackend;
+
+  /**
+   * The cache tags invalidator.
+   *
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheTagsInvalidator;
 
   /**
    * The language manager.
@@ -118,7 +125,8 @@ protected function setUp() {
       ->with('entity_type_build')
       ->will($this->returnValue(array()));
 
-    $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
 
     $language = $this->getMock('Drupal\Core\Language\LanguageInterface');
     $language->expects($this->any())
@@ -138,7 +146,7 @@ protected function setUp() {
     $this->formBuilder = $this->getMock('Drupal\Core\Form\FormBuilderInterface');
     $this->controllerResolver = $this->getClassResolverStub();
 
-    $this->container = $this->getContainerWithCacheBins($this->cache);
+    $this->container = $this->getContainerWithCacheTagsInvalidator($this->cacheTagsInvalidator);
 
     $this->discovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface');
 
@@ -179,7 +187,7 @@ protected function setUpEntityManager($definitions = array()) {
       ->method('getDefinitions')
       ->will($this->returnValue($definitions));
 
-    $this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cache, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->installedDefinitions, $this->eventDispatcher);
+    $this->entityManager = new TestEntityManager(new \ArrayObject(), $this->moduleHandler, $this->cacheBackend, $this->languageManager, $this->translationManager, $this->getClassResolverStub(), $this->typedDataManager, $this->installedDefinitions, $this->eventDispatcher);
     $this->entityManager->setContainer($this->container);
     $this->entityManager->setDiscovery($this->discovery);
   }
@@ -192,14 +200,14 @@ protected function setUpEntityManager($definitions = array()) {
    */
   public function testClearCachedDefinitions() {
     $this->setUpEntityManager();
-    $this->cache->expects($this->at(0))
-      ->method('deleteTags')
+    $this->cacheTagsInvalidator->expects($this->at(0))
+      ->method('invalidateTags')
       ->with(array('entity_types'));
-    $this->cache->expects($this->at(1))
-      ->method('deleteTags')
+    $this->cacheTagsInvalidator->expects($this->at(1))
+      ->method('invalidateTags')
       ->with(array('entity_bundles'));
-    $this->cache->expects($this->at(2))
-      ->method('deleteTags')
+    $this->cacheTagsInvalidator->expects($this->at(2))
+      ->method('invalidateTags')
       ->with(array('entity_field_info'));
 
     $this->entityManager->clearCachedDefinitions();
@@ -523,21 +531,21 @@ public function testGetBaseFieldDefinitionsWithCaching() {
 
     $expected = array('id' => $field_definition);
 
-    $this->cache->expects($this->at(0))
+    $this->cacheBackend->expects($this->at(0))
       ->method('get')
       ->with('entity_base_field_definitions:test_entity_type:en', FALSE)
       ->will($this->returnValue(FALSE));
-    $this->cache->expects($this->at(1))
+    $this->cacheBackend->expects($this->at(1))
       ->method('get')
       ->with('entity_type', FALSE)
       ->will($this->returnValue(FALSE));
-    $this->cache->expects($this->at(2))
+    $this->cacheBackend->expects($this->at(2))
       ->method('set')
       ->with('entity_type');
-    $this->cache->expects($this->at(3))
+    $this->cacheBackend->expects($this->at(3))
       ->method('set')
       ->with('entity_base_field_definitions:test_entity_type:en');
-    $this->cache->expects($this->at(4))
+    $this->cacheBackend->expects($this->at(4))
       ->method('get')
       ->with('entity_base_field_definitions:test_entity_type:en', FALSE)
       ->will($this->returnValue((object) array('data' => $expected)));
@@ -557,27 +565,27 @@ public function testGetFieldDefinitionsWithCaching() {
 
     $expected = array('id' => $field_definition);
 
-    $this->cache->expects($this->at(0))
+    $this->cacheBackend->expects($this->at(0))
       ->method('get')
       ->with('entity_base_field_definitions:test_entity_type:en', FALSE)
       ->will($this->returnValue((object) array('data' => $expected)));
-    $this->cache->expects($this->at(1))
+    $this->cacheBackend->expects($this->at(1))
       ->method('get')
       ->with('entity_bundle_field_definitions:test_entity_type:test_bundle:en', FALSE)
       ->will($this->returnValue(FALSE));
-    $this->cache->expects($this->at(2))
+    $this->cacheBackend->expects($this->at(2))
       ->method('get')
       ->with('entity_type', FALSE)
       ->will($this->returnValue(FALSE));
-    $this->cache->expects($this->at(3))
+    $this->cacheBackend->expects($this->at(3))
       ->method('set');
-    $this->cache->expects($this->at(4))
+    $this->cacheBackend->expects($this->at(4))
       ->method('set');
-    $this->cache->expects($this->at(5))
+    $this->cacheBackend->expects($this->at(5))
       ->method('get')
       ->with('entity_base_field_definitions:test_entity_type:en', FALSE)
       ->will($this->returnValue((object) array('data' => $expected)));
-    $this->cache->expects($this->at(6))
+    $this->cacheBackend->expects($this->at(6))
       ->method('get')
       ->with('entity_bundle_field_definitions:test_entity_type:test_bundle:en', FALSE)
       ->will($this->returnValue((object) array('data' => $expected)));
@@ -616,29 +624,29 @@ public function testGetFieldStorageDefinitionsWithCaching() {
       'field_storage' => $field_storage_definition,
     );
 
-    $this->cache->expects($this->at(0))
+    $this->cacheBackend->expects($this->at(0))
       ->method('get')
       ->with('entity_base_field_definitions:test_entity_type:en', FALSE)
       ->will($this->returnValue((object) array('data' => array('id' => $expected['id']))));
-    $this->cache->expects($this->at(1))
+    $this->cacheBackend->expects($this->at(1))
       ->method('get')
       ->with('entity_field_storage_definitions:test_entity_type:en', FALSE)
       ->will($this->returnValue(FALSE));
-    $this->cache->expects($this->at(2))
+    $this->cacheBackend->expects($this->at(2))
       ->method('get')
       ->with('entity_type', FALSE)
       ->will($this->returnValue(FALSE));
-    $this->cache->expects($this->at(3))
+    $this->cacheBackend->expects($this->at(3))
       ->method('set')
       ->with('entity_type');
-    $this->cache->expects($this->at(4))
+    $this->cacheBackend->expects($this->at(4))
       ->method('set')
       ->with('entity_field_storage_definitions:test_entity_type:en');
-    $this->cache->expects($this->at(5))
+    $this->cacheBackend->expects($this->at(5))
       ->method('get')
       ->with('entity_base_field_definitions:test_entity_type:en', FALSE)
       ->will($this->returnValue((object) array('data' => array('id' => $expected['id']))));
-    $this->cache->expects($this->at(6))
+    $this->cacheBackend->expects($this->at(6))
       ->method('get')
       ->with('entity_field_storage_definitions:test_entity_type:en', FALSE)
       ->will($this->returnValue((object) array('data' => $expected)));
@@ -773,8 +781,8 @@ protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $f
    */
   public function testClearCachedFieldDefinitions() {
     $this->setUpEntityManager();
-    $this->cache->expects($this->once())
-      ->method('deleteTags')
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
       ->with(array('entity_field_info'));
     $this->typedDataManager->expects($this->once())
       ->method('clearCachedDefinitions');
@@ -789,8 +797,8 @@ public function testClearCachedFieldDefinitions() {
    */
   public function testClearCachedBundles() {
     $this->setUpEntityManager();
-    $this->cache->expects($this->once())
-      ->method('deleteTags')
+    $this->cacheTagsInvalidator->expects($this->once())
+      ->method('invalidateTags')
       ->with(array('entity_bundles'));
 
     $this->entityManager->clearCachedBundles();
@@ -861,30 +869,30 @@ public function testGetAllBundleInfo() {
       'apple' => $apple,
       'banana' => $banana,
     ));
-    $this->cache->expects($this->at(0))
+    $this->cacheBackend->expects($this->at(0))
       ->method('get')
       ->with("entity_bundle_info:en", FALSE)
       ->will($this->returnValue(FALSE));
-    $this->cache->expects($this->at(1))
+    $this->cacheBackend->expects($this->at(1))
       ->method('get')
       ->with("entity_type", FALSE)
       ->will($this->returnValue(FALSE));
-    $this->cache->expects($this->at(2))
+    $this->cacheBackend->expects($this->at(2))
       ->method('set')
       ->with("entity_type");
-    $this->cache->expects($this->at(3))
+    $this->cacheBackend->expects($this->at(3))
       ->method('set')
       ->with("entity_bundle_info:en");
-    $this->cache->expects($this->at(4))
-      ->method('deleteTags')
+    $this->cacheTagsInvalidator->expects($this->at(0))
+      ->method('invalidateTags')
       ->with(array('entity_types'));
-    $this->cache->expects($this->at(5))
-      ->method('deleteTags')
+    $this->cacheTagsInvalidator->expects($this->at(1))
+      ->method('invalidateTags')
       ->with(array('entity_bundles'));
-    $this->cache->expects($this->at(6))
-      ->method('deleteTags')
+    $this->cacheTagsInvalidator->expects($this->at(2))
+      ->method('invalidateTags')
       ->with(array('entity_field_info'));
-    $this->cache->expects($this->at(7))
+    $this->cacheBackend->expects($this->at(4))
       ->method('get')
       ->with("entity_bundle_info:en", FALSE)
       ->will($this->returnValue((object) array('data' => 'cached data')));
@@ -1017,7 +1025,7 @@ function testgetExtraFields() {
       ->method('getCurrentLanguage')
       ->will($this->returnValue($language));
 
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('get')
       ->with($cache_id);
 
@@ -1029,7 +1037,7 @@ function testgetExtraFields() {
       ->method('alter')
       ->with('entity_extra_field_info', $hook_bundle_extra_fields);
 
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('set')
       ->with($cache_id, $processed_hook_bundle_extra_fields[$entity_type_id][$bundle]);
 
@@ -1169,7 +1177,7 @@ public function testGetFieldMapFromCache() {
       )
     );
     $this->setUpEntityManager();
-    $this->cache->expects($this->once())
+    $this->cacheBackend->expects($this->once())
       ->method('get')
       ->with('entity_field_map')
       ->will($this->returnValue((object) array('data' => $expected)));
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
index a531489e733b..a9fe86044a5f 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
@@ -73,11 +73,11 @@ class EntityUnitTest extends UnitTestCase {
   protected $languageManager;
 
   /**
-   * The mocked cache backend.
+   * The mocked cache tags invalidator.
    *
-   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $cacheBackend;
+  protected $cacheTagsInvalidator;
 
   /**
    * The entity values.
@@ -116,14 +116,13 @@ protected function setUp() {
       ->with('en')
       ->will($this->returnValue(new Language(array('id' => 'en'))));
 
-    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidator');
 
     $container = new ContainerBuilder();
     $container->set('entity.manager', $this->entityManager);
     $container->set('uuid', $this->uuid);
     $container->set('language_manager', $this->languageManager);
-    $container->set('cache.test', $this->cacheBackend);
-    $container->setParameter('cache_bins', array('cache.test' => 'test'));
+    $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
     \Drupal::setContainer($container);
 
     $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\Entity', array($this->values, $this->entityTypeId));
@@ -398,12 +397,12 @@ public function testPreSave() {
    * @covers ::postSave
    */
   public function testPostSave() {
-    $this->cacheBackend->expects($this->at(0))
+    $this->cacheTagsInvalidator->expects($this->at(0))
       ->method('invalidateTags')
       ->with(array(
         $this->entityTypeId . '_list', // List cache tag.
       ));
-    $this->cacheBackend->expects($this->at(1))
+    $this->cacheTagsInvalidator->expects($this->at(1))
       ->method('invalidateTags')
       ->with(array(
         $this->entityTypeId . ':' . $this->values['id'], // Own cache tag.
@@ -455,7 +454,7 @@ public function testPreDelete() {
    * @covers ::postDelete
    */
   public function testPostDelete() {
-    $this->cacheBackend->expects($this->once())
+    $this->cacheTagsInvalidator->expects($this->once())
       ->method('invalidateTags')
       ->with(array(
         $this->entityTypeId . ':' . $this->values['id'],
diff --git a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
index 300151a7f4fe..02662290fa41 100644
--- a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
@@ -68,11 +68,11 @@ class KeyValueEntityStorageTest extends UnitTestCase {
   protected $entityManager;
 
   /**
-   * The mocked cache backend.
+   * The mocked cache tags invalidator.
    *
-   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $cacheBackend;
+  protected $cacheTagsInvalidator;
 
   /**
    * {@inheritdoc}
@@ -111,7 +111,7 @@ protected function setUpKeyValueEntityStorage($uuid_key = 'uuid') {
       ->with('test_entity_type')
       ->will($this->returnValue($this->entityType));
 
-    $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
 
     $this->keyValueStore = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
@@ -131,8 +131,7 @@ protected function setUpKeyValueEntityStorage($uuid_key = 'uuid') {
     $container = new ContainerBuilder();
     $container->set('entity.manager', $this->entityManager);
     $container->set('language_manager', $this->languageManager);
-    $container->set('cache.test', $this->cacheBackend);
-    $container->setParameter('cache_bins', array('cache.test' => 'test'));
+    $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
     \Drupal::setContainer($container);
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
index 0a58e81047c3..2c9a01562ead 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php
@@ -121,8 +121,8 @@ protected function setUp() {
     $logger = $this->getMock('Psr\Log\LoggerInterface');
     $this->themeHandler = new TestThemeHandler($this->root, $this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $logger, $this->cssCollectionOptimizer, $this->configInstaller, $this->configManager, $this->routeBuilderIndicator, $this->extensionDiscovery);
 
-    $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
-    $this->getContainerWithCacheBins($cache_backend);
+    $cache_tags_invalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
+    $this->getContainerWithCacheTagsInvalidator($cache_tags_invalidator);
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
index c2ef84143c16..9cd9375b3859 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
@@ -190,18 +190,17 @@ public function testDefaultPluginManagerWithFilledCache() {
    */
   public function testCacheClearWithTags() {
     $cid = $this->randomMachineName();
-    $cache_backend = $this->getMockBuilder('Drupal\Core\Cache\MemoryBackend')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $cache_backend
+    $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $cache_tags_invalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
+    $cache_tags_invalidator
       ->expects($this->once())
-      ->method('deleteTags')
+      ->method('invalidateTags')
       ->with(array('tag'));
     $cache_backend
       ->expects($this->never())
       ->method('deleteMultiple');
 
-    $this->getContainerWithCacheBins($cache_backend);
+    $this->getContainerWithCacheTagsInvalidator($cache_tags_invalidator);
 
     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, NULL, NULL, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
     $plugin_manager->setCacheBackend($cache_backend, $cid, array('tag'));
diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php
index 43748e8ebe05..1a0ac77d0b16 100644
--- a/core/tests/Drupal/Tests/UnitTestCase.php
+++ b/core/tests/Drupal/Tests/UnitTestCase.php
@@ -8,8 +8,7 @@
 namespace Drupal\Tests;
 
 use Drupal\Component\Utility\Random;
-use Drupal\Component\Utility\String;
-use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 
 /**
@@ -197,24 +196,20 @@ public function getStringTranslationStub() {
   }
 
   /**
-   * Sets up a container with cache bins.
+   * Sets up a container with a cache tags invalidator.
    *
-   * @param \Drupal\Core\Cache\CacheBackendInterface $backend
-   *   The cache backend to set up.
+   * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_validator
+   *   The cache tags invalidator.
    *
    * @return \Symfony\Component\DependencyInjection\ContainerInterface|\PHPUnit_Framework_MockObject_MockObject
-   *   The container with the cache bins set up.
+   *   The container with the cache tags invalidator service.
    */
-  protected function getContainerWithCacheBins(CacheBackendInterface $backend) {
+  protected function getContainerWithCacheTagsInvalidator(CacheTagsInvalidatorInterface $cache_tags_validator) {
     $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
-    $container->expects($this->any())
-      ->method('getParameter')
-      ->with('cache_bins')
-      ->will($this->returnValue(array('cache.test' => 'test')));
     $container->expects($this->any())
       ->method('get')
-      ->with('cache.test')
-      ->will($this->returnValue($backend));
+      ->with('cache_tags.invalidator')
+      ->will($this->returnValue($cache_tags_validator));
 
     \Drupal::setContainer($container);
     return $container;
-- 
GitLab