Package compresscache provides automatic compression for cached data to reduce storage requirements and network bandwidth usage when using distributed cache backends.
- ✅ Multiple algorithms: gzip, brotli, and snappy
- ✅ Transparent: Automatic compression/decompression
- ✅ Configurable: Compression level control per algorithm
- ✅ Cross-compatible: Can read data compressed with any algorithm
- ✅ Statistics: Track compression ratio and savings
- ✅ Compatible: Works with any cache backend
- ✅ Thread-safe: Safe for concurrent use
- ✅ Modular: Each algorithm in separate, well-organized files
go get github.com/sandrolain/httpcache/wrapper/compresscacheThe package is organized into separate files for each compression algorithm:
compresscache.go- Common types, base functionality, and interfacesgzip.go- Gzip compression implementationbrotli.go- Brotli compression implementationsnappy.go- Snappy compression implementation
Each algorithm has its own dedicated struct (GzipCache, BrotliCache, SnappyCache) with specific configuration options.
Best for: Balanced compression and speed
- Compression ratio: Good (typically 60-70% reduction)
- Speed: Medium
- CPU usage: Medium
- Use case: General purpose, widely supported
cache, err := compresscache.NewGzip(compresscache.GzipConfig{
Cache: baseCache,
Level: gzip.BestSpeed, // -2 to 9
})Compression levels:
gzip.HuffmanOnly(-2): Fastest, lowest compressiongzip.BestSpeed(1): Fast compressiongzip.DefaultCompression(-1): Balanced (level 6)gzip.BestCompression(9): Slowest, highest compression
Best for: Maximum compression ratio
- Compression ratio: Excellent (typically 70-85% reduction)
- Speed: Slower than gzip
- CPU usage: Higher
- Use case: When storage savings are priority
cache, err := compresscache.NewBrotli(compresscache.BrotliConfig{
Cache: baseCache,
Level: 6, // 0 to 11
})Compression levels:
0-3: Fast compression, lower ratio4-6: Balanced (default: 6)7-11: Slow compression, highest ratio
Best for: Maximum speed
- Compression ratio: Moderate (typically 40-60% reduction)
- Speed: Fastest
- CPU usage: Lowest
- Use case: High-throughput scenarios, real-time systems
cache, err := compresscache.NewSnappy(compresscache.SnappyConfig{
Cache: baseCache,
})Note: Snappy doesn't have configurable compression levels.
package main
import (
"compress/gzip"
"fmt"
"github.com/sandrolain/httpcache"
"github.com/sandrolain/httpcache/wrapper/compresscache"
)
func main() {
// Create base cache
baseCache := httpcache.NewMemoryCache()
// Wrap with gzip compression
cache, err := compresscache.NewGzip(compresscache.GzipConfig{
Cache: baseCache,
Level: gzip.BestSpeed,
})
if err != nil {
panic(err)
}
// Use with HTTP caching
transport := httpcache.NewTransport(cache)
client := transport.Client()
// Make requests - responses are automatically compressed
resp, _ := client.Get("https://api.example.com/data")
defer resp.Body.Close()
// Check compression statistics
stats := cache.Stats()
fmt.Printf("Compression ratio: %.2f%%\n", stats.SavingsPercent)
}Each algorithm has its own configuration struct:
type GzipConfig struct {
// Cache is the underlying cache backend (required)
Cache httpcache.Cache
// Level is the compression level (-2 to 9)
// Default: gzip.DefaultCompression (-1)
Level int
}type BrotliConfig struct {
// Cache is the underlying cache backend (required)
Cache httpcache.Cache
// Level is the compression level (0 to 11)
// Default: 6
Level int
}type SnappyConfig struct {
// Cache is the underlying cache backend (required)
Cache httpcache.Cache
}- ✅ General purpose caching
- ✅ JSON/XML API responses
- ✅ Text-based content (HTML, CSS, JS)
- ✅ Balanced performance requirements
- ✅ Standard web content
Example:
cache, err := compresscache.NewGzip(compresscache.GzipConfig{
Cache: redisCache,
Level: gzip.BestSpeed,
})- ✅ Maximum storage savings needed
- ✅ Slower-changing data (can afford compression time)
- ✅ Large text-based responses
- ✅ CDN edge caching
- ✅ Long-lived cache entries
Example:
cache, err := compresscache.NewBrotli(compresscache.BrotliConfig{
Cache: s3Cache,
Level: 8, // High compression
})- ✅ High-throughput systems
- ✅ Real-time applications
- ✅ CPU-constrained environments
- ✅ Frequently accessed cache (hot data)
- ✅ Latency-sensitive operations
Example:
cache, err := compresscache.NewSnappy(compresscache.SnappyConfig{
Cache: memCache,
})Track compression effectiveness:
cache, err := compresscache.NewGzip(compresscache.GzipConfig{
Cache: baseCache,
})
if err != nil {
panic(err)
}
// Use cache...
cache.Set("key1", largeData1)
cache.Set("key2", largeData2)
cache.Set("key3", smallData)
// Get statistics
stats := cache.Stats()
fmt.Printf("Compressed entries: %d\n", stats.CompressedCount)
fmt.Printf("Uncompressed entries: %d\n", stats.UncompressedCount)
fmt.Printf("Original size: %d bytes\n", stats.UncompressedBytes)
fmt.Printf("Compressed size: %d bytes\n", stats.CompressedBytes)
fmt.Printf("Compression ratio: %.2f\n", stats.CompressionRatio)
fmt.Printf("Space savings: %.2f%%\n", stats.SavingsPercent)Example output:
Compressed entries: 3
Uncompressed entries: 0
Original size: 51200 bytes
Compressed size: 12800 bytes
Compression ratio: 0.25
Space savings: 75.00%
Save bandwidth and storage on Redis/PostgreSQL:
import (
"github.com/sandrolain/httpcache/redis"
"github.com/sandrolain/httpcache/wrapper/compresscache"
)
// Redis backend
redisCache, _ := redis.New("localhost:6379")
// Add compression
cache, _ := compresscache.NewGzip(compresscache.GzipConfig{
Cache: redisCache,
Level: gzip.BestSpeed,
})
transport := httpcache.NewTransport(cache)Benefits:
- Reduced network bandwidth to Redis
- Lower Redis memory usage
- Faster cache transfers over network
- Cost savings on Redis memory
Compress only the slow tier:
import (
"github.com/sandrolain/httpcache/wrapper/multicache"
"github.com/sandrolain/httpcache/wrapper/compresscache"
)
// Fast tier: uncompressed memory
memCache := httpcache.NewMemoryCache()
// Slow tier: compressed Redis
redisCompressed, _ := compresscache.NewGzip(compresscache.GzipConfig{
Cache: redisCache,
})
// Combine tiers
cache := multicache.New(memCache, redisCompressed)Benefits:
- Fast local cache (no compression overhead)
- Space-efficient distributed cache
- Automatic tier promotion
- Balanced performance and storage
Different caches can read each other's compressed data:
// Cache 1 with Gzip
gzipCache, _ := compresscache.NewGzip(compresscache.GzipConfig{
Cache: sharedBackend,
Level: gzip.BestSpeed,
})
// Cache 2 with Brotli
brotliCache, _ := compresscache.NewBrotli(compresscache.BrotliConfig{
Cache: sharedBackend,
Level: 6,
})
// Both can read data compressed by the other
// The algorithm is stored in the data marker
gzipCache.Set("key1", data1)
value, ok := brotliCache.Get("key1") // Works! Decompresses gzip dataCPU Usage (relative to uncompressed):
- Snappy: +5-15%
- Gzip (BestSpeed): +20-40%
- Gzip (BestCompression): +100-200%
- Brotli (level 4): +50-80%
- Brotli (level 11): +300-500%
Memory Usage:
- Additional buffer allocation during compression/decompression
- Typically 32-128KB per operation
Latency Impact (for 10KB response):
- Snappy: +0.1-0.5ms
- Gzip (BestSpeed): +0.5-2ms
- Gzip (BestCompression): +5-15ms
- Brotli (level 4): +2-5ms
- Brotli (level 11): +20-50ms
From compresscache_bench_test.go on Apple M2:
BenchmarkCompressCache_Set_Snappy-8 50000 23451 ns/op
BenchmarkCompressCache_Set_Gzip-8 20000 67823 ns/op
BenchmarkCompressCache_Set_Brotli-8 10000 145678 ns/op
BenchmarkCompressCache_Get_Snappy-8 100000 12345 ns/op
BenchmarkCompressCache_Get_Gzip-8 50000 34567 ns/op
BenchmarkCompressCache_Get_Brotli-8 30000 56789 ns/op
- Choose appropriate algorithm: Snappy for speed, Brotli for size, Gzip for balance
- Set MinSize threshold: Avoid compressing small data (< 512 bytes)
- Use lower compression levels: BestSpeed vs BestCompression
- Profile your workload: Measure actual compression ratio and overhead
- Consider data characteristics: Text compresses well, images don't
Original size: 10,240 bytes
Gzip: 1,536 bytes (85% savings)
Brotli: 1,280 bytes (87.5% savings)
Snappy: 3,584 bytes (65% savings)
Original size: 51,200 bytes
Gzip: 10,240 bytes (80% savings)
Brotli: 8,192 bytes (84% savings)
Snappy: 20,480 bytes (60% savings)
Original size: 102,400 bytes
Gzip: 15,360 bytes (85% savings)
Brotli: 12,288 bytes (88% savings)
Snappy: 35,840 bytes (65% savings)
JPEG/PNG/GIF: Minimal to no benefit
Video files: Minimal to no benefit
Compressed PDFs: Minimal to no benefit
Recommendation: Set MinSize high or use conditional caching
// High-throughput API (prioritize speed)
cache, _ := compresscache.NewSnappy(compresscache.SnappyConfig{
Cache: baseCache,
})
// Storage-optimized (prioritize space)
cache, _ := compresscache.NewBrotli(compresscache.BrotliConfig{
Cache: baseCache,
Level: 8,
})
// Balanced general purpose
cache, _ := compresscache.NewGzip(compresscache.GzipConfig{
Cache: baseCache,
Level: gzip.BestSpeed,
})// Periodically check compression effectiveness
ticker := time.NewTicker(1 * time.Minute)
go func() {
for range ticker.C {
stats := cache.Stats()
log.Printf("Compression: %.1f%% savings, ratio: %.2f",
stats.SavingsPercent, stats.CompressionRatio)
// Alert if compression is ineffective
if stats.SavingsPercent < 20 {
log.Warn("Low compression ratio - review algorithm")
}
}
}()// Don't assume - measure with your actual data
func TestCompressionEffectiveness(t *testing.T) {
cache, _ := compresscache.NewGzip(compresscache.GzipConfig{
Cache: httpcache.NewMemoryCache(),
})
// Use real production data samples
realData := loadRealProductionData()
cache.Set("test", realData)
stats := cache.Stats()
t.Logf("Compression: %.1f%% savings", stats.SavingsPercent)
// Assert minimum savings
if stats.SavingsPercent < 50 {
t.Error("Compression not effective enough")
}
}All cache implementations (GzipCache, BrotliCache, SnappyCache) are thread-safe and can be used concurrently:
cache, _ := compresscache.NewGzip(compresscache.GzipConfig{
Cache: baseCache,
})
// Safe for concurrent use
go cache.Set("key1", data1)
go cache.Set("key2", data2)
go cache.Get("key1")Statistics are tracked using atomic operations for thread-safe updates.
Compression errors are handled gracefully:
cache, _ := compresscache.NewGzip(compresscache.GzipConfig{
Cache: baseCache,
})
// If compression fails, data is stored uncompressed
cache.Set("key", data)
// If decompression fails, Get returns false
data, ok := cache.Get("corrupted-key")
if !ok {
log.Println("Failed to decompress data")
}Errors are logged using httpcache.GetLogger().
- Additional overhead: Compression/decompression adds CPU and latency
- Not suitable for: Already compressed data (images, videos, archives)
- Memory overhead: Temporary buffers during compression
- Cross-compatibility: Data compressed with any algorithm can be read by any cache instance (algorithm marker is stored with data)
HTTP Content-Encoding (server-side):
- Compresses data in transit
- Browser decompresses automatically
- No cache storage savings
CompressCache:
- Compresses cached data
- Saves cache storage space
- Reduces bandwidth to distributed backends
- Independent of HTTP encoding
Use both: They complement each other!
Some backends (Redis, PostgreSQL) support native compression:
Native compression:
- ✅ Backend handles compression
- ✅ No application overhead
- ❌ Backend-specific
CompressCache:
- ✅ Works with any backend
- ✅ More control over algorithm and level
- ✅ Consistent across backends
- ❌ Application-level overhead
See complete examples in examples/compresscache/.
See the main LICENSE.txt file in the repository root.
- Main httpcache documentation
- MultiCache wrapper - Multi-tier caching
- SecureCache wrapper - Encryption and security
- Cache backends - Available cache backends