|
| 1 | +# 🚀 EasyCache - A Fast In-Memory Cache for Go |
| 2 | + |
| 3 | +[](https://golang.org/) |
| 4 | +[](LICENSE) |
| 5 | +[](https://github.com/hugocarreira/easycache/actions) |
| 6 | +[](https://github.com/hugocarreira/easycache/actions) |
| 7 | +[](https://goreportcard.com/report/github.com/hugocarreira/easycache) |
| 8 | +[](https://pkg.go.dev/github.com/hugocarreira/easycache) |
| 9 | + |
| 10 | + |
| 11 | +EasyCache is a **high-performance, in-memory caching library** for Go, supporting multiple **eviction policies** like **FIFO, LRU, LFU**, and **TTL-based expiration**. It is **thread-safe**, lightweight, and provides **built-in metrics**. |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## ⚡ Installation |
| 16 | + |
| 17 | +To install EasyCache, run: |
| 18 | + |
| 19 | +```sh |
| 20 | +go get github.com/hugocarreira/easycache |
| 21 | +``` |
| 22 | + |
| 23 | +## ❓ Why EasyCache? |
| 24 | + |
| 25 | +There are several caching solutions available, so why choose EasyCache? |
| 26 | + |
| 27 | +✅ **Lightweight** – Minimal dependencies and optimized for performance. |
| 28 | +✅ **Multiple eviction policies** – Supports FIFO, LRU, LFU, and TTL-based caching. |
| 29 | +✅ **Thread-safe** – Uses `sync.RWMutex` to handle concurrent access. |
| 30 | +✅ **Memory-efficient** – Allows memory usage limits and automatic cleanup. |
| 31 | +✅ **Built-in metrics** – Track hits, misses, and evictions for performance insights. |
| 32 | + |
| 33 | + |
| 34 | +## 🛠️ Basic Usage |
| 35 | + |
| 36 | +Here's how to use EasyCache in your Go project: |
| 37 | + |
| 38 | +```go |
| 39 | +package main |
| 40 | + |
| 41 | +import ( |
| 42 | + "fmt" |
| 43 | + "time" |
| 44 | + |
| 45 | + "github.com/hugocarreira/easycache/cache" |
| 46 | +) |
| 47 | + |
| 48 | +func main() { |
| 49 | + // Create a new cache with Basic eviction policy |
| 50 | + c := cache.New(&cache.Config{ |
| 51 | + MaxSize: 5, |
| 52 | + TTL: 30 * time.Second, |
| 53 | + EvictionPolicy: cache.Basic, |
| 54 | + MetricsEnabled: false, |
| 55 | + MemoryLimits: 0, |
| 56 | + MemoryCheckInterval: 0, |
| 57 | + CleanupInterval: 10 * time.Second, |
| 58 | + }) |
| 59 | + |
| 60 | + // Add items to the cache |
| 61 | + c.Set("A", "Item A") |
| 62 | + c.Set("B", "Item B") |
| 63 | + |
| 64 | + // Retrieve an item |
| 65 | + value, found := c.Get("A") |
| 66 | + if found { |
| 67 | + fmt.Println("Cache hit:", value) // Output: Cache hit: Item A |
| 68 | + } else { |
| 69 | + fmt.Println("Cache miss") |
| 70 | + } |
| 71 | + |
| 72 | + // Check if a key exists |
| 73 | + fmt.Println("Has key 'B'?", c.Has("B")) // Output: true |
| 74 | + |
| 75 | + // Delete an item |
| 76 | + c.Delete("A") |
| 77 | + |
| 78 | + // Check cache length |
| 79 | + fmt.Println("Cache size:", c.Len()) // Output: 1 |
| 80 | +} |
| 81 | + |
| 82 | +``` |
| 83 | + |
| 84 | +## ⚙️ Cache Policies |
| 85 | + |
| 86 | +EasyCache supports **four different eviction policies**: |
| 87 | + |
| 88 | +| Policy | Description | |
| 89 | +|---------|------------| |
| 90 | +| `Basic` | A simple TTL-based cache with no eviction policy. Items are removed only when they expire. | |
| 91 | +| `FIFO` | First-In, First-Out. The oldest item is removed when the cache is full. | |
| 92 | +| `LRU` | Least Recently Used. The least recently accessed item is removed when the cache is full. | |
| 93 | +| `LFU` | Least Frequently Used. The item with the fewest accesses is removed when the cache is full. | |
| 94 | + |
| 95 | +### 🛠️ Basic Cache (TTL-based) |
| 96 | + |
| 97 | +The **Basic** cache is a simple TTL-based cache with no eviction policy. |
| 98 | +Items are **only removed when they expire** based on their **TTL (Time-To-Live)**. |
| 99 | + |
| 100 | +#### **Example:** |
| 101 | +```go |
| 102 | +package main |
| 103 | + |
| 104 | +import ( |
| 105 | + "time" |
| 106 | + "github.com/hugocarreira/easycache/cache" |
| 107 | +) |
| 108 | + |
| 109 | +func main() { |
| 110 | + // Create a Basic cache with TTL-based expiration |
| 111 | + c := cache.New(&cache.Config{ |
| 112 | + EvictionPolicy: cache.Basic, |
| 113 | + TTL: 30 * time.Second, // Items expire after 30 seconds |
| 114 | + CleanupInterval: 10 * time.Second, // Cleanup runs every 10 seconds |
| 115 | + }) |
| 116 | + |
| 117 | + // Add item to the cache |
| 118 | + c.Set("session1", "user123") |
| 119 | + |
| 120 | + // Get item from cache |
| 121 | + value, found := c.Set("session1") |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +### 🔄 FIFO Cache (First-In, First-Out) |
| 126 | + |
| 127 | +The **FIFO (First-In, First-Out)** cache evicts the **oldest item** when the cache reaches its maximum size. |
| 128 | +This policy ensures that the **first item added is the first one to be removed**, regardless of access frequency. |
| 129 | + |
| 130 | +#### **Example:** |
| 131 | +```go |
| 132 | +package main |
| 133 | + |
| 134 | +import ( |
| 135 | + "github.com/hugocarreira/easycache/cache" |
| 136 | +) |
| 137 | + |
| 138 | +func main() { |
| 139 | + // Create a FIFO cache with a maximum of 2 items |
| 140 | + c := cache.New(&cache.Config{ |
| 141 | + EvictionPolicy: cache.FIFO, |
| 142 | + MaxSize: 2, // Cache holds up to 2 items |
| 143 | + }) |
| 144 | + |
| 145 | + // Add items to the cache |
| 146 | + c.Set("A", "Item A") |
| 147 | + |
| 148 | + // Adding a second item causes "A" to be evicted |
| 149 | + c.Set("D", "Item D") |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +### 🔄 LRU Cache (Least Recently Used) |
| 154 | + |
| 155 | +The **LRU (Least Recently Used)** cache removes the **least recently accessed item** when the cache reaches its maximum size. |
| 156 | +This policy ensures that frequently used items stay in the cache while older, less-used items are evicted. |
| 157 | + |
| 158 | +#### **Example:** |
| 159 | +```go |
| 160 | +package main |
| 161 | + |
| 162 | +import ( |
| 163 | + "github.com/hugocarreira/easycache/cache" |
| 164 | +) |
| 165 | + |
| 166 | +func main() { |
| 167 | + // Create an LRU cache with a maximum of 3 items |
| 168 | + c := cache.New(&cache.Config{ |
| 169 | + EvictionPolicy: cache.LRU, |
| 170 | + MaxSize: 3, // Cache holds up to 3 items |
| 171 | + }) |
| 172 | + |
| 173 | + // Add items to the cache |
| 174 | + c.Set("A", "Item A") |
| 175 | + c.Set("B", "Item B") |
| 176 | + c.Set("C", "Item C") |
| 177 | + |
| 178 | + // Access "A" to mark it as recently used |
| 179 | + c.Get("A") |
| 180 | + |
| 181 | + // Adding a fourth item causes "B" to be evicted (least recently used) |
| 182 | + c.Set("D", "Item D") |
| 183 | +} |
| 184 | +``` |
| 185 | + |
| 186 | +### 🔄 LFU Cache (Least Frequently Used) |
| 187 | + |
| 188 | +The **LFU (Least Frequently Used)** cache removes the **least accessed item** when the cache reaches its maximum size. |
| 189 | +This policy ensures that frequently accessed items stay in the cache, while items with the lowest usage count are evicted first. |
| 190 | + |
| 191 | +#### **Example:** |
| 192 | +```go |
| 193 | +package main |
| 194 | + |
| 195 | +import ( |
| 196 | + "github.com/hugocarreira/easycache/cache" |
| 197 | +) |
| 198 | + |
| 199 | +func main() { |
| 200 | + // Create an LFU cache with a maximum of 3 items |
| 201 | + c := cache.New(&cache.Config{ |
| 202 | + EvictionPolicy: cache.LFU, |
| 203 | + MaxSize: 3, // Cache holds up to 3 items |
| 204 | + }) |
| 205 | + |
| 206 | + // Add items to the cache |
| 207 | + c.Set("A", "Item A") |
| 208 | + c.Set("B", "Item B") |
| 209 | + c.Set("C", "Item C") |
| 210 | + |
| 211 | + // Access "A" twice and "B" once |
| 212 | + c.Get("A") |
| 213 | + c.Get("A") |
| 214 | + c.Get("B") |
| 215 | + |
| 216 | + // Adding a fourth item causes "C" to be evicted (least frequently used) |
| 217 | + c.Set("D", "Item D") |
| 218 | +} |
| 219 | +``` |
| 220 | + |
| 221 | + |
| 222 | +## 🧹 Memory Management & Cleanup |
| 223 | + |
| 224 | +EasyCache provides **automatic memory cleanup** to remove expired items and prevent excessive memory usage. |
| 225 | +This is useful for **TTL-based caches** (`Basic`) and for scenarios where memory constraints are important. |
| 226 | + |
| 227 | +--- |
| 228 | + |
| 229 | +### 🔄 Expired Items Cleanup (TTL-based) |
| 230 | +For **Basic (TTL-based) caches**, items are **removed automatically** when they expire. |
| 231 | +The **`CleanupInterval`** parameter defines how often expired items are removed. |
| 232 | + |
| 233 | +#### **Example:** |
| 234 | +```go |
| 235 | +c := cache.New(&cache.Config{ |
| 236 | + EvictionPolicy: cache.Basic, |
| 237 | + TTL: 30 * time.Second, // Items expire after 30s |
| 238 | + CleanupInterval: 10 * time.Second, // Cleanup runs every 10s |
| 239 | +}) |
| 240 | +``` |
| 241 | + |
| 242 | +### 🔄 Memory Usage Monitoring & Cleanup |
| 243 | +EasyCache allows automatic memory checks to prevent the cache from exceeding a defined memory limit. |
| 244 | + |
| 245 | +The MemoryLimits parameter sets a max memory usage (in bytes), |
| 246 | +and the MemoryCheckInterval defines how often memory is checked. |
| 247 | + |
| 248 | +#### **Example:** |
| 249 | +```go |
| 250 | +c := cache.New(&cache.Config{ |
| 251 | + EvictionPolicy: cache.Basic, |
| 252 | + MemoryLimits: 100 * 1024 * 1024, // 100 MB limit |
| 253 | + MemoryCheckInterval: 30 * time.Second, // Check memory every 30s |
| 254 | +}) |
| 255 | +``` |
| 256 | + |
| 257 | + |
| 258 | +## 🚀 Performance Benchmarks |
| 259 | + |
| 260 | +We ran performance benchmarks on EasyCache to measure the efficiency of `Set()`, `Get()`, `Delete()`, and eviction policies (`FIFO`, `LRU`, `LFU`). |
| 261 | + |
| 262 | +| Benchmark | Iterations | Time per operation | Memory used | Allocations per op | |
| 263 | +|--------------------------|------------|--------------------|-------------|--------------------| |
| 264 | +| **`BenchmarkCacheSet`** | 2,936,356 | **408.4 ns/op** | **122 B/op** | **5 allocs/op** | |
| 265 | +| **`BenchmarkCacheGet`** | 39,143,538 | **30.79 ns/op** | **0 B/op** | **0 allocs/op** | |
| 266 | +| **`BenchmarkCacheDelete`**| 5,376,940 | **223.3 ns/op** | **96 B/op** | **3 allocs/op** | |
| 267 | +| **`BenchmarkFIFOEviction`** | 3,065,480 | **391.7 ns/op** | **122 B/op** | **5 allocs/op** | |
| 268 | +| **`BenchmarkLRUEviction`** | 3,045,759 | **402.1 ns/op** | **122 B/op** | **5 allocs/op** | |
| 269 | +| **`BenchmarkLFUEviction`** | 2,916,150 | **394.3 ns/op** | **88 B/op** | **4 allocs/op** | |
| 270 | + |
| 271 | +**Tested on:** |
| 272 | +- **Go Version:** 1.23.5 |
| 273 | +- **Cache Configuration:** `MaxSize = 10,000`, `TTL = 60s` |
| 274 | + |
| 275 | +--- |
| 276 | + |
| 277 | +### 🛠️ Running the Benchmarks |
| 278 | + |
| 279 | +To run the benchmarks yourself, use: |
| 280 | + |
| 281 | +```sh |
| 282 | +go test -bench=. -benchmem ./tests |
| 283 | +``` |
| 284 | + |
| 285 | +## 💡 Contributing |
| 286 | + |
| 287 | +Please see [`CONTRIBUTING`](CONTRIBUTING.md)for details on submitting patches and the contribution workflow. |
| 288 | + |
0 commit comments