At present, the Drupal memcache module disables compression when using the memcached extension:

    $default_opts = array(
      Memcached::OPT_COMPRESSION => FALSE,
      Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
    );

This was discussed several years ago in #1472332: Implications of enabling compression and the consensus seemed to be that the benefits of compression were not worth the cost in terms of CPU.

However, lots of sites are moving from the memcache extension to memcached as a side-effect of upgrading to PHP7.x (as only the memcached extension has been updated to support PHP7), and we have seen quite a few suffer from significant performance degradation as a result of compression being disabled as part of this change.

The negative effects include:

  • A dramatic increase in volume of data being transmitted.
  • An increase in the number of cache items which have to be split as they exceed the size limit.
  • An increase in evictions in memcached as much more data is stored.

It's possible to enable compression with the memcached extension e.g. in settings.php (this example is for D7):

  $conf['memcache_options'] = array(
    Memcached::OPT_COMPRESSION => TRUE,
  );

...and we've observed a reversal of the negative performance impacts outlined above when compression is enabled like this. In some cases the performance improvement after enabling compression has been quite dramatic.

As the memcache extension does compression by default, mirroring that behaviour with the memcached extension seems reasonable; there should not be a net increase in CPU cost (assuming a site is moving from memcache to memcached), and the benefits of compression seem to be considerable in many cases.

We've seen enough evidence of disabling compression (as part of the move from PHP5.6/memcache to PHP7.x/memcached) causing significant performance degradation to advocate for compression to be enabled by default.

Patch(es) on the way; this should be done in both the D7 and D8 branches.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mcdruid created an issue. See original summary.

mcdruid’s picture

Status: Active » Needs review
FileSize
1.56 KB
1.51 KB

Patches for 7.x-1.x and 8.x-2.x

Jeremy’s picture

Thanks for the patch; I'll do some new load testing to compare and confirm it's a win.

mxh’s picture

Here are some observations using Gzip compression with Database backend.

webchick’s picture

@Jeremy pointed me over to https://github.com/tag1consulting/drupal-loadtest which is what they use for load testing, if anyone is game to do this.

mcdruid’s picture

Thanks, I did a first pass at a load test using D7 and the current memcache-7.x-1.6 with and without the patch.

The test scripts are set up for D6 so I had to make a few minor tweaks (e.g. setting passwords through drush rather than just md5 straight into the db). I don't think I had to change anything particularly significant though.

This was with PHP 7.2 and D7 HEAD, using the memcached extension. The memcached deamon had pretty much default settings, with -m 64 memory allocation.

Initial results:

Before (i.e. no compression)

"total_connections"     111
"cmd_get"       268889
"cmd_set"       50959
"get_hits"      186348
"get_misses"    82541
"delete_hits"   0
"delete_misses" 0
"incr_hits"     0
"bytes_read"    156301429
"bytes_written" 1598495217
"evictions"     30473
"total_items"   50959

"Hit rate"      69.3000%
"Miss rate"     30.6900%

After (i.e. with compression enabled)

"total_connections"     113
"cmd_get"       268311
"cmd_set"       48971
"get_hits"      187693
"get_misses"    80618
"delete_hits"   103
"delete_misses" 5
"incr_hits"     0
"bytes_read"    95554661
"bytes_written" 529175708
"evictions"     16110
"total_items"   48971

"Hit rate"      69.9500%
"Miss rate"     30.0400%

So my interpretation of those numbers are that the hit rates etc.. were not significantly different, but...

Without compression, 1598.50MB were written, and 156.30MB read. There were 30.5k evictions.

With compression, 529.18MB were written, and 95.55MB read. There were 16.1k evictions.

Very roughly speaking, compression resulted in 1/3 of the amount of data being written, 2/3 the amount of data read, and almost half the amount of evictions.

Here are the performance comparisons:

Before

Type                    Total       Err           ms_(Average)  ms_(Max)  Rate
"Anon_frontpage"        5945        0(0.00%)      580           1782      14.00
"Anon_node"             3930        0(0.00%)      753           1832      9.40
"Anon_profile"          1179        0(0.00%)      749           1842      3.10
"Anon_load_login_form"  270         0(0.00%)      1080          2191      0.20
"Auth_post_login"       0           135(100.00%)  0             0         0.00
"Auth_node"             888         0(0.00%)      761           1838      2.10
"Auth_profile"          286         0(0.00%)      732           1451      1.00
"Auth_frontpage"        1310        0(0.00%)      772           1924      3.50
"Auth_comment_form"     270         0(0.00%)      1080          2191      0.20
"Auth_post_comment"     0           135(100.00%)  0             0         0.00
"Static_file"           36154       0(0.00%)      427           1749      83.40
"Total"                 49964       135(0.27%)    116.70

After

Type                    Total      Err           ms_(Average)  ms_(Max)  Rate
"Anon_frontpage"        5925       0(0.00%)      628           1889      14.30
"Anon_node"             4035       0(0.00%)      790           2224      9.50
"Anon_profile"          1193       0(0.00%)      801           1622      2.00
"Anon_load_login_form"  225        0(0.00%)      1191          2773      0.40
"Auth_post_login"       0          114(100.00%)  0             0         0.00
"Auth_node"             864        0(0.00%)      852           1840      2.30
"Auth_profile"          277        0(0.00%)      780           1555      0.90
"Auth_frontpage"        1316       0(0.00%)      807           1804      3.00
"Auth_comment_form"     225        0(0.00%)      1191          2773      0.40
"Auth_post_comment"     0          114(100.00%)  0             0         0.00
"Static_file"           36144      0(0.00%)      472           1850      94.30
"Total"                 49985      114(0.23%)    126.70

I'm not sure how much to trust these numbers, as this test was run on my laptop using containers while it had several other things running... so this comparison may be less accurate than the memcache stats.

It looks like the site may have been very marginally slower with compression enabled, but I'd like to run these again in a more scientific test before drawing any real conclusions.

I suspect I may not have configured the test site perfectly (e.g. the changes which are marked as "todo: should be scripted"), as reflected by the 100% error rates for a couple of the rows.

IMHO whether the compression is "worth" any additional cost in CPU will depend on a few factors, but if data going over the network incurs any significant cost then that alone can cause performance degradation with no compression. The higher memory requirements for storing uncompressed data, and corresponding higher rate of evictions given the same allocation of memory can also be significant.

FWIW I think some D8 sites may cache significantly more data than this basic D7 test site, and it seems that the performance impact of handling uncompressed data is therefore greater.

For some simple sites that don't cache a large amount of data, the difference may not be much.

I'd still vote for compression on by default, at this stage... but I'm happy to do some more testing if this is a useful exercise.

mxh’s picture

Maybe following points can improve the significance of the test results regards performance impact by compression:

  • Set size threshold to 0. Currently at 100 bytes as its documentation points out, but it seems Memcached does not have an option to change this threshold :( Memcache does though.
  • Ensure a 0% eviction rate by setting the Memcache size to a very high limit )if not yet done.

I'm trying to find some time playing around with the compression functions provided by the zlib extension inside the Memcache backend service, instead of using Memcached's built in compression. Probably worth checking out in case serialization is not the problem there...

mxh’s picture

I'm sorry that I can't deliver well-defined test results here atm, but I took some time playing around a little bit on a D8 project using 180+ modules. On its frontpage - not using page and dynamic page cache - 1582 config items, 135 discovery items, 7 bootstrap items, 87 entity items, 148 data items, 61 render items, 65 default items and 8 menu items are being loaded from the cache when cache items are valid. My findings on that:

  1. Overall, I had a very high volatility (up to 10%) on loading times due to varying environment processes, regardless whether compression was enabled or not. Yet I could see that ...
  2. ... When having a small Memcache size of 64M, enabled compression significantly improved performance due to the reduction of evictions. Loading time was reduced from about 700 ms down to 500 ms on average (read).
  3. ... Cache writes with enabled compression are visibly slower, but the volatility on this project's loading time is still so high that this makes barely a difference. For me, this difference is subjectively negligible.

I estimate (2.) for not being true at production mode, as the Memcache size would still be too small to handle all variants of cached items. I think the eviction rate would still be too high at this size. Luckily the Memcache size is way larger in production mode, yet having many evictions due to a very busy render and data cache bin. I already have worked against that with the help of the Cache Split module. Especially the render cache can be split up very well.

I also tried with zlib compression by adding a serialization+gzcompress (level 6 and level 3) and unserialization+gzuncompress step before get/sets, not using Memcached's built in compression. But this was visibly slower than Memcached's built in compression (up to 300 ms difference, but I could not exclude the volatility), maybe because it compresses all items including those which are smaller than 100 bytes. So I think my zlib approach from above is not a valuable alternative.

