Verified Commit 4f3e6a28 authored by godotislate's avatar godotislate
Browse files

task: #3588547 Don't invalidate the entire fast backend on persistent backend cache misses

By: catch
By: godotislate
parent ecf6584e
Loading
Loading
Loading
Loading
Loading
+24 −9
Original line number Diff line number Diff line
@@ -202,26 +202,41 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
   * {@inheritdoc}
   */
  public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
    // Setting a cache item on the consistent backend requires invalidating the
    // fast backend. In a cold cache situation, there can be thousands of cache
    // sets. However, because each cache set invalidates every previous set,
    // only the item(s) from the last one will be valid. Therefore, don't write
    // to the fast backend, this avoids lock/write contention on the fast
    // backend, for cache items which may not be requested immediately anyway,
    // e.g. when higher level caches are warmed at the same time. The fast
    // backend will be populated via the logic in ::get() instead when cache
    // items are actually requested.

    // When setting a cache item, if the cache item already exists in the
    // consistent backend then the fast backend must be invalidated. However,
    // if the item does not exist in the consistent backend at all, it will also
    // be new on the fast backend and we can skip invalidating the entire bin.
    //
    // Because there is no way inherently to know whether an item is being added
    // or updated when it is set, first get the item from the persistent backend
    // then act based on that. There will be a small window between getting the
    // item and setting it where another process may have set a value, but that
    // process will also have previously got an empty cache item so the risk of
    // a race condition resulting in different values is extremely low.
    //
    // In both cases, don't bother writing back to the fast backend - the next
    // call to ::get() with this cache ID will do that.
    $cached = $this->consistentBackend->get($cid);
    $this->consistentBackend->set($cid, $data, $expire, $tags);
    if ($cached) {
      $this->markAsOutdated();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function setMultiple(array $items) {
    $cids = array_keys($items);

    $cached = $this->consistentBackend->getMultiple($cids);
    $this->consistentBackend->setMultiple($items);

    if (count($cached)) {
      $this->markAsOutdated();
    }
  }

  /**
   * {@inheritdoc}
+47 −1
Original line number Diff line number Diff line
@@ -112,7 +112,41 @@ public function testSetInvalidDataFastBackend(): void {
  /**
   * Tests that sets only get written to the consistent backend.
   */
  public function testSet(): void {
  public function testSetEmptyConsistent(): void {
    $consistent_cache = $this->createMock(CacheBackendInterface::class);
    $fast_cache = $this->createMock(CacheBackendInterface::class);

    // The initial write to the fast backend should result in two writes to the
    // consistent backend, once to invalidate the last write timestamp and once
    // for the item itself. However subsequent writes during the same second
    // should only write to the cache item without further invalidations.

    $consistent_cache->expects($this->exactly(4))
      ->method('set');
    $consistent_cache->expects($this->exactly(4))
      ->method('get')
      ->willReturn(FALSE);

    $fast_cache->expects($this->never())
      ->method('set');
    $fast_cache->expects($this->never())
      ->method('setMultiple');

    $chained_fast_backend = new ChainedFastBackend(
      $consistent_cache,
      $fast_cache,
      'foo'
    );
    $chained_fast_backend->set('foo', TRUE);
    $chained_fast_backend->set('bar', TRUE);
    $chained_fast_backend->set('baz', TRUE);
    $chained_fast_backend->set('zoo', TRUE);
  }

  /**
   * Tests that sets only get written to the consistent backend.
   */
  public function testSetFullConsistent(): void {
    $consistent_cache = $this->createMock(CacheBackendInterface::class);
    $fast_cache = $this->createMock(CacheBackendInterface::class);

@@ -123,6 +157,18 @@ public function testSet(): void {

    $consistent_cache->expects($this->exactly(5))
      ->method('set');
    $consistent_cache->expects($this->exactly(5))
      ->method('get')
      ->willReturnCallback(function ($cid) {
        if ($cid === 'last_write_timestamp_cache_foo') {
          return FALSE;
        }
        return (object) [
          'cid' => $cid,
          'data' => 'foo',
        ];
      });

    $fast_cache->expects($this->never())
      ->method('set');
    $fast_cache->expects($this->never())