Skip to content

Instantly share code, notes, and snippets.

@calebschoepp
Created April 6, 2020 06:17
Show Gist options
  • Save calebschoepp/0165d92de412e288aa7441e792d0aa3a to your computer and use it in GitHub Desktop.
Save calebschoepp/0165d92de412e288aa7441e792d0aa3a to your computer and use it in GitHub Desktop.
Code to wrap Bigcache with a generic interface
package cache
import (
"bytes"
"encoding/gob"
"errors"
"github.com/allegro/bigcache/v2"
)
type bigCache struct {
cache *bigcache.BigCache
}
// newBigCache returns a new BigCache struct
func newBigCache(cacheConfig *cacheConfig) (*bigCache, error) {
cache, err := bigcache.NewBigCache(bigcache.Config{
Shards: 16,
LifeWindow: cacheConfig.ttl,
CleanWindow: cacheConfig.cleanFreq,
MaxEntriesInWindow: 1000 * 10 * 60,
MaxEntrySize: 500,
Verbose: false,
HardMaxCacheSize: cacheConfig.size,
StatsEnabled: true,
})
if err != nil {
return nil, err
}
return &bigCache{
cache: cache,
}, nil
}
// Set inserts the key/value pair into the cache.
// Only the exported fields of the given struct will be
// serialized and stored
func (c *bigCache) Set(key, value interface{}) error {
keyString, ok := key.(string)
if !ok {
return errors.New("a cache key must be a string")
}
valueBytes, err := serializeGOB(value)
if err != nil {
return err
}
return c.cache.Set(keyString, valueBytes)
}
// Get returns the value correlating to the key in the cache
func (c *bigCache) Get(key interface{}) (interface{}, error) {
// Assert the key is of string type
keyString, ok := key.(string)
if !ok {
return nil, errors.New("a cache key must be a string")
}
// Get the value in the byte format it is stored in
valueBytes, err := c.cache.Get(keyString)
if err != nil {
return nil, err
}
// Deserialize the bytes of the value
value, err := deserializeGOB(valueBytes)
if err != nil {
return nil, err
}
return value, nil
}
func serializeGOB(value interface{}) ([]byte, error) {
buf := bytes.Buffer{}
enc := gob.NewEncoder(&buf)
gob.Register(value)
err := enc.Encode(&value)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func deserializeGOB(valueBytes []byte) (interface{}, error) {
var value interface{}
buf := bytes.NewBuffer(valueBytes)
dec := gob.NewDecoder(buf)
err := dec.Decode(&value)
if err != nil {
return nil, err
}
return value, nil
}
package cache
import (
"time"
)
type cacheConfig struct {
size int // Size in MB
ttl time.Duration
cleanFreq time.Duration
}
// Interface to wrap any caching implementation
type Cache interface {
Set(key, value interface{}) error // Only exported fields in struct will be stored
Get(key interface{}) (interface{}, error)
}
// New builds a new default cache. You may pass options to modify the default values
func New(opts ...Option) (Cache, error) {
cacheConfig := &cacheConfig{
size: 1,
ttl: 60 * time.Second,
cleanFreq: 30 * time.Second,
}
for _, opt := range opts {
opt.apply(cacheConfig)
}
cache, err := newBigCache(cacheConfig)
if err != nil {
return nil, err
}
return cache, nil
}
type Option interface {
apply(cacheConfig *cacheConfig)
}
type optionFunc func(*cacheConfig)
func (opt optionFunc) apply(cacheConfig *cacheConfig) {
opt(cacheConfig)
}
// WithSizeInMB sets the size of the cache in MBs
// The minimum size of the cache is 1 MB
// If a size of 0 or less is passed the cache will have unlimited size
func WithSizeInMB(size int) Option {
return optionFunc(func(cacheConfig *cacheConfig) {
cacheConfig.size = size
})
}
// WithTTL will cause the cache to expire any item that lives longer
// than the given ttl
func WithTTL(ttl time.Duration) Option {
return optionFunc(func(cacheConfig *cacheConfig) {
cacheConfig.ttl = ttl
})
}
// WithCleanFrequency sets how often the cache will clean out expired items
// The lowest the frequency may be is 1 second
// If the time is 0 then no cleaning will happen and items will never be removed
func WithCleanFrequency(cleanFreq time.Duration) Option {
return optionFunc(func(cacheConfig *cacheConfig) {
cacheConfig.cleanFreq = cleanFreq
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment