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).