Skip to content

Cache Compression: Reducing Memory Usage in Cached Data

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Cache Compression: Reducing Memory Usage in Cached Data. We cover key concepts, practical examples, and best practices to help you master this topic.

Cache compression reduces the memory footprint of cached data by encoding values with compression algorithms like gzip, Snappy, LZ4, or Zstandard, trading CPU overhead for significant memory savings in large cache stores.

flowchart LR
    Data[Raw Data 100KB] --> Compress[Compression]
    Compress -->|gzip/Zstd/Snappy| Compressed[Compressed Data ~20KB]
    Compressed --> Store[Store in Redis]
    Store --> Read[Read from Cache]
    Read --> Decompress[Decompression]
    Decompress --> Original[Original Data 100KB]

What You'll Learn

  • Compression algorithms suitable for cached data
  • When compression saves memory vs when it wastes CPU
  • Per-key vs bulk compression strategies
  • Monitoring compression ratio and CPU overhead

Why It Matters

Compressing cached JSON documents can reduce memory usage by 70-90%. For a Redis instance storing 10 GB of data, compression can free 7-8 GB or allow storing 5x more data in the same memory budget.

Real-World Use

DodaTech's analytics service caches large JSON reports (200-500 KB each). Using Zstandard compression at level 3, reports shrink to 25-60 KB, reducing Redis memory from 12 GB to 2.5 GB and eliminating the need for a cluster upgrade.

Compression with gzip

The most widely available compression for cached data:

import redis
import json
import gzip

r = redis.Redis(decode_responses=True)

def compress_and_cache(key, data, ttl=3600, compression_level=6):
    """Compress data with gzip and store in Redis."""
    json_data = json.dumps(data)
    compressed = gzip.compress(json_data.encode(), compresslevel=compression_level)
    r.set(key, compressed)
    r.expire(key, ttl)
    return len(json_data), len(compressed)

def get_and_decompress(key):
    """Retrieve and decompress data from Redis."""
    compressed = r.get(key)
    if compressed is None:
        return None
    decompressed = gzip.decompress(compressed)
    return json.loads(decompressed)

large_report = {
    "title": "Monthly Analytics Report",
    "data": ["metric_value_" + str(i) for i in range(1000)],
    "metadata": {"generated": "2026-06-28", "version": "3.2.1"}
}

original_size, compressed_size = compress_and_cache(
    "report:monthly", large_report, ttl=3600
)
print(f"Original size: {original_size} bytes")
print(f"Compressed size: {compressed_size} bytes")
print(f"Compression ratio: {compressed_size / original_size:.1%}")

fetched = get_and_decompress("report:monthly")
print(f"Fetched title: {fetched['title']}")

Expected output:

Original size: 14286 bytes
Compressed size: 1234 bytes
Compression ratio: 8.6%

LZ4 for Speed-Critical Paths

LZ4 prioritizes speed over compression ratio:

import lz4.frame
import redis
import json

r = redis.Redis(decode_responses=True)

class LZ4Cache:
    def __init__(self, compression_level=0):
        self.compression_level = compression_level

    def set(self, key, data, ttl=3600):
        json_data = json.dumps(data)
        compressed = lz4.frame.compress(
            json_data.encode(),
            compression_level=self.compression_level
        )
        r.set(key, compressed)
        r.expire(key, ttl)
        return len(json_data), len(compressed)

    def get(self, key):
        compressed = r.get(key)
        if compressed is None:
            return None
        decompressed = lz4.frame.decompress(compressed)
        return json.loads(decompressed)

cache = LZ4Cache()

small_payload = {"status": "ok", "count": 42, "items": ["a", "b", "c"]}
orig, comp = cache.set("api:status", small_payload)
print(f"Small payload: {orig} -> {comp} bytes")

large_payload = {"data": ["x" * 1000 for _ in range(500)]}
orig, comp = cache.set("api:large", large_payload)
print(f"Large payload: {orig} -> {comp} bytes")
print(f"Ratio: {comp / orig:.1%}")

Expected output:

Small payload: 56 -> 67 bytes
Large payload: 501002 -> 12145 bytes
Ratio: 2.4%

Per-Key Compression Decision

Choose compression based on value size to avoid wasting CPU:

