Skip to content

Enable Redis cache key compression

Stan Hu requested to merge sh-enable-redis-key-compression into master

We have a number of keys that are over 1 MB that would be compressed significantly (over 90%) if this feature were enabled. We had this disabled by default to ensure backwards compatibility with the previous Redis store.

We should enable it now because compression significantly reduces the pressure on the Redis server. Every time a large payload is stored in Redis, Redis may need to evict many small keys to make room.

This adds a little more CPU overhead on the application workers, which is why we have an environment variable (ENABLE_REDIS_CACHE_COMPRESSION) to turn if off it it becomes a problem.

Behind the scenes, Rails implements this by serializing a compressed variable with the ActiveSupport::Cache::Entry if the payload size exceeds the compression threshold (4K by default). When the entry is deserialized from Redis, if the compressed boolean is set, the value will be decompressed. Thus even in a mixed-deployment where this setting is not on by default, Rails will be able to decode compressed values just fine.

Closes #198586 (closed)

Example

[2] pry(main)> data = File.read('/tmp/parsed.txt'); nil
=> nil
[3] pry(main)> Rails.cache.write('test-compress', data); nil
=> nil
[4] pry(main)> Rails.cache.read('test-compress').length
=> 5194491
[5] pry(main)> Gitlab::Redis::Cache.with { |redis| redis.get('cache:gitlab:test-compress') }.length
=> 148086

The read also works when the config variable is disabled.

Benchmarks

Summary

Read performance

I tested read/write performance in three cases:

  1. Staging
  2. My local GDK
  3. Local Omnibus instance
Test Compressed? Iterations/s
GDK Yes 62.5
Omnibus No 36.8
Omnibus Yes 36.7
Staging Yes 34.6
Staging No 26.0
GDK No 12.3
Write performance
Test Compressed? Iterations/s
Staging No 30.8
GDK Yes 19.3
Staging Yes 14.2
Omnibus Yes 9.2
GDK No 7.0
Omnibus No 1.2

As we can see, compression usually speeds up reads, presumably because there are fewer network hops, but is a little slower on writes. I'm not sure why the times were roughly equivalent in the Omnibus case. The iterations/s comparison across tests may not be as important as the differences within the same machine.

Read performance (staging)

require 'benchmark/ips'

data = File.read('/tmp/parsed.txt')

Rails.cache.options[:compress] = true
Rails.cache.write('test1', data)

Rails.cache.options[:compress] = false
Rails.cache.write('test2', data)

Benchmark.ips do |x|
  x.report("compressed") {
    Rails.cache.read('test1')
  }

  x.report("uncompressed") {
    Rails.cache.read('test2')
  }

  x.compare!
end
Calculating -------------------------------------
          compressed     3.000  i/100ms
        uncompressed     2.000  i/100ms
-------------------------------------------------
          compressed     34.566  (± 2.9%) i/s -    174.000
        uncompressed     26.046  (±23.0%) i/s -    124.000

Comparison:
          compressed:       34.6 i/s
        uncompressed:       26.0 i/s - 1.33x slower

Write performance (staging)

However, write performance is 2x slower:

require 'benchmark/ips'

data = File.read('/tmp/parsed.txt')

Benchmark.ips do |x|
  x.report("compressed") {
    Rails.cache.options[:compress] = true
    Rails.cache.write('test1', data)
  }

  x.report("uncompressed") {
    Rails.cache.options[:compress] = false
    Rails.cache.write('test2', data)
  }

  x.compare!
end
Calculating -------------------------------------
          compressed     1.000  i/100ms
        uncompressed     3.000  i/100ms
-------------------------------------------------
          compressed     14.198  (±21.1%) i/s -     68.000
        uncompressed     30.807  (± 3.2%) i/s -    156.000

Comparison:
        uncompressed:       30.8 i/s
          compressed:       14.2 i/s - 2.17x slower

Local benchmarks

Read

Calculating -------------------------------------
          compressed     5.000  i/100ms
        uncompressed     1.000  i/100ms
-------------------------------------------------
          compressed     62.534  (±17.6%) i/s -    305.000
        uncompressed     12.306  (±65.0%) i/s -     47.000

Comparison:
          compressed:       62.5 i/s
        uncompressed:       12.3 i/s - 5.08x slower

Write

Calculating -------------------------------------
          compressed     1.000  i/100ms
        uncompressed     1.000  i/100ms
-------------------------------------------------
          compressed     19.297  (±15.5%) i/s -     94.000
        uncompressed      6.975  (±14.3%) i/s -     35.000

Comparison:
          compressed:       19.3 i/s
        uncompressed:        7.0 i/s - 2.77x slower
Edited by Yorick Peterse

Merge request reports