github.com/maypok86/otter@v1.2.1/README.md (about) 1 <p align="center"> 2 <img src="./assets/logo.png" width="40%" height="auto" > 3 <h2 align="center">High performance in-memory cache</h2> 4 </p> 5 6 <p align="center"> 7 <a href="https://pkg.go.dev/github.com/maypok86/otter"><img src="https://pkg.go.dev/badge/github.com/maypok86/otter.svg" alt="Go Reference"></a> 8 <img src="https://github.com/maypok86/otter/actions/workflows/test.yml/badge.svg" /> 9 <a href="https://codecov.io/gh/maypok86/otter" > 10 <img src="https://codecov.io/gh/maypok86/otter/graph/badge.svg?token=G0PJFOR8IF"/> 11 </a> 12 <img src="https://goreportcard.com/badge/github.com/maypok86/otter" /> 13 <a href="https://github.com/avelino/awesome-go"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Go"></a> 14 </p> 15 16 ## 📖 Contents 17 18 - [Motivation](#motivation) 19 - [Related works](#related-works) 20 - [Features](#features) 21 - [Usage](#usage) 22 - [Requirements](#requirements) 23 - [Installation](#installation) 24 - [Examples](#examples) 25 - [Performance](#performance) 26 - [Throughput](#throughput) 27 - [Hit ratio](#hit-ratio) 28 - [Contribute](#contribute) 29 - [License](#license) 30 31 ## 💡 Motivation <a id="motivation" /> 32 33 I once came across the fact that none of the Go cache libraries are truly contention-free. Most of them are a map with a mutex and an eviction policy. Unfortunately, these are not able to reach the speed of caches in other languages (such as [Caffeine](https://github.com/ben-manes/caffeine)). For example, the fastest cache from Dgraph labs called [Ristretto](https://github.com/dgraph-io/ristretto), was faster than competitors by 30% at best (Otter is many times faster) but had [poor hit ratio](https://github.com/dgraph-io/ristretto/issues/336), even though its README says otherwise. This can be a problem in real-world applications, because no one wants to bump into performance of a cache library 🙂. As a result, I wanted to make the fastest, easiest-to-use cache with excellent hit ratio. 34 35 **Please leave a ⭐ as motivation if you liked the idea 😄** 36 37 ## 🗃 Related works <a id="related-works" /> 38 39 Otter is based on the following papers 40 41 - [BP-Wrapper: A Framework Making Any Replacement Algorithms (Almost) Lock Contention Free](https://www.researchgate.net/publication/220966845_BP-Wrapper_A_System_Framework_Making_Any_Replacement_Algorithms_Almost_Lock_Contention_Free) 42 - [FIFO queues are all you need for cache eviction](https://dl.acm.org/doi/10.1145/3600006.3613147) 43 - [Bucket-Based Expiration Algorithm: Improving Eviction Efficiency for In-Memory Key-Value Database](https://dl.acm.org/doi/fullHtml/10.1145/3422575.3422797) 44 - [A large scale analysis of hundreds of in-memory cache clusters at Twitter](https://www.usenix.org/system/files/osdi20-yang.pdf) 45 46 ## ✨ Features <a id="features" /> 47 48 - **Simple API**: Just set the parameters you want in the builder and enjoy 49 - **Autoconfiguration**: Otter is automatically configured based on the parallelism of your application 50 - **Generics**: You can safely use any comparable types as keys and any types as values 51 - **TTL**: Expired values will be automatically deleted from the cache 52 - **Cost-based eviction**: Otter supports eviction based on the cost of each item 53 - **Excellent throughput**: Otter is currently the fastest cache library with a huge lead over the [competition](#throughput) 54 - **Great hit ratio**: New S3-FIFO algorithm is used, which shows excellent [results](#hit-ratio) 55 56 ## 📚 Usage <a id="usage" /> 57 58 ### 📋 Requirements <a id="requirements" /> 59 60 - Go 1.19+ 61 62 ### 🛠️ Installation <a id="installation" /> 63 64 ```shell 65 go get -u github.com/maypok86/otter 66 ``` 67 68 ### ✏️ Examples <a id="examples" /> 69 70 Otter uses a builder pattern that allows you to conveniently create a cache instance with different parameters. 71 72 **Cache with const TTL** 73 ```go 74 package main 75 76 import ( 77 "fmt" 78 "time" 79 80 "github.com/maypok86/otter" 81 ) 82 83 func main() { 84 // create a cache with capacity equal to 10000 elements 85 cache, err := otter.MustBuilder[string, string](10_000). 86 CollectStats(). 87 Cost(func(key string, value string) uint32 { 88 return 1 89 }). 90 WithTTL(time.Hour). 91 Build() 92 if err != nil { 93 panic(err) 94 } 95 96 // set item with ttl (1 hour) 97 cache.Set("key", "value") 98 99 // get value from cache 100 value, ok := cache.Get("key") 101 if !ok { 102 panic("not found key") 103 } 104 fmt.Println(value) 105 106 // delete item from cache 107 cache.Delete("key") 108 109 // delete data and stop goroutines 110 cache.Close() 111 } 112 ``` 113 114 **Cache with variable TTL** 115 ```go 116 package main 117 118 import ( 119 "fmt" 120 "time" 121 122 "github.com/maypok86/otter" 123 ) 124 125 func main() { 126 // create a cache with capacity equal to 10000 elements 127 cache, err := otter.MustBuilder[string, string](10_000). 128 CollectStats(). 129 Cost(func(key string, value string) uint32 { 130 return 1 131 }). 132 WithVariableTTL(). 133 Build() 134 if err != nil { 135 panic(err) 136 } 137 138 // set item with ttl (1 hour) 139 cache.Set("key1", "value1", time.Hour) 140 // set item with ttl (1 minute) 141 cache.Set("key2", "value2", time.Minute) 142 143 // get value from cache 144 value, ok := cache.Get("key1") 145 if !ok { 146 panic("not found key") 147 } 148 fmt.Println(value) 149 150 // delete item from cache 151 cache.Delete("key1") 152 153 // delete data and stop goroutines 154 cache.Close() 155 } 156 ``` 157 158 ## 📊 Performance <a id="performance" /> 159 160 The benchmark code can be found [here](https://github.com/maypok86/benchmarks). 161 162 ### 🚀 Throughput <a id="throughput" /> 163 164 Throughput benchmarks are a Go port of the caffeine [benchmarks](https://github.com/ben-manes/caffeine/blob/master/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/GetPutBenchmark.java). 165 166 #### Read (100%) 167 168 In this [benchmark](https://github.com/maypok86/benchmarks/blob/main/throughput/bench_test.go) **8 threads** concurrently read from a cache configured with a maximum size. 169 170 <img width="60%" src="assets/results/reads=100,writes=0.png" alt="reads=100%,writes=0%" /> 171 172 #### Read (75%) / Write (25%) 173 174 In this [benchmark](https://github.com/maypok86/benchmarks/blob/main/throughput/bench_test.go) **6 threads** concurrently read from and **2 threads** write to a cache configured with a maximum size. 175 176 <img width="60%" src="assets/results/reads=75,writes=25.png" alt="reads=75%,writes=25%" /> 177 178 #### Read (50%) / Write (50%) 179 180 In this [benchmark](https://github.com/maypok86/benchmarks/blob/main/throughput/bench_test.go) **4 threads** concurrently read from and **4 threads** write to a cache configured with a maximum size. 181 182 <img width="60%" src="assets/results/reads=50,writes=50.png" alt="reads=50%,writes=50%" /> 183 184 #### Read (25%) / Write (75%) 185 186 In this [benchmark](https://github.com/maypok86/benchmarks/blob/main/throughput/bench_test.go) **2 threads** concurrently read from and **6 threads** write to a cache configured with a maximum size. 187 188 <img width="60%" src="assets/results/reads=25,writes=75.png" alt="reads=25%,writes=75%" /> 189 190 #### Write (100%) 191 192 In this [benchmark](https://github.com/maypok86/benchmarks/blob/main/throughput/bench_test.go) **8 threads** concurrently write to a cache configured with a maximum size. 193 194 <img width="60%" src="assets/results/reads=0,writes=100.png" alt="reads=0%,writes=100%" /> 195 196 Otter shows fantastic speed under all workloads except extreme write-heavy, but such a workload is very rare for caches and usually indicates that the cache has a very small hit ratio. 197 198 ### 🎯 Hit ratio <a id="hit-ratio" /> 199 200 #### Zipf 201 202 <img width="60%" src="./assets/results/zipf.png" alt="zipf" /> 203 204 #### S3 205 206 This trace is described as "disk read accesses initiated by a large commercial search engine in response to various web search requests." 207 208 <img width="60%" src="./assets/results/s3.png" alt="s3" /> 209 210 #### DS1 211 212 This trace is described as "a database server running at a commercial site running an ERP application on top of a commercial database." 213 214 <img width="60%" src="./assets/results/ds1.png" alt="ds1" /> 215 216 #### P3 217 218 The trace P3 was collected from workstations running Windows NT by using Vtrace 219 which captures disk operations through the use of device 220 filters 221 222 <img width="60%" src="./assets/results/p3.png" alt="p3" /> 223 224 #### P8 225 226 The trace P8 was collected from workstations running Windows NT by using Vtrace 227 which captures disk operations through the use of device 228 filters 229 230 <img width="60%" src="./assets/results/p8.png" alt="p8" /> 231 232 #### LOOP 233 234 This trace demonstrates a looping access pattern. 235 236 <img width="60%" src="./assets/results/loop.png" alt="loop" /> 237 238 #### OLTP 239 240 This trace is described as "references to a CODASYL database for a one hour period." 241 242 <img width="60%" src="./assets/results/oltp.png" alt="oltp" /> 243 244 In summary, we have that S3-FIFO (otter) is inferior to W-TinyLFU (theine) on lfu friendly traces (databases, search, analytics), but has a greater or equal hit ratio on web traces. 245 246 ## 👏 Contribute <a id="contribute" /> 247 248 Contributions are welcome as always, before submitting a new PR please make sure to open a new issue so community members can discuss it. 249 For more information please see [contribution guidelines](./CONTRIBUTING.md). 250 251 Additionally, you might find existing open issues which can help with improvements. 252 253 This project follows a standard [code of conduct](./CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated. 254 255 ## 📄 License <a id="license" /> 256 257 This project is Apache 2.0 licensed, as found in the [LICENSE](./LICENSE).