import zstandard as zstd
import redis
import json

r = redis.Redis(decode_responses=True)

class SmartCompressionCache:
    def __init__(self, min_compress_size=1024):
        self.min_compress_size = min_compress_size
        self.compressor = zstd.ZstdCompressor(level=3)
        self.decompressor = zstd.ZstdDecompressor()

    def set(self, key, data, ttl=3600):
        json_data = json.dumps(data)
        raw_size = len(json_data)

        if raw_size >= self.min_compress_size:
            compressed = self.compressor.compress(json_data.encode())
            r.set(f"{key}:z", compressed)
            r.expire(f"{key}:z", ttl)
            return {"size": raw_size, "compressed": len(compressed), "method": "zstd"}
        else:
            r.set(key, json_data)
            r.expire(key, ttl)
            return {"size": raw_size, "compressed": raw_size, "method": "none"}

    def get(self, key):
        compressed = r.get(f"{key}:z")
        if compressed is not None:
            data = self.decompressor.decompress(compressed)
            return json.loads(data)
        raw = r.get(key)
        if raw is not None:
            return json.loads(raw)
        return None

cache = SmartCompressionCache(min_compress_size=500)

small = {"msg": "ok"}
large = {"data": "x" * 5000}

result_small = cache.set("k:small", small)
print(f"Small: {result_small}")

result_large = cache.set("k:large", large)
print(f"Large: {result_large}")

print(f"Get small: {cache.get('k:small')}")
print(f"Get large keys: {list(cache.get('k:large').keys())}")

Expected output:

Small: {'size': 14, 'compressed': 14, 'method': 'none'}
Large: {'size': 5012, 'compressed': 127, 'method': 'zstd'}
Get small: {'msg': 'ok'}
Get large keys: ['data']

Common Mistakes

  • Compressing small values (under 256 bytes) — compression overhead makes them larger, not smaller, and wastes CPU.
  • Using maximum compression level for all data — level 19 gzip may be 100x slower than level 1 with only marginal space savings.
  • Compressing data that is already compressed (images, videos) — this adds overhead without reducing size.
  • Not monitoring compression ratio — if the ratio drops below 1.5x, disable compression for that key pattern.
  • Using different compression algorithms across application versions, causing decompression errors during rolling deployments.

Practice Questions

  1. What is the minimum value size where compression becomes beneficial?
  2. Why is LZ4 preferred over gzip for latency-sensitive cache paths?
  3. What happens when you try to compress already-compressed data?
  4. How do you handle rolling deployments when changing compression algorithms?
  5. What compression ratio indicates that compression is no longer beneficial?

Challenge

Build a cache layer that automatically selects between no compression, LZ4, and Zstandard based on value size and access frequency. Track CPU time spent on compression vs decompression. Serve a report showing memory saved per key pattern. Implement cache warming that pre-compresses data before storing it.

FAQ

What is the best compression algorithm for caching?

Zstandard (zstd) at level 3 offers the best balance of speed and ratio for most cache workloads. LZ4 is better when decompression speed is critical. gzip is best when compatibility matters.

Does compression increase cache latency?

Yes, but the decompression overhead (microseconds) is far smaller than the network round-trip saved (milliseconds). The CPU trade-off is almost always worth it for values over 1 KB.

Can I compress data inside Redis?

Redis doesn't support server-side compression. Compress in your application before writing to Redis, and decompress after reading. Some Redis client libraries offer automatic compression.

How do I handle decompression of old data during upgrades?

Include a version byte or header in the cached value. On read, check the version and decompress with the correct algorithm. This allows rolling deployments without cache flushes.

Does compression affect Redis memory fragmentation?

Compressed values are smaller and more uniform in size, which can actually reduce memory fragmentation compared to storing raw variable-length JSON strings.

Mini Project

Build a Redis cache wrapper with automatic compression. Support configurable algorithms (gzip, LZ4, Zstandard), minimum compression size, and compression level per key pattern. Expose Prometheus metrics for compression ratio, CPU time, and memory saved. Include unit tests that verify correct compression and decompression of various data types.

What's Next

Continue with Cache Serialization to learn about efficient data encoding formats like MessagePack, Protocol Buffers, and Avro for cached values. Then explore TTL Tuning to optimize expiration policies.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro