iwanbk/bcache

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# bcache

[![godoc](https://godoc.org/github.com/iwanbk/bcache?status.svg)](http://godoc.org/github.com/iwanbk/bcache)
[![Build Status](https://travis-ci.org/iwanbk/bcache.svg?branch=master)](https://travis-ci.org/iwanbk/bcache)
[![codecov](https://codecov.io/gh/iwanbk/bcache/branch/master/graph/badge.svg)](https://codecov.io/gh/iwanbk/bcache)
[![Go Report Card](https://goreportcard.com/badge/github.com/iwanbk/bcache)](https://goreportcard.com/report/github.com/iwanbk/bcache)
[![Maintainability](https://api.codeclimate.com/v1/badges/0535095fdd215f2e22ad/maintainability)](https://codeclimate.com/github/iwanbk/bcache/maintainability)

A Go Library to create distributed in-memory cache inside your app.

## Features

- LRU cache with configurable maximum keys
- Eventual Consistency synchronization between peers
- Data are replicated to all nodes
- cache filling mechanism. When the cache of the given key is not exist, bcache coordinates cache fills such that only one call populates the cache to avoid thundering herd or [cache stampede](https://en.wikipedia.org/wiki/Cache_stampede)

## Why using it

- if extra network hops needed by external caches like `redis` or `memcached` is not acceptable for you
- you only need cache with simple `Set`, `Get`, and `Delete` operation
- you have enough RAM to hold the cache data

## How it Works

1. Nodes find each other using [Gossip Protocol](https://en.wikipedia.org/wiki/Gossip_protocol)

Only need to specify one or few nodes as bootstrap nodes, and all nodes will find each other using gossip protocol

2. When there is cache `Set` and `Delete`, the event will be propagated to all of the nodes.

So, all of the nodes will eventually have synced data.



## Cache filling

Cache filling mechanism is provided in [GetWithFiller](https://godoc.org/github.com/iwanbk/bcache#Bcache.GetWithFiller) func.

When the cache for the given key is not exists:
- it will call the provided `Filler`
- set the cache using value returned by the `Filler`

Even there are many goroutines which call `GetWithFiller`, the given `Filler` func
will only called once for each of the key.
Cache stampede could be avoided this way.

## Quick Start

In server 1
```go
bc, err := New(Config{
    // PeerID:     1, // leave it, will be set automatically based on mac addr
    ListenAddr: "192.168.0.1:12345",
    Peers:      nil, // it nil because we will use this node as bootstrap node
    MaxKeys:    1000,
    Logger:     logrus.New(),
})
if err != nil {
    log.Fatalf("failed to create cache: %v", err)
}
bc.Set("my_key", "my_val",86400)
```

In server 2
```go
bc, err := New(Config{
    // PeerID:     2, // leave it, will be set automatically based on mac addr
    ListenAddr: "192.168.0.2:12345",
    Peers:      []string{"192.168.0.1:12345"},
    MaxKeys:    1000,
    Logger:     logrus.New(),
})
if err != nil {
    log.Fatalf("failed to create cache: %v", err)
}
bc.Set("my_key2", "my_val2", 86400)
```

In server 3
```go
bc, err := New(Config{
    // PeerID:     3,// will be set automatically based on mac addr
    ListenAddr: "192.168.0.3:12345",
    Peers:      []string{"192.168.0.1:12345"},
    MaxKeys:    1000,
    Logger:     logrus.New(),
})
if err != nil {
    log.Fatalf("failed to create cache: %v", err)
}
val, exists := bc.Get("my_key2")
```

### GetWithFiller example

```go
c, err := New(Config{
    PeerID:     3,
    ListenAddr: "192.168.0.3:12345",
    Peers:      []string{"192.168.0.1:12345"},
    MaxKeys:    1000,
})
if err != nil {
    log.Fatalf("failed to create cache: %v", err)
}
val, exp,err  := bc.GetWithFiller("my_key2",func(key string) (string, error) {
        // get value from database
         .....
         //
        return value, 0, nil
}, 86400)
```

## Credits

- [weaveworks/mesh](https://github.com/weaveworks/mesh) for the gossip library
- [groupcache](https://github.com/golang/groupcache) for the inspiration