Skip to content

Commit 6ae7e12

Browse files
committed
feat: add MultiCache wrapper for multi-tier caching strategies
- Introduced `multicache` wrapper to support sophisticated caching with automatic fallback and promotion across multiple backends. - Updated documentation to include MultiCache features, benefits, and usage examples. - Added example implementation demonstrating a three-tier caching strategy using memory, disk, and Redis. - Implemented benchmarks for performance evaluation of single and multi-tier cache operations. - Created unit tests to ensure correct functionality of the MultiCache implementation and its interactions with various cache tiers.
1 parent 2fecc6e commit 6ae7e12

10 files changed

Lines changed: 1263 additions & 0 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
- ✅ Pragma: no-cache support (Section 5.4)
4242
- ✅ Cache invalidation on unsafe methods (Section 4.4)
4343
-**Multiple Backends** - Memory, Disk, Redis, LevelDB, Memcache, PostgreSQL, NATS K/V, Hazelcast
44+
-**Multi-Tier Caching** - Combine multiple backends with automatic fallback and promotion
4445
-**Security Wrapper** - Optional SHA-256 key hashing and AES-256 encryption
4546
-**Thread-Safe** - Safe for concurrent use
4647
-**Zero Dependencies** - Core package uses only Go standard library
@@ -115,6 +116,7 @@ go get github.com/sandrolain/httpcache
115116

116117
- [RFC 7234 compliance](./docs/how-it-works.md#rfc-7234-compliance-features)
117118
- [Stale-while-revalidate](./docs/advanced-features.md#stale-while-revalidate-support)
119+
- [Multi-tier caching strategies](./wrapper/multicache/README.md)
118120
- [Custom cache implementation](./docs/how-it-works.md#custom-cache-implementation)
119121
- [Multi-user considerations](./docs/security.md#private-cache-and-multi-user-applications)
120122

@@ -131,6 +133,7 @@ See the [`examples/`](./examples) directory for complete, runnable examples:
131133
- **[Hazelcast](./examples/hazelcast/)** - Enterprise distributed cache
132134
- **[FreeCache](./examples/freecache/)** - High-performance in-memory with zero GC
133135
- **[Security Best Practices](./examples/security-best-practices/)** - Secure cache with encryption and key hashing
136+
- **[Multi-Tier Cache](./examples/multicache/)** - Multi-tiered caching with automatic fallback and promotion
134137
- **[Custom Backend](./examples/custom-backend/)** - Build your own cache backend
135138
- **[Prometheus Metrics](./examples/prometheus/)** - Monitoring cache performance
136139
- **[Cache Key Headers](./examples/cachekeyheaders/)** - User-specific caching with headers

docs/advanced-features.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,37 @@ transport.ShouldCache = func(resp *http.Response) bool {
301301
⚠️ **Current Limitation**: The `Vary` response header is currently used for **validation only**, not for creating separate cache entries.
302302

303303
See [How It Works](./how-it-works.md) for details on Vary header handling.
304+
305+
## Multi-Tier Caching
306+
307+
For sophisticated caching strategies with multiple storage backends, use the [`multicache`](../wrapper/multicache/README.md) wrapper:
308+
309+
```go
310+
import "github.com/sandrolain/httpcache/wrapper/multicache"
311+
312+
// Create individual cache tiers
313+
memCache := httpcache.NewMemoryCache() // Fast, volatile
314+
diskCache := diskcache.New("/tmp/cache") // Medium, persistent
315+
redisCache, _ := redis.New("localhost:6379") // Distributed, shared
316+
317+
// Combine into multi-tier cache (order matters!)
318+
mc := multicache.New(memCache, diskCache, redisCache)
319+
320+
transport := httpcache.NewTransport(mc)
321+
client := &http.Client{Transport: transport}
322+
```
323+
324+
**Benefits:**
325+
326+
- **Performance**: Hot data in fast tiers, cold data in slow tiers
327+
- **Resilience**: Automatic fallback if faster tiers are empty
328+
- **Automatic promotion**: Popular data migrates to faster tiers
329+
- **Flexibility**: Each tier can have different eviction policies
330+
331+
**Common Patterns:**
332+
333+
- Memory → Disk → Database (performance + persistence)
334+
- Local → Redis → PostgreSQL (local + distributed)
335+
- Edge → Regional → Origin (CDN-like architecture)
336+
337+
See the [MultiCache documentation](../wrapper/multicache/README.md) for complete details and examples.

docs/backends.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,58 @@ httpcache supports multiple storage backends. Choose the one that fits your use
2323
- [`github.com/die-net/lrucache/twotier`](https://github.com/die-net/lrucache/tree/master/twotier) - Multi-tier caching (e.g., memory + disk)
2424
- [`github.com/birkelund/boltdbcache`](https://github.com/birkelund/boltdbcache) - BoltDB implementation
2525

26+
## Cache Wrappers
27+
28+
### MultiCache - Multi-Tiered Caching
29+
30+
The [`multicache`](../wrapper/multicache/README.md) wrapper allows you to combine multiple cache backends with automatic fallback and promotion:
31+
32+
```go
33+
import "github.com/sandrolain/httpcache/wrapper/multicache"
34+
35+
// Tier 1: Fast in-memory cache
36+
memCache := httpcache.NewMemoryCache()
37+
38+
// Tier 2: Medium-speed disk cache
39+
diskCache := diskcache.New("/tmp/cache")
40+
41+
// Tier 3: Persistent distributed cache
42+
redisCache, _ := redis.New("localhost:6379")
43+
44+
// Combine into multi-tier cache
45+
mc := multicache.New(
46+
memCache, // Fastest, checked first
47+
diskCache, // Medium speed
48+
redisCache, // Slowest, checked last
49+
)
50+
51+
transport := httpcache.NewTransport(mc)
52+
client := &http.Client{Transport: transport}
53+
```
54+
55+
**How it works:**
56+
57+
- **GET**: Searches tiers in order (fast → slow), promotes found data to faster tiers
58+
- **SET**: Writes to all tiers simultaneously
59+
- **DELETE**: Removes from all tiers for consistency
60+
61+
**Use cases:**
62+
63+
- Performance + Persistence: Memory → Disk → Database
64+
- Local + Distributed: Memory → Redis → PostgreSQL
65+
- CDN-like: Edge → Regional → Origin
66+
67+
See the [MultiCache documentation](../wrapper/multicache/README.md) for details.
68+
69+
### SecureCache - Encryption Wrapper
70+
71+
The [`securecache`](../wrapper/securecache/README.md) wrapper adds security features:
72+
73+
- **Key hashing**: SHA-256 hashing of cache keys (always enabled)
74+
- **Data encryption**: Optional AES-256-GCM encryption with passphrase
75+
76+
See [Security Considerations](./security.md#secure-cache-wrapper) for details.
77+
2678
## Related Projects
2779

2880
- [`github.com/moul/hcfilters`](https://github.com/moul/hcfilters) - HTTP cache middleware and filters for advanced cache control

examples/README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,63 @@ Distributed caching using Hazelcast in-memory data grid.
169169
- Enterprise applications requiring HA
170170
- When you need automatic data partitioning
171171

172+
### 9. [Multi-Tier Cache](./multicache/)
173+
174+
Combine multiple cache backends with automatic fallback and promotion.
175+
176+
**Features:**
177+
178+
- Multi-tiered caching strategy
179+
- Automatic fallback from fast to slow tiers
180+
- Automatic promotion to faster tiers
181+
- Write-through to all tiers
182+
- CDN-like architecture
183+
184+
**When to use:**
185+
186+
- Performance + Persistence requirements
187+
- Local + Distributed caching
188+
- CDN-like edge caching
189+
- Complex caching strategies with multiple storage levels
190+
- When you need both speed and resilience
191+
192+
### 10. [PostgreSQL Cache](./postgresql/)
193+
194+
Persistent distributed caching using PostgreSQL.
195+
196+
**Features:**
197+
198+
- SQL-based persistent cache
199+
- ACID compliance
200+
- Connection pool support
201+
- Distributed cache shared across instances
202+
- Works with existing PostgreSQL infrastructure
203+
204+
**When to use:**
205+
206+
- Already using PostgreSQL
207+
- Need ACID compliance for cache
208+
- SQL-based systems
209+
- When you need persistent distributed cache
210+
211+
### 11. [Security Best Practices](./security-best-practices/)
212+
213+
Secure cache implementation with encryption and key hashing.
214+
215+
**Features:**
216+
217+
- SHA-256 key hashing
218+
- AES-256-GCM encryption
219+
- Multi-user scenarios
220+
- Compliance requirements (GDPR, HIPAA)
221+
222+
**When to use:**
223+
224+
- Multi-tenant applications
225+
- Storing sensitive data
226+
- Compliance requirements
227+
- Shared cache backends
228+
172229
## Running Examples
173230

174231
Each example has its own directory with:
@@ -198,12 +255,16 @@ go run main.go
198255
| Disk ||||| Persistence needed |
199256
| LevelDB | ⚡⚡ ||| ⭐⭐ | Fast + persistent |
200257
| Redis | ⚡⚡ |* || ⭐⭐⭐ | Distributed systems |
258+
| PostgreSQL | ⚡⚡ ||| ⭐⭐⭐ | SQL infrastructure |
201259
| Memcache | ⚡⚡ ||| ⭐⭐⭐ | Distributed, no persistence |
202260
| NATS K/V | ⚡⚡ |* || ⭐⭐⭐ | NATS users |
203261
| Hazelcast | ⚡⚡⚡ |* || ⭐⭐⭐ | Enterprise, HA |
262+
| **MultiCache** | **⚡⚡⚡→⚡** | **** | **** | **⭐⭐** | **Multi-tier strategies** |
204263

205264
*Redis, NATS K/V, and Hazelcast persistence depends on configuration
206265

266+
**MultiCache**: Speed varies by tier (fastest tier = fastest speed), combines benefits of all configured backends
267+
207268
## Common Patterns
208269

209270
### Basic Setup

examples/multicache/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# MultiCache Example
2+
3+
This example demonstrates how to use the `multicache` wrapper to create a multi-tiered caching strategy with automatic fallback and promotion.
4+
5+
## Scenario
6+
7+
In this example, we set up a three-tier cache system:
8+
9+
1. **Tier 1 - Memory Cache**: Fast, small capacity, volatile
10+
2. **Tier 2 - Disk Cache**: Medium speed, larger capacity, persistent across restarts
11+
3. **Tier 3 - Redis Cache**: Network-based, largest capacity, shared across instances
12+
13+
## How It Works
14+
15+
- **GET operations**: Search from fastest to slowest tier. When found in a slower tier, automatically promote to all faster tiers.
16+
- **SET operations**: Write to all tiers simultaneously.
17+
- **DELETE operations**: Remove from all tiers to maintain consistency.
18+
19+
## Benefits
20+
21+
- **Performance**: Hot data naturally migrates to faster tiers
22+
- **Resilience**: Data persists in slower tiers even if faster caches are cleared
23+
- **Scalability**: Different tiers can have different eviction policies
24+
- **Flexibility**: Add or remove tiers based on your needs
25+
26+
## Running the Example
27+
28+
```bash
29+
# Make sure Redis is running (if you want to test with Redis tier)
30+
docker run -d -p 6379:6379 redis:alpine
31+
32+
# Run the example
33+
go run main.go
34+
```
35+
36+
## Expected Output
37+
38+
You'll see:
39+
40+
1. Initial write to all tiers
41+
2. Fast reads from tier 1 (memory)
42+
3. Automatic promotion when tier 1 is cleared
43+
4. Fallback to tier 3 when tier 1 and 2 are cleared

examples/multicache/main.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"log"
7+
"net/http"
8+
"time"
9+
10+
httpcache "github.com/sandrolain/httpcache"
11+
"github.com/sandrolain/httpcache/diskcache"
12+
rediscache "github.com/sandrolain/httpcache/redis"
13+
"github.com/sandrolain/httpcache/wrapper/multicache"
14+
15+
"github.com/gomodule/redigo/redis"
16+
)
17+
18+
const (
19+
jsonURL = "https://httpbin.org/json"
20+
)
21+
22+
func main() {
23+
fmt.Println("MultiCache Example - Three-Tier Caching Strategy")
24+
fmt.Println("=================================================")
25+
fmt.Println("")
26+
27+
// Tier 1: In-memory cache (fast, small, volatile)
28+
// 10 MB limit, evicts least recently used
29+
memoryCache := httpcache.NewMemoryCache()
30+
fmt.Println("✓ Tier 1: Memory cache initialized (fast, volatile)")
31+
32+
// Tier 2: Disk cache (medium speed, larger, persistent)
33+
// 100 MB limit, survives restarts
34+
diskCache := diskcache.New("/tmp/httpcache-multicache-example")
35+
fmt.Println("✓ Tier 2: Disk cache initialized (medium speed, persistent)")
36+
37+
// Tier 3: Redis cache (network-based, largest, shared)
38+
// Optional - only if Redis is available
39+
var mc *multicache.MultiCache
40+
redisConn, err := redis.Dial("tcp", "localhost:6379")
41+
if err != nil {
42+
fmt.Println("⚠ Tier 3: Redis not available, using only 2 tiers")
43+
mc = multicache.New(memoryCache, diskCache)
44+
} else {
45+
fmt.Println("✓ Tier 3: Redis cache initialized (network-based, shared)")
46+
redisCache := rediscache.NewWithClient(redisConn)
47+
mc = multicache.New(memoryCache, diskCache, redisCache)
48+
}
49+
50+
// Create HTTP client with multi-tier caching
51+
transport := httpcache.NewTransport(mc)
52+
client := &http.Client{
53+
Transport: transport,
54+
Timeout: 10 * time.Second,
55+
}
56+
57+
fmt.Println("\nSetup complete!")
58+
fmt.Println("")
59+
60+
// Example 1: Initial request (cache miss, writes to all tiers)
61+
fmt.Println("Example 1: Initial request")
62+
fmt.Println("---------------------------")
63+
makeRequest(client, jsonURL)
64+
fmt.Println()
65+
66+
// Example 2: Subsequent request (cache hit from tier 1 - fastest)
67+
fmt.Println("Example 2: Second request (should hit tier 1 - memory)")
68+
fmt.Println("-------------------------------------------------------")
69+
makeRequest(client, jsonURL)
70+
fmt.Println()
71+
72+
// Example 3: Simulate tier 1 eviction/restart
73+
fmt.Println("Example 3: Simulating tier 1 cache clear")
74+
fmt.Println("-----------------------------------------")
75+
fmt.Println("Clearing memory cache...")
76+
memoryCache.Delete(cacheKey(jsonURL))
77+
fmt.Println("Making request (should hit tier 2 and promote to tier 1)...")
78+
makeRequest(client, jsonURL)
79+
fmt.Println()
80+
81+
// Example 4: Make another request to verify promotion
82+
fmt.Println("Example 4: Verify promotion to tier 1")
83+
fmt.Println("--------------------------------------")
84+
fmt.Println("Making request (should hit tier 1 again after promotion)...")
85+
makeRequest(client, jsonURL)
86+
fmt.Println()
87+
88+
// Example 5: Different URL to demonstrate independent caching
89+
fmt.Println("Example 5: Different URL")
90+
fmt.Println("-------------------------")
91+
makeRequest(client, "https://httpbin.org/headers")
92+
fmt.Println()
93+
94+
fmt.Println("Examples completed!")
95+
fmt.Println("\nKey Takeaways:")
96+
fmt.Println("• First request: Cache miss → stores in all tiers")
97+
fmt.Println("• Second request: Cache hit from tier 1 (fastest)")
98+
fmt.Println("• After tier 1 clear: Hit from tier 2, auto-promoted to tier 1")
99+
fmt.Println("• Subsequent requests: Fast hits from tier 1 again")
100+
fmt.Println("• Each URL is cached independently across all tiers")
101+
}
102+
103+
func makeRequest(client *http.Client, url string) {
104+
start := time.Now()
105+
106+
resp, err := client.Get(url)
107+
if err != nil {
108+
log.Printf("Error: %v", err)
109+
return
110+
}
111+
defer resp.Body.Close()
112+
113+
// Read and discard body to trigger caching
114+
_, _ = io.Copy(io.Discard, resp.Body)
115+
116+
elapsed := time.Since(start)
117+
118+
// Check cache status from header
119+
cacheStatus := "MISS"
120+
if resp.Header.Get("X-From-Cache") == "1" {
121+
cacheStatus = "HIT"
122+
}
123+
124+
fmt.Printf("URL: %s\n", url)
125+
fmt.Printf("Status: %d\n", resp.StatusCode)
126+
fmt.Printf("Cache: %s\n", cacheStatus)
127+
fmt.Printf("Time: %v\n", elapsed)
128+
}
129+
130+
// cacheKey generates the cache key for a URL
131+
// This is a simplified version - the actual Transport uses a more sophisticated key
132+
func cacheKey(url string) string {
133+
return url
134+
}

0 commit comments

Comments
 (0)