You are here

class ChainedFastRawBackend in Supercache 8

Same name and namespace in other branches
  1. 2.0.x src/Cache/ChainedFastRawBackend.php \Drupal\supercache\Cache\ChainedFastRawBackend

Defines a backend with a fast and a consistent backend chain.

In order to mitigate a network roundtrip for each cache get operation, this cache allows a fast backend to be put in front of a slow(er) backend. Typically the fast backend will be something like APCu, and be bound to a single web node, and will not require a network round trip to fetch a cache item. The fast backend will also typically be inconsistent (will only see changes from one web node). The slower backend will be something like Mysql, Memcached or Redis, and will be used by all web nodes, thus making it consistent, but also require a network round trip for each cache get.

In addition to being useful for sites running on multiple web nodes, this backend can also be useful for sites running on a single web node where the fast backend (For example, APCu) isn't shareable between the web and CLI processes. Single-node configurations that don't have that limitation can just use the fast cache backend directly.

We always use the fast backend when reading (get()) entries from cache, but check whether they were created before the last write (set()) to this (chained) cache backend. Those cache entries that were created before the last write are discarded, but we use their cache IDs to then read them from the consistent (slower) cache backend instead; at the same time we update the fast cache backend so that the next read will hit the faster backend again. Hence we can guarantee that the cache entries we return are all up-to-date, and maximally exploit the faster cache backend. This cache backend uses and maintains a "last write timestamp" to determine which cache entries should be discarded.

Because this backend will mark all the cache entries in a bin as out-dated for each write to a bin, it is best suited to bins with fewer changes.

Note that this is designed specifically for combining a fast inconsistent cache backend with a slower consistent cache back-end. To still function correctly, it needs to do a consistency check (see the "last write timestamp" logic). This contrasts with \Drupal\Core\Cache\BackendChain, which assumes both chained cache backends are consistent, thus a consistency check being pointless.

Hierarchy

Expanded class hierarchy of ChainedFastRawBackend

See also

\Drupal\Core\Cache\BackendChain

File

src/Cache/ChainedFastRawBackend.php, line 55
Contains \Drupal\supercache\Cache\ChainedFastRawBackend.

Namespace

Drupal\supercache\Cache
View source
class ChainedFastRawBackend implements CacheRawBackendInterface {
  use RequestTimeTrait;
  use CoordinatedWriteCounterTrait;

  /**
   * Override this method from RequestTimeTrait.
   */
  public function refreshRequestTime() {
    $this->fastBackend
      ->refreshRequestTime();
    $this->consistentBackend
      ->refreshRequestTime();
  }

  /**
   * @var string
   */
  protected $bin;

  /**
   * Name of the bin assigned to the fastBackend.
   *
   * @var string
   */
  protected $fastBin;

  /**
   * The consistent cache backend.
   *
   * @var CacheRawBackendInterface
   */
  protected $consistentBackend;

  /**
   * The fast cache backend.
   *
   * @var CacheRawBackendInterface
   */
  protected $fastBackend;

  /**
   * Constructs a ChainedFastBackend object.
   *
   * @param CacheRawBackendInterface $consistent_backend
   *   The consistent cache backend.
   * @param CacheRawBackendInterface $fast_backend
   *   The fast cache backend.
   * @param CacheRawBackendInterface $fast_backend_invalidations
   *   The fast cache backend used to store invalidations. Must always have the same binary.
   * @throws \Exception
   *   When the consistent cache backend and the fast cache backend are the same
   *   service.
   */
  public function __construct(CacheRawBackendInterface $consistent_backend, CacheRawBackendInterface $fast_backend, $mark_as_outdated_explicit = FALSE) {
    if ($consistent_backend == $fast_backend) {

      // @todo: should throw a proper exception. See https://www.drupal.org/node/2751847.
      trigger_error('Consistent cache backend and fast cache backend cannot use the same service.', E_USER_ERROR);
    }
    $this->consistentBackend = $consistent_backend;
    $this->fastBackend = $fast_backend;
    $this->doMarkAsOutdatedExplicit = $mark_as_outdated_explicit;
  }

  /**
   * {@inheritdoc}
   */
  public function get($cid) {
    $cids = array(
      $cid,
    );
    $cache = $this
      ->getMultiple($cids);
    return reset($cache);
  }

  /**
   * {@inheritdoc}
   */
  public function getMultiple(&$cids) {
    $this
      ->clearFastStorageIfInvalid();
    $cids_copy = $cids;
    $cache = array();
    try {
      $items = $this->fastBackend
        ->getMultiple($cids);
    } catch (\Exception $e) {
      $cids = $cids_copy;
      $items = array();
    }

    // Even if items were successfully fetched from the fast backend, they
    // are potentially invalid if older than the last time the bin was
    // written to in the consistent backend, so only keep ones that aren't.
    $cache = $items;

    // If there were any cache entries that were not available in the fast
    // backend, retrieve them from the consistent backend and store them in the
    // fast one.
    if ($cids) {
      $missing = array();
      $cached = $this->consistentBackend
        ->getMultiple($cids);
      foreach ($cached as $cid => $item) {
        $cache[$cid] = $item;
        $missing[$cid] = [
          'data' => $item->data,
        ];
      }
      if (!empty($missing)) {

        // TODO: Expiration data from the consistent
        // backend is lost here. We are setting items
        // in the fast backend with permanent status...
        // But time based expirations are becoming less
        // relevant, and considering that the items
        // in the consistent backend will actually expire
        // properly, this might not be that of an issue.
        $this->fastBackend
          ->setMultiple($missing);
      }
    }
    return $cache;
  }

  /**
   * {@inheritdoc}
   */
  public function set($cid, $data, $expire = Cache::PERMANENT) {
    $this->consistentBackend
      ->set($cid, $data, $expire);
    $this->fastBackend
      ->set($cid, $data, $expire);
    $this
      ->markAsOutdated();
  }

  /**
   * {@inheritdoc}
   */
  public function setMultiple(array $items) {
    if (empty($items)) {
      return;
    }
    $this->consistentBackend
      ->setMultiple($items);
    $this->fastBackend
      ->setMultiple($items);
    $this
      ->markAsOutdated();
  }

  /**
   * {@inheritdoc}
   */
  public function delete($cid) {
    $this->consistentBackend
      ->deleteMultiple(array(
      $cid,
    ));
    $this->fastBackend
      ->deleteMultiple(array(
      $cid,
    ));
    $this
      ->markAsOutdated();
  }

  /**
   * {@inheritdoc}
   */
  public function deleteMultiple(array $cids) {
    $this->consistentBackend
      ->deleteMultiple($cids);
    $this->fastBackend
      ->deleteMultiple($cids);
    $this
      ->markAsOutdated();
  }

  /**
   * {@inheritdoc}
   */
  public function deleteAll() {
    $this->consistentBackend
      ->deleteAll();
    $this->fastBackend
      ->deleteAll();
    $this
      ->markAsOutdated();
  }

  /**
   * {@inheritdoc}
   */
  public function garbageCollection() {
    $this->consistentBackend
      ->garbageCollection();
    $this->fastBackend
      ->garbageCollection();
  }

  /**
   * {@inheritdoc}
   */
  public function removeBin() {
    $this->consistentBackend
      ->removeBin();
    $this->fastBackend
      ->removeBin();
  }