Next time I will try to reduce the volatility, and analyze the method's overhead with the help of Xhprof.

AS a result from my observations: For this project, using PHP 7.1, I would enable Memcached's built in compression in favor of

  • reduced network overhead, as there is a lot going on with PHP, Memcache and Database communications.
  • reduced eviction rate (would need to observe though at busy times)
  • negligible performance loss (subjectively, will see what Xhprof says)

Edit: I fetched some numbers via Xhprof - 3 times - out of Drupal\memcache\MemcacheBackend::getMultiple regards the above mentioned frontpage (all numbers are milliseconds):

Compression disabled:
(1) Request after drush cr: 183, 267, 262
(2) Request after (1): 170, 137, 195
Compression enabled:
(1) Request after drush cr: 198, 244, 250
(2) Request after (1): 192, 135, 135
So still the volatility thing here.

Then, looking at Drupal\memcache\DrupalMemcached::set I was quite surprised:

Compression disabled:
(1) Request after drush cr: 700, 765, 509
Compression enabled:
(1) Request after drush cr: 533, 520, 451
I'm not sure, it could be that the environment processes were going wild when I looked at disabled compression. It just seems to me that compression is a completely negligible performance impact.
I guess, the more complex the project, the higher its volatility and by this, I would recommend using compression.

bkosborne’s picture

I think a big benefit of using compression is that it's less likely you'll exceed the default memcache item size limit of 1MiB. The D7 version of this module accounts for items larger than that by splitting up the data into multiple chunks and then re-constructing them. There's some work to get that same procedure into the D8 branch as well but it's not committed yet.

vurt’s picture

I did a short benchmark on two quite big D8 sites (local memcached). The difference with compression was not huge and for my test cases it even seemed to be a little faster and consistent without compression.

Compression offers an advantage when the memcache server(s) are not installed on the same machine as the webserver and network throughput becomes more important.

I guess it is not really important if compression becomes the default or not. It is more important to document the options (with advantages and disadvantages). For really big sites owners should do benchmarks and compare for themselves anyway.

@bkosborne: This was missing for D8 a long time - happy to see this is on the way now.

bkosborne’s picture

Priority: Normal » Major

I want to re-iterate my point in #9. I recently found out that the PHP "memcache" extension has had compression enabled by default since version 3.0.3. Users that switch to PHP 7 and must use the Memcached extension will likely suddenly stop using compression, since the memcached extension does NOT enable it by default.

As a result, much less data will be available for storage, so evictions will be higher.

vurt’s picture

bkosborne, you are correct. We are on the safer side if compression is on as a default.

bkosborne’s picture

Status: Needs review » Reviewed & tested by the community
catch’s picture

FileSize
1.24 KB

The 8.x patch didn't apply. I've re-rolled it, and also instead of removing all the mentions of compression, changed the default to TRUE and updated the documentation to reflect the choice.

Also, generally agreed on enabling compression by default. 8.x stores things like entities which potentially have a lot of long text fields that are very compressible as well as render caching in general, so the ratio of items that will benefit from compression should be higher as well as there just generally being a lot more stuff to cache and therefore space/memory being more of an issue.

catch’s picture

Given this is something that can be enabled and disabled in settings.php, should we be adding the setting as a bit to memcache keys? That way changing it doesn't require a flush. Not sure what memcache does if it encounters compressed/uncompressed data when the setting is flipped.

vurt’s picture

I would just add something like "when changing this setting you need to empty the cache..." to the readme. It is not a setting that you change often.

Fabianx’s picture

RTBC + 1, let's get this committed

Given that we did not document changing it that much before, I'd suggest people should have an idea what they do when changing that setting.

In other words:

I do not think the key space we waste for storing the setting is worth the trouble of how less this will be changed in practice - especially if this sneakily is the default in memcache-3.0.3 anyway.

damiankloip’s picture

I have been following along, and had a couple of discussions about this too. I agree with the conclusions above. Going to commit this one. Thanks everyone for your input.

  • damiankloip committed df43788 on 8.x-2.x authored by catch
    Issue #2958403 by mcdruid, catch, mxh, bkosborne, vurt, Jeremy,...
damiankloip’s picture

Status: Reviewed & tested by the community » Fixed
mcdruid’s picture

Great - thanks for committing this for D8.

I created #2996888: enable compression by default with memcached extension (D7 branch) to get this into the D7 branch too.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.