Problem/Motivation
ChainedFast is a nice backend, which has it's problems as it needs to write back all data again and again to the fast backend.
One of the most CPU intensive things is the constant serialization and unserialization -- even if it's usually handled by the cache backends themselves.
One of the reasons is that Drupal uses DEEP arrays, and DEEP objects.
However if you do:
$item = (object) [
'data' => serialize($data),
];
$store = serialize($data);
this is almost as fast as:
$item = (object) [
'data' => $data,
];
$store = serialize($data);
because the one level for serialization is not responsible for a huge performance penalty.
And we can use this to our advantage!
Proposed resolution
If we serialize() the data before set() and unserialize() the data after get() / getMultiple(), then in practice it means that 90% of the data is already serialized, which we get back from the consistent backend!
Which means we save 90% of the serialization effort when saving data to the fast backend!
And that in the end means that things are 90% faster.
(And APCu is storing serialized data as well and no longer copying raw PHP objects into memory, so that's no longer a concern.)
Also: Because we do not need to unserialize the data coming from APCu, we can effectively save the unserialize() step when the data is invalid -- before we would unserialize twice.
Remaining tasks
- Create patch based on the following:
diff --git a/web/core/lib/Drupal/Core/Cache/ChainedFastBackend.php b/web/core/lib/Drupal/Core/Cache/ChainedFastBackend.php
--- a/web/core/lib/Drupal/Core/Cache/ChainedFastBackend.php
+++ b/web/core/lib/Drupal/Core/Cache/ChainedFastBackend.php
@@ -170,6 +170,10 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
}
}
+ foreach ($cache as $cid => $item) {
+ $item->data = unserialize($item->data);
+ }
+
return $cache;
}
@@ -177,6 +181,8 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
* {@inheritdoc}
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
+ $data = serialize($data);
+
$this->consistentBackend->set($cid, $data, $expire, $tags);
$this->markAsOutdated();
// Don't write the cache tags to the fast backend as any cache tag
@@ -188,6 +194,10 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
* {@inheritdoc}
*/
public function setMultiple(array $items) {
+ foreach ($items as &$item) {
+ $item['data'] = serialize($item['data']);
+ }
+
$this->consistentBackend->setMultiple($items);
$this->markAsOutdated();
// Don't write the cache tags to the fast backend as any cache tag
User interface changes
- None
API changes
- None
Data model changes
- None

| Comment | File | Size | Author |
|---|---|---|---|
| #34 | 3014521-34.patch | 2.13 KB | qusai taha |
| #29 | Screenshot 2023-01-22 at 14-01-04 Blackfire.io Continuous Performance Testing.png | 32.78 KB | berdir |
| #26 | 3014521-26.patch | 2.15 KB | it-cru |
| #21 | 3014521-21.patch | 1.26 KB | _utsavsharma |
| #21 | interdiff_19-21.txt | 493 bytes | _utsavsharma |
Issue fork drupal-3014521
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
fabianx commentedComment #3
fabianx commentedComment #4
fabianx commentedComment #5
andypostWhat if replace this call with configurable serializer so igbinary (for example) could be used here
Comment #6
zahord commentedHi! created patch file using copy code shared
Comment #7
zahord commentedComment #11
sivaji_ganesh_jojodae commentedThe patch still applies and the test result shows success on the "View Results" page.
Comment #12
mradcliffeI am removing the Novice tag from this issue because I am not sure it is clear what the next step is. The current patch and approach failed tests.
Also I think that having an additional test or looking to see how this change behaves with how existing data is serialized.
Comment #19
it-cruProvided patch for D10.1.x
Comment #20
catchThe static analysis errors need fixing.
Comment #21
_utsavsharma commentedTried to fix CCF for #19.
Comment #22
_utsavsharma commentedComment #24
socialnicheguru commentedCould http://drupal.org/project/igbinary be used for a similar result for Drupal 8,9?
Comment #25
it-cru@SocialNicheGuru maybe it could be configurable at cache.backend.chainedfast service definition. Especially when someone doesn't use DB and APCu as chainedfast caching backend. I think this also depends on how often igbinary PHP extension is enabled by default.
Maybe at the first step it would be good to get some feedback, that this is already such a performance improvement and it doesn't break something for other caching setups for example with Memcached, Redis and so on.
In my setup I run it together with Memcached as default caching backend with following config with default service config for cache.backend.chainedfast from core (currently only tested locally in my DDEV setup).
Comment #26
it-cruBased on patch from #21 and adjusted ChainedFast related tests.
Comment #27
catchigbinary has an existing issue: #1340260: Support for igbinary.
Comment #28
berdirI'm a bit confused by the patch as it seems to ignore the conditional serialization that the databasebackend and most others do, although it also says at the same time that serializing a string is very efficient, which I assume is quite possible.
I did some profiling by forcing the fast chained backend to always treat the fast one as dirty. apcu_store() took about 12.5ms (2.85% of the full request as admin with disabled render cache on umami) and blackfire is no longer reporting those calls at all then with the patch.
That said, the title is clearly misleading, it's only 90% faster *if* we have a dirty fast backend (and with #3334489: ChainedFastBackend invalidates all items when cache tags are invalidated, that should be way, way less frequent) and it's less only about 2-3% speedup of the whole request, so I'm downgrading that from major to normal.
Comment #29
berdirTo add to that, even when just looking at the isolated performance of ChainedFastBackend, an all-miss situation looks like this:
So writing back the cache entries only takes 25% of the time, so we save ~90% of 25%.
Here's an attempt at a better issue title that reflects what it is actually about.
One thing to keep in mind is also that this change is quite tricky to apply on a site, adding or removing the patch or updating if it were to be committed with any existing caches is not be handled and probably should be.
Comment #30
smustgrave commented#29 requested an issue summary update so moving to NW for that.
Comment #33
it-cruRollback of patch from #26 as MR to apply against 11.x and 10.3.x again.
One kernel and some functional unit tests are failing in Gitlab CI jobs now, but I have no clue why this wasn't previously the case. So I think this needs some more investigations.
Comment #34
qusai taha commentedRe-roll the patch to make it working with 10.3.2
Comment #35
socialnicheguru commentedI was migrating a site from Drupal 10 to Drupal 10.3.
I applied the patch in composer.json:
"Improve fast-backend write performance of ChainedFastBackend by serializing upfront":"https://www.drupal.org/files/issues/2024-09-01/3014521-34.patch",
I received the following error:
Drush command terminated abnormally due to an unrecoverable error. Error: Uncaught TypeError: unserialize(): Argument #1 ($data) must be of type string, array given in drupal-10.3.x/html/core/lib/Drupal/Core/Cache/ChainedFastBackend.php:174 Stack trace:
#0 drupal-10.3.x/html/core/lib/Drupal/Core/Cache/ChainedFastBackend.php(174): unserialize()
#1 drupal-10.3.x/html/core/lib/Drupal/Core/Config/CachedStorage.php(87): Drupal\Core\Cache\ChainedFastBackend->getMultiple()
#2 drupal-10.3.x/html/core/lib/Drupal/Core/Config/ConfigFactory.php(165): Drupal\Core\Config\CachedStorage->readMultiple()
#3 drupal-10.3.x/html/core/lib/Drupal/Core/Config/ConfigFactory.php(104): Drupal\Core\Config\ConfigFactory->doLoadMultiple()
#4 drupal-10.3.x/html/core/lib/Drupal/Core/Config/ConfigFactory.php(89): Drupal\Core\Config\ConfigFactory->doGet()
#5 drupal-10.3.x/html/core/lib/Drupal.php(414): Drupal\Core\Config\ConfigFactory->get()
#6 drupal-10.3.x/html/core/includes/errors.inc(323): Drupal::config()
#7 drupal-10.3.x/html/core/includes/errors.inc(123): _drupal_get_error_level()
#8 drupal-10.3.x/html/core/includes/bootstrap.inc(204): error_displayable()
#9 drupal-10.3.x/html/core/includes/bootstrap.inc(188): _drupal_exception_handler_additional()
#10 [internal function]: _drupal_exception_handler()
#11 {main} thrown in drupal-10.3.x/html/core/lib/Drupal/Core/Cache/ChainedFastBackend.php, line 174