https://github.com/juliaqiuxy/wfcache
Multi-layered cache with waterfall hit propagation and built-in storage adapters for DynamoDB, Redis, BigCache, GoLRU etc.
https://github.com/juliaqiuxy/wfcache
bigcache cache dynamodb go redis
Last synced: 5 months ago
JSON representation
Multi-layered cache with waterfall hit propagation and built-in storage adapters for DynamoDB, Redis, BigCache, GoLRU etc.
- Host: GitHub
- URL: https://github.com/juliaqiuxy/wfcache
- Owner: juliaqiuxy
- License: mit
- Created: 2021-06-01T00:25:38.000Z (about 5 years ago)
- Default Branch: main
- Last Pushed: 2021-11-22T18:19:24.000Z (over 4 years ago)
- Last Synced: 2024-06-20T12:07:09.336Z (about 2 years ago)
- Topics: bigcache, cache, dynamodb, go, redis
- Language: Go
- Homepage:
- Size: 78.1 KB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Waterfall Cache
[](https://godoc.org/github.com/juliaqiuxy/wfcache) [](https://github.com/juliaqiuxy/wfcache/actions/workflows/ci.yml) [](https://goreportcard.com/report/github.com/juliaqiuxy/wfcache) [](https://github.com/juliaqiuxy/wfcache/blob/master/LICENSE.md)
wfcache is a multi-layered cache with waterfall hit propagation and built-in storage adapters for DynamoDB, Redis, BigCache (in-memory), and GoLRU (in-memory)
wfcache is effective for read-heavy workloads and it can be used both as a side-cache or a read-through/write-through cache.
This libary is used in production by an application that receives 8 Million hits a month.
## Built-in Storage Adapters
| Package | Description | Eviction strategy
| --- | --- | --- |
| [DynamoDB](https://docs.aws.amazon.com/sdk-for-go/api/service/dynamodb) | DynamoDB | [TTL](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html) |
| [Redis](https://github.com/go-redis/redis) | Redis | TTL/[LRU](https://redis.io/topics/lru-cache) |
| [BigCache](https://github.com/allegro/bigcache) | Performant on heap storage with [minimal GC](https://github.com/allegro/bigcache#gc-pause-time) | TTL ([enforced on add](https://github.com/allegro/bigcache/issues/123#issuecomment-468902638)) |
| [GoLRU](https://github.com/manucorporat/golru) | In-memory storage with approximated LRU similar to Redis | TTL/LRU |
| [Basic](basic/basic.go) | Basic in-memory storage (not recommended) | TTL (enforced on get) |
## Installation
To retrieve wfcache, run:
```sh
$ go get github.com/juliaqiuxy/wfcache
```
### Usage
```go
import (
"github.com/juliaqiuxy/wfcache"
basic "github.com/juliaqiuxy/wfcache/basic"
bigcache "github.com/juliaqiuxy/wfcache/bigcache"
dynamodb "github.com/juliaqiuxy/wfcache/dynamodb"
)
c, err := wfcache.New(
basic.Create(5 * time.Minute),
bigcache.Create(2 * time.Hour),
dynamodb.Create(dynamodbClient, "my-cache-table", 24 * time.Hour),
)
items, err := c.BatchGet(keys)
if err == wfcache.ErrPartiallyFulfilled {
fmt.Println("Somethings are missing")
}
```
## Usage with hooks
You can configure wfcache to notify you when each storage operation starts and finishes. This is useful when you want to do performance logging, tracing etc.
```go
import (
"context"
"github.com/juliaqiuxy/wfcache"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
basic "github.com/juliaqiuxy/wfcache/basic"
)
func onStartStorageOp(ctx context.Context, opName string) interface{} {
span, _ := tracer.StartSpanFromContext(ctx, opName)
return span
}
func onFinishStorageOp(span interface{}) {
span.(ddtrace.Span).Finish()
}
wfcache.NewWithHooks(
onStartStorageOp,
onFinishStorageOp,
basic.Create(5 * time.Minute),
)
```
## How it works
The following steps outline how reads from wfcache work:
- When getting a value, wfcache tries to read it from the first storage layer (e.g. BigCache).
- If the storage layer is not populated with the requested key-value pair (cache miss), transparent to the application, wfcache notes the missing key and moves on to the next layer. This continues until all configured storage options are exhausted.
- When there is a cache hit, wfcache then primes each storage layer with a previously reported cache miss to make the data available for any subsequent reads.
- wfcache returns the key-value pair back to the application
If you want to use wfcache as read-through cache, you can implement a [custom adapter](#implementing-custom-adapters) for your source database and configure it as the last storage layer. In this setup, a cache miss only ever happens in intermediate storage layers (which are then primed as your source storage resolves values) but wfcache would always yield data.
When mutating wfcache, key-value pairs are written and removed from all storage layers. To mutate a specific storage layer in isolation, you can ask wfcache to provide you with a reference to the underlying slice of storages by calling its `Storages()` method.
### Cache eviction
wfcache leaves it up to each storage layer to implement their eviction strategy. Built-in adapters use a combination of Time-to-Live (TTL) and Least Recently Used (LRU) algorithm to decide which items to evict.
Also note that the built-in Basic storage is not meant for production use as the TTL enforcement only happens if and when a "stale" item is requested form the storage layer.
## Implementing Custom Adapters
For use cases where:
- you require a stroge adapter which is not [included](#built-in-storage-adapters) in wfcache, or
- you want to use wfcache as a read-through/write-through cache
it is trivial to extend wfcache by implementing the following adapter interface:
```go
type Storage interface {
Get(ctx context.Context, key string) *CacheItem
BatchGet(ctx context.Context, keys []string) []*CacheItem
Set(ctx context.Context, key string, value []byte) error
BatchSet(ctx context.Context, pairs map[string][]byte) error
Del(ctx context.Context, key string) error
BatchDel(ctx context.Context, keys []string) error
}
```