- Multiple cache stores: actually in memory, Redis, SQLite or your own custom store
- High concurrent thread-safe access
- A metric cache to let you store metrics about your caches usage (hits, miss, set success, set error, ...)
- An efficient binary marshaler to automatically marshal/unmarshal your cache values
- A well tested and adaptable lightweight pure Go code
- Use of Generics
gcache requires a Go version with Generics support (Go 1.18 or newer). To install gcache, use go get
:
go get github.com/amerkurev/gcache
See it in action:
import (
"fmt"
"github.com/amerkurev/gcache"
"github.com/amerkurev/gcache/store"
)
func main() {
c := gcache.New[int, string](store.MapStore(0))
c.Set(1, "Hello World")
v, _ := c.Get(1)
fmt.Println(v) // Hello World
}
A more complex example:
import (
"fmt"
"github.com/amerkurev/gcache"
"github.com/amerkurev/gcache/store"
"time"
)
type Key struct {
ID int
}
type Employee struct {
Key
Name string
DoB time.Time
}
func main() {
c := gcache.New[Key, *Employee](store.MapStore(0))
key := Key{1001}
c.Set(key, &Employee{
Key: key,
Name: "Amelia",
DoB: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
})
v, err := c.Get(key)
if err == nil {
fmt.Println(v.ID) // 1001
fmt.Println(v.Name) // Amelia
fmt.Println(v.DoB) // 2009-11-11 02:00:00 +0300 MSK
}
}
Go builtin map with mutex lock.
import (
"github.com/amerkurev/gcache"
"github.com/amerkurev/gcache/store"
)
func main() {
c := gcache.New[int, string](store.MapStore(0))
// ...
}
Bigcache is a fast, concurrent, evicting in-memory cache written to keep big number of entries.
import (
"github.com/allegro/bigcache/v3"
"github.com/amerkurev/gcache"
"github.com/amerkurev/gcache/store"
"time"
)
func main() {
bc, err := bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Minute))
if err != nil {
panic(err)
}
c := gcache.New[int, string](store.BigcacheStore(bc))
// ...
}
Redis is an in-memory database that persists on disk.
import (
"github.com/amerkurev/gcache"
"github.com/amerkurev/gcache/store"
"github.com/go-redis/redis/v8"
)
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
DB: 0,
})
c := gcache.New[int, string](store.RedisStore(rdb))
// ...
}
SQLite is a lightweight disk-based database that doesn’t require a separate server process.
import (
"context"
"database/sql"
"github.com/amerkurev/gcache"
"github.com/amerkurev/gcache/store"
)
func main() {
ctx := context.Background()
db, err := sql.Open("sqlite3", "test.db")
if err != nil {
panic(err)
}
sqliteStore, err := store.SQLiteStore(ctx, db)
if err != nil {
panic(err)
}
c := gcache.New[int, string](sqliteStore)
// ...
}
You also have the ability to write your own custom store by implementing the following interface:
type Store interface {
Get(ctx context.Context, key string) ([]byte, error)
Set(ctx context.Context, key string, data []byte) error
Delete(ctx context.Context, key string) error
Clear(ctx context.Context) error
}
Let's do this together:
import (
"context"
"github.com/amerkurev/gcache"
)
type MySuperStore struct {
m map[string][]byte // non-concurrent
}
func (s *MySuperStore) Get(_ context.Context, key string) ([]byte, error) {
v, ok := s.m[key]
if !ok {
return nil, gcache.ErrNotFound
}
return v, nil
}
func (s *MySuperStore) Set(_ context.Context, key string, data []byte) error {
s.m[key] = data
return nil
}
func (s *MySuperStore) Delete(_ context.Context, key string) error {
delete(s.m, key)
return nil
}
func (s *MySuperStore) Clear(_ context.Context) error {
s.m = make(map[string][]byte)
return nil
}
func main() {
store := &MySuperStore{m: make(map[string][]byte, 0)}
c := gcache.New[int, string](store)
// ...
}
import (
"fmt"
"github.com/amerkurev/gcache"
"github.com/amerkurev/gcache/store"
"sync"
"time"
)
func main() {
c := gcache.New[int, int](store.MapStore(10_000))
c.UseStats() // enable to store metrics about caches usage
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for k := 0; k < 10_000; k++ {
c.Set(k, k)
c.Get(k + 1)
}
}()
}
wg.Wait()
fmt.Printf("Took: %v\n", time.Since(start))
s, _ := c.Stats()
fmt.Printf("Hits: %d\n", s.Hits)
fmt.Printf("Miss: %d\n", s.Miss)
fmt.Printf("ReadCount: %d\n", s.ReadCount)
fmt.Printf("WriteCount: %d\n", s.WriteCount)
fmt.Printf("ReadBytes: %d\n", s.ReadBytes)
fmt.Printf("WriteBytes: %d\n", s.WriteBytes)
// Our results:
// Took: 1.295712084s
// Hits: 989805
// Miss: 10195
// ReadCount: 1000000
// WriteCount: 1000000
// ReadBytes: 4895973
// WriteBytes: 4946000
}
The project is under active development and may have breaking changes till v1 is released. However, we are trying our best not to break things unless there is a good reason.
The MIT License