  /**
   * @todo Document in https://www.drupal.org/node/2311945.
   */
  public function reset() {
    $this->lastWriteTimestamp = NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function counter($cid, $increment, $default = 0) {
    $this->consistentBackend
      ->counter($cid, $increment, $default);
    $this->fastBackend
      ->counterSet($cid, $increment, $default);
    $this
      ->markAsOutdated();
  }

  /**
   * {@inheritdoc}
   */
  public function counterMultiple(array $cids, $increment, $default = 0) {
    $this->consistentBackend
      ->counterMultiple($cids, $increment, $default);
    $this->fastBackend
      ->counterMultiple($cids, $increment, $default);
    $this
      ->markAsOutdated();
  }

  /**
   * {@inheritdoc}
   */
  public function counterSet($cid, $value) {
    $this->consistentBackend
      ->set($cid, $value);
    $this->fastBackend
      ->set($cid, $value);
    $this
      ->markAsOutdated();
  }

  /**
   * {@inheritdoc}
   */
  public function counterSetMultiple(array $items) {
    foreach ($items as $cid => $item) {
      $this->consistentBackend
        ->set($cid, (int) $item);
      $this->fastBackend
        ->set($cid, (int) $item);
    }
    $this
      ->markAsOutdated();
  }

  /**
   * {@inheritdoc}
   */
  public function counterGet($cid) {
    $result = $this->counterGetMultiple[$cid];
    return reset($result);
  }

  /**
   * {@inheritdoc}
   */
  public function counterGetMultiple(array &$cids) {
    $this
      ->clearFastStorageIfInvalid();
    $cids_copy = $cids;
    $cache = array();
    try {
      $items = $this->fastBackend
        ->counterGetMultiple($cids);
    } catch (\Exception $e) {
      $cids = $cids_copy;
      $items = array();
    }

    // Even if items were successfully fetched from the fast backend, they
    // are potentially invalid if older than the last time the bin was
    // written to in the consistent backend, so only keep ones that aren't.
    $cache = $items;

    // If there were any cache entries that were not available in the fast
    // backend, retrieve them from the consistent backend and store them in the
    // fast one.
    if ($cids) {
      $missing = array();
      $cached = $this->consistentBackend
        ->counterGetMultiple($cids);
      foreach ($cached as $cid => $item) {
        $cache[$cid] = $item;
        $missing[$cid] = $item;
      }
      if (!empty($missing)) {

        // TODO: Expiration data from the consistent
        // backend is lost here. We are setting items
        // in the fast backend with permanent status...
        // But time based expirations are becoming less
        // relevant, and considering that the items
        // in the consistent backend will actually expire
        // properly, this might not be that of an issue.
        // A possibility is not to support expiration
        // for the ChainedFastRawBackend...
        $this->fastBackend
          ->counterSetMultiple($missing);
      }
    }
    return $cache;
  }

  /**
   * Shutdown functions.
   *
   * Using __destruct() proved to be problematic
   * with some some cache backends such as couchbase
   * with custom transcoders or the Drupal.org
   * test bot.
   *
   * But because binaries are not services... we rely
   * on the ChainedFastBackend factory to subscribe to
   * the onKernelTerminate event and call us.
   *
   */
  public function onKernelTerminate() {
    $this
      ->doMarkAsOutdatedExplicitCall();

    // Once this is done here, any further invalidations
    // must be done as they come.
    $this->doMarkAsOutdatedExplicit = FALSE;
  }

  #region Helper implementations for CoordinatedWriteCounterTrait

  /**
   * {@inheritdoc}
   */
  public function getFastStorage($cid) {
    return $this->fastBackend
      ->get($cid);
  }

  /**
   * {@inheritdoc}
   */
  public function getPersistentStorage($cid) {
    return $this->consistentBackend
      ->get($cid);
  }

  /**
   * {@inheritdoc}
   */
  public function setFastStorage($cid, $value) {
    $this->fastBackend
      ->set($cid, $value);
  }

  /**
   * {@inheritdoc}
   */
  public function setPersistentStorage($cid, $value) {
    $this->consistentBackend
      ->set($cid, $value);
  }

  /**
   * {@inheritdoc}
   */
  public function clearFastStorage($cid, $value) {
    $this->fastBackend
      ->deleteAll();
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CacheRawBackendInterface::CACHE_PERMANENT constant Indicates that the item should never be removed unless explicitly deleted.
ChainedFastRawBackend::$bin protected property
ChainedFastRawBackend::$consistentBackend protected property The consistent cache backend.
ChainedFastRawBackend::$fastBackend protected property The fast cache backend.
ChainedFastRawBackend::$fastBin protected property Name of the bin assigned to the fastBackend.
ChainedFastRawBackend::clearFastStorage public function Overrides CoordinatedWriteCounterTrait::clearFastStorage
ChainedFastRawBackend::counter public function Add an increment (can be negative) to the stored cache data. Only works for stored numeric data. Overrides CacheRawBackendInterface::counter
ChainedFastRawBackend::counterGet public function Get the value of a counter variable. Overrides CacheRawBackendInterface::counterGet
ChainedFastRawBackend::counterGetMultiple public function Get multiple counter values at once. Overrides CacheRawBackendInterface::counterGetMultiple
ChainedFastRawBackend::counterMultiple public function Add an increment (can be negative) to the stored cache data. Only works for stored numeric data. Overrides CacheRawBackendInterface::counterMultiple
ChainedFastRawBackend::counterSet public function Set the value for a counter storage item. Overrides CacheRawBackendInterface::counterSet
ChainedFastRawBackend::counterSetMultiple public function Set the value of counter variables in batch. Overrides CacheRawBackendInterface::counterSetMultiple
ChainedFastRawBackend::delete public function Deletes an item from the cache. Overrides CacheRawBackendInterface::delete
ChainedFastRawBackend::deleteAll public function Deletes all cache items in a bin. Overrides CacheRawBackendInterface::deleteAll
ChainedFastRawBackend::deleteMultiple public function Deletes multiple items from the cache. Overrides CacheRawBackendInterface::deleteMultiple
ChainedFastRawBackend::garbageCollection public function Performs garbage collection on a cache bin. Overrides CacheRawBackendInterface::garbageCollection
ChainedFastRawBackend::get public function Returns data from the persistent cache. Overrides CacheRawBackendInterface::get
ChainedFastRawBackend::getFastStorage public function Overrides CoordinatedWriteCounterTrait::getFastStorage
ChainedFastRawBackend::getMultiple public function Returns data from the persistent cache when given an array of cache IDs. Overrides CacheRawBackendInterface::getMultiple
ChainedFastRawBackend::getPersistentStorage public function Overrides CoordinatedWriteCounterTrait::getPersistentStorage
ChainedFastRawBackend::onKernelTerminate public function Shutdown functions.
ChainedFastRawBackend::refreshRequestTime public function Override this method from RequestTimeTrait. Overrides RequestTimeTrait::refreshRequestTime
ChainedFastRawBackend::removeBin public function Remove a cache bin. Overrides CacheRawBackendInterface::removeBin
ChainedFastRawBackend::reset public function @todo Document in https://www.drupal.org/node/2311945.
ChainedFastRawBackend::set public function Stores data in the persistent cache. Overrides CacheRawBackendInterface::set
ChainedFastRawBackend::setFastStorage public function Overrides CoordinatedWriteCounterTrait::setFastStorage
ChainedFastRawBackend::setMultiple public function Store multiple items in the persistent cache. Overrides CacheRawBackendInterface::setMultiple
ChainedFastRawBackend::setPersistentStorage public function Overrides CoordinatedWriteCounterTrait::setPersistentStorage
ChainedFastRawBackend::__construct public function Constructs a ChainedFastBackend object.
CoordinatedWriteCounterTrait::$doMarkAsOutdatedExplicit protected property For the persistent backend to be marked as outdated the implementing class must explictly call
CoordinatedWriteCounterTrait::$fastStorageInvalid protected property Summary of $fastStorageInvalid
CoordinatedWriteCounterTrait::$headId protected property Identifier for the instance of the volatile storage.
CoordinatedWriteCounterTrait::$headKey protected property Key used to store the coordinated write counters.
CoordinatedWriteCounterTrait::$lastWrite protected property The time at which the last write to this cache bin happened.
CoordinatedWriteCounterTrait::$last_invalidation protected property Last timestamp that a local invalidation took place.
CoordinatedWriteCounterTrait::$writeKey protected property Key used to store the coordinated write counters.
CoordinatedWriteCounterTrait::clearFastStorageIfInvalid protected function Some storage backends cannot rely on the information provided by $this->getLastWrite() to tell what items they should invalidate. Calling this method will clear all of the fast backend if it is considered not to be consistent with the contents of…
CoordinatedWriteCounterTrait::doMarkAsOutdatedExplicitCall protected function To be called by the implementing class whenever it wants to persistent the last invalidation.
CoordinatedWriteCounterTrait::getHeadId protected function Retrieve the key that identifies the volatile storage head.
CoordinatedWriteCounterTrait::getLastWrite protected function Items retrieve from the persistent backend that have been modified prior to this timestamp are to be considered outdated.
CoordinatedWriteCounterTrait::markAsOutdated protected function Notify that a write has happened, it does not inmediately invalidate the persistent storage.
CoordinatedWriteCounterTrait::_doMarkAsOutdated protected function Mark as outdated.
RequestTimeTrait::$requestTime protected property Current time used to validate cache item expiration times.
RequestTimeTrait::shortMd5 protected function Returns a 12 character length MD5.