Loading core/lib/Drupal/Core/Cache/ChainedFastBackend.php +24 −9 Original line number Diff line number Diff line Loading @@ -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} Loading core/tests/Drupal/Tests/Core/Cache/ChainedFastBackendTest.php +47 −1 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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()) Loading Loading
core/lib/Drupal/Core/Cache/ChainedFastBackend.php +24 −9 Original line number Diff line number Diff line Loading @@ -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} Loading
core/tests/Drupal/Tests/Core/Cache/ChainedFastBackendTest.php +47 −1 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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()) Loading