Skip to content

Commit 1af7798

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
more tuning, benchmarking
1 parent cccdbbd commit 1af7798

File tree

12 files changed

+1087
-47
lines changed

12 files changed

+1087
-47
lines changed

Makefile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: test lint bench clean
1+
.PHONY: test lint bench benchmark clean
22

33
test:
44
go test -v -race -cover ./...
@@ -11,6 +11,14 @@ lint:
1111
bench:
1212
go test -bench=. -benchmem
1313

14+
benchmark:
15+
@echo "Running benchmarks vs other Go cache libraries..."
16+
@echo "Note: External libraries are only in benchmarks/ go.mod, not main go.mod"
17+
@cd benchmarks && go test -bench=BenchmarkSpeed -benchmem -run=^$$
18+
@echo ""
19+
@echo "Running hit rate comparison (cherrypicked workload)..."
20+
@cd benchmarks && go test -run=TestFIFOvsLRU_ScanResistance -v
21+
1422
clean:
1523
go clean -testcache
1624

README.md

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,9 @@
66
[![Go Report Card](https://goreportcard.com/badge/github.com/codeGROOVE-dev/bdcache)](https://goreportcard.com/report/github.com/codeGROOVE-dev/bdcache)
77
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
88

9-
Simple, fast, secure Go cache with [S3-FIFO eviction](https://s3fifo.com/) - better hit rates than LRU.
9+
<br clear="right">
1010

11-
## Why?
12-
13-
- **S3-FIFO Algorithm** - [Superior cache hit rates](https://s3fifo.com/) compared to LRU/LFU
14-
- **Fast** - ~20ns per operation, zero allocations
15-
- **Reliable** - Memory cache always works, even if persistence fails
16-
- **Smart Persistence** - Local files for dev, Cloud Datastore for Cloud Run
17-
- **Minimal Dependencies** - Only one (Cloud Datastore)
11+
Fast, persistent Go cache with S3-FIFO eviction - better hit rates than LRU, survives restarts with local files or Google Cloud Datastore, zero allocations.
1812

1913
## Install
2014

@@ -35,28 +29,53 @@ if err := cache.Set(ctx, "answer", 42, 0); err != nil {
3529
}
3630
val, found, err := cache.Get(ctx, "answer")
3731

38-
// With smart persistence (files for dev, Datastore for Cloud Run)
32+
// With smart persistence (local files for dev, Google Cloud Datastore for Cloud Run)
3933
cache, err := bdcache.New[string, User](ctx, bdcache.WithBestStore("myapp"))
4034
```
4135

4236
## Features
4337

4438
- **S3-FIFO eviction** - Better than LRU ([learn more](https://s3fifo.com/))
4539
- **Type safe** - Go generics
46-
- **Persistence** - Local files (gob) or Cloud Datastore (JSON)
40+
- **Persistence** - Local files (gob) or Google Cloud Datastore (JSON)
4741
- **Graceful degradation** - Cache works even if persistence fails
4842
- **Per-item TTL** - Optional expiration
4943

5044
## Performance
5145

52-
Benchmarks from MacBook Pro M4 Max:
46+
### vs Popular Go Caches
47+
48+
Benchmarks on MacBook Pro M4 Max comparing memory-only Get operations:
49+
50+
| Library | Algorithm | ns/op | Allocations | Persistence |
51+
|---------|-----------|-------|-------------|-------------|
52+
| **bdcache** | S3-FIFO | **12.95** | **0 allocs** | ✅ Auto (Local files + GCP Datastore) |
53+
| golang-lru | LRU | 13.63 | 0 allocs | ❌ None |
54+
| otter | S3-FIFO | 15.73 | 0 allocs | ⚠️ Manual (Save/Load entire cache) |
55+
| ristretto | TinyLFU | 30.43 | 0 allocs | ❌ None |
56+
57+
> ⚠️ **Benchmark Disclaimer**: These benchmarks are highly cherrypicked to show S3-FIFO's advantages. Different cache implementations excel at different workloads - LRU may outperform S3-FIFO in some scenarios, while TinyLFU shines in others. Performance varies based on access patterns, working set size, and hardware.
58+
>
59+
> **The real differentiator** is bdcache's automatic per-item persistence designed for unreliable environments like Cloud Run and Kubernetes, where shutdowns are unpredictable. See [benchmarks/](benchmarks/) for methodology.
60+
61+
**Key advantage:**
62+
- **Automatic persistence for unreliable environments** - per-item writes to local files or Google Cloud Datastore survive unexpected shutdowns (Cloud Run, Kubernetes), container restarts, and crashes without manual save/load choreography
63+
64+
**Also competitive on:**
65+
- Speed - comparable to or faster than alternatives on typical workloads
66+
- Hit rates - S3-FIFO protects hot data from scans in specific scenarios
67+
- Zero allocations - efficient for high-frequency operations
68+
69+
### Detailed Benchmarks
5370

54-
```
5571
Memory-only operations:
72+
```
5673
BenchmarkCache_Get_Hit-16 56M ops/sec 17.8 ns/op 0 B/op 0 allocs
5774
BenchmarkCache_Set-16 56M ops/sec 17.8 ns/op 0 B/op 0 allocs
75+
```
5876

5977
With file persistence enabled:
78+
```
6079
BenchmarkCache_Get_PersistMemoryHit-16 85M ops/sec 11.8 ns/op 0 B/op 0 allocs
6180
BenchmarkCache_Get_PersistDiskRead-16 73K ops/sec 13.8 µs/op 7921 B/op 178 allocs
6281
BenchmarkCache_Set_WithPersistence-16 9K ops/sec 112.3 µs/op 2383 B/op 36 allocs

benchmarks/README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# bdcache Benchmarks
2+
3+
This directory contains comparison benchmarks against popular Go cache libraries.
4+
5+
## ⚠️ Important Disclaimers
6+
7+
### Cherrypicked Benchmarks
8+
9+
These benchmarks are **intentionally cherrypicked** to demonstrate S3-FIFO's strengths:
10+
11+
- **Scan resistance workloads** - Where large scans of cold data shouldn't evict hot working set
12+
- **One-hit wonder scenarios** - Where many items are accessed once and shouldn't pollute the cache
13+
- **Memory-only Get operations** - Pure speed comparisons without I/O
14+
15+
**Different workloads favor different algorithms:**
16+
- **LRU** excels with temporal locality and simple sequential access patterns
17+
- **TinyLFU (Ristretto)** shines with frequency-based workloads and large caches
18+
- **S3-FIFO** handles mixed workloads with both hot items and one-hit wonders
19+
20+
Your mileage **will** vary based on:
21+
- Access patterns (sequential, random, zipfian, etc.)
22+
- Working set size vs cache capacity
23+
- Read/write ratio
24+
- Key/value sizes
25+
- Hardware (CPU, memory speed)
26+
27+
### The Real Differentiator: Persistence
28+
29+
**bdcache's primary advantage isn't raw speed or hit rates** - it's the automatic per-item persistence designed for unreliable cloud environments:
30+
31+
- **Cloud Run** - Instances shut down unpredictably after idle periods
32+
- **Kubernetes** - Pods can be evicted, rescheduled, or killed anytime
33+
- **Container environments** - Restarts lose all in-memory data
34+
- **Crash recovery** - Application failures don't lose cache state
35+
36+
Other libraries require manual save/load of the entire cache, which:
37+
- Doesn't work when shutdowns are unexpected
38+
- Requires coordination and timing logic
39+
- Risks data loss on crashes
40+
- Adds operational complexity
41+
42+
## Running Benchmarks
43+
44+
### Speed Comparison
45+
46+
```bash
47+
go test -bench=BenchmarkSpeed -benchmem
48+
```
49+
50+
Compares raw Get operation performance across:
51+
- bdcache (S3-FIFO)
52+
- golang-lru (LRU)
53+
- otter (S3-FIFO with manual persistence)
54+
- ristretto (TinyLFU)
55+
56+
### Hit Rate Comparison
57+
58+
```bash
59+
go test -run=TestFIFOvsLRU_ScanResistance -v
60+
```
61+
62+
Demonstrates S3-FIFO's scan resistance with a cherrypicked workload:
63+
1. Build 8K item working set (fits in 10K cache)
64+
2. Access working set once (marks as hot)
65+
3. One-time scan through 10K cold items
66+
4. Re-access working set (should hit)
67+
68+
**Results:**
69+
- S3-FIFO: 100% hit rate (working set protected in Main queue)
70+
- LRU: 0% hit rate (scan evicted entire working set)
71+
72+
This is a **best-case scenario for S3-FIFO**. Many real workloads won't see this dramatic of a difference.
73+
74+
### Additional Tests
75+
76+
```bash
77+
# S3-FIFO correctness tests
78+
go test -run=TestS3FIFO -v
79+
80+
# Detailed behavior demonstrations
81+
go test -run=TestS3FIFODetailed -v
82+
83+
# Hit rate comparisons (mixed workloads)
84+
go test -run=TestHitRateComparison -v
85+
```
86+
87+
## Benchmark Files
88+
89+
- `benchmark_comparison_test.go` - Speed benchmarks across libraries
90+
- `hitrate_comparison_test.go` - Hit rate workload generators
91+
- `fifo_vs_lru_test.go` - S3-FIFO vs LRU scan resistance demo
92+
- `s3fifo_debug_test.go` - Queue behavior validation
93+
- `s3fifo_detailed_test.go` - Detailed eviction order verification
94+
95+
## Interpreting Results
96+
97+
When evaluating caches for your use case:
98+
99+
1. **Profile your actual workload** - Synthetic benchmarks don't capture real-world complexity
100+
2. **Measure what matters** - Hit rate, latency, throughput, memory usage
101+
3. **Consider operational needs** - Persistence, observability, graceful degradation
102+
4. **Test with your data** - Key/value sizes and access patterns vary wildly
103+
5. **Benchmark in production-like environments** - Hardware and load matter
104+
105+
**Don't choose a cache based solely on these benchmarks.** Choose based on your specific requirements, with special attention to operational characteristics like persistence if you're running in unreliable cloud environments.

0 commit comments

Comments
 (0)