Skip to content

Commit

Permalink
feat: implement local cache (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
beihai0xff authored Jun 19, 2024
1 parent b576e44 commit 172ca52
Show file tree
Hide file tree
Showing 15 changed files with 359 additions and 9 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ jobs:
uses: docker/setup-buildx-action@v3
- name: Run Unittest
run: make test
- name: Upload coverage reports to Codecov
uses: codecov/[email protected]
with:
token: ${{ secrets.CODECOV_TOKEN }}

# build_docker_image:
# name: Build And Push Docker Image
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ gen/swagger:
swag init --parseDependency --parseDepth 1 -g app/turl/server/http_controller.go -o docs/swagger

test: bootstrap gen/mock
docker compose -f ./test/docker-compose.yaml up -d
docker compose -f ./test/docker-compose.yaml up -d --wait
go test -gcflags="all=-l" -race -coverprofile=coverage.out -v ./...


Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ module github.com/beiai0xff/turl
go 1.22

require (
github.com/allegro/bigcache/v3 v3.1.0
github.com/fatih/gomodifytags v1.16.0
github.com/go-playground/validator/v10 v10.22.0
github.com/jedib0t/go-pretty/v6 v6.5.9
github.com/redis/go-redis/v9 v9.5.3
github.com/samber/lo v1.39.0
github.com/stretchr/testify v1.9.0
github.com/swaggo/swag v1.16.3
Expand All @@ -22,8 +24,10 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk=
github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/gomodifytags v1.16.0 h1:B65npXIXSk44F6c1hZGE1NazSnt+eXvtdEOG2Uy+QdU=
Expand Down Expand Up @@ -84,6 +94,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
Expand Down
52 changes: 52 additions & 0 deletions pkg/cache/dcache/redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Package dcache implements a distributed cache Interface
// redis.go implements a distributed cache with redis
package dcache

import (
"context"
"errors"
"time"

"github.com/redis/go-redis/v9"

"github.com/beiai0xff/turl/pkg/cache"
redis2 "github.com/beiai0xff/turl/pkg/db/redis"
)

var _ cache.Interface = (*redisCache)(nil)

type redisCache struct {
rdb redis.UniversalClient
// bucketSize int
}

// NewRedis returns a new redis cache
func NewRedis(addr []string) cache.Interface {
return newRedisCache(addr)
}

func newRedisCache(addr []string) *redisCache {
return &redisCache{
rdb: redis2.Client(addr),
}
}

// Set the k v pair to the cache
func (c *redisCache) Set(ctx context.Context, k string, v []byte, ttl time.Duration) error {
return c.rdb.SetEx(ctx, k, v, ttl).Err()
}

// Get the value by key
func (c *redisCache) Get(ctx context.Context, k string) ([]byte, error) {
value, err := c.rdb.Get(ctx, k).Bytes()
if err != nil && errors.Is(err, redis.Nil) {
return nil, cache.ErrCacheMiss
}

return value, err
}

// Close the cache
func (c *redisCache) Close() error {
return c.rdb.Close()
}
54 changes: 54 additions & 0 deletions pkg/cache/dcache/redis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dcache

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/beiai0xff/turl/pkg/cache"
"github.com/beiai0xff/turl/test"
)

func TestNewRedisCache(t *testing.T) {
got := NewRedis(test.RedisAddr)
require.NotNil(t, got)
}

func Test_newRedisCache(t *testing.T) {
got := newRedisCache(test.RedisAddr)
require.NotNil(t, got)
}

func Test_redisCache_Set(t *testing.T) {
c := newRedisCache(test.RedisAddr)
t.Cleanup(
func() {
c.Close()
})

ctx := context.Background()
k, v, ttl := "key", []byte("value"), time.Minute
require.NoError(t, c.Set(ctx, k, v, ttl))
}

func Test_redisCache_Get(t *testing.T) {
c := newRedisCache(test.RedisAddr)
t.Cleanup(
func() {
c.Close()
})

ctx := context.Background()
k, v, ttl := "key_get", []byte("value"), time.Minute
require.NoError(t, c.Set(ctx, k, v, ttl))
got, err := c.Get(ctx, k)
require.NoError(t, err)
require.Equal(t, v, got)

// test cache miss
got, err = c.Get(ctx, "empty")
require.ErrorIs(t, err, cache.ErrCacheMiss)
require.Nil(t, got)
}
22 changes: 22 additions & 0 deletions pkg/cache/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Package cache provides the cache management interface define
package cache

import (
"context"
"errors"
"time"
)

// ErrCacheMiss cache miss error
// if Get() method return this error, means key is not exist
var ErrCacheMiss = errors.New("cache: key is missing")

// Interface cache interface
type Interface interface {
// Set the key value to cache
Set(ctx context.Context, k string, v []byte, ttl time.Duration) error
// Get the key value from cache
Get(ctx context.Context, k string) ([]byte, error)
// Close the cache
Close() error
}
29 changes: 29 additions & 0 deletions pkg/cache/lcahce/benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package lcahce

import (
"context"
"strconv"
"testing"
"time"

"github.com/allegro/bigcache/v3"
)

func Benchmark_bigCache_Set(b *testing.B) {
config := bigcache.DefaultConfig(10 * time.Minute)
config.MaxEntriesInWindow = 1e6

c, _ := bigcache.New(context.Background(), config)

v := []byte("https://www.abc.com/images/100040.jpg\n")

keys := make([]string, 0, b.N)
for i := range b.N {
keys = append(keys, strconv.Itoa(i+10000))
}

b.ResetTimer()
for i := range b.N {
_ = c.Set(keys[i], v)
}
}
65 changes: 65 additions & 0 deletions pkg/cache/lcahce/localCache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Package lcahce provides the local cache
package lcahce

import (
"context"
"errors"
"time"

"github.com/allegro/bigcache/v3"

"github.com/beiai0xff/turl/pkg/cache"
)

var (
_ cache.Interface = (*localCache)(nil)
errInvalidCap = errors.New("cache: invalid capacity")
)

type localCache struct {
cache *bigcache.BigCache
}

// New create a local cache
// capacity is the cache capacity
// ttl is the time to live
func New(capacity int, ttl time.Duration) (cache.Interface, error) {
return newLocalCache(capacity, ttl)
}

func newLocalCache(capacity int, ttl time.Duration) (*localCache, error) {
if capacity <= 0 {
return nil, errInvalidCap
}

config := bigcache.DefaultConfig(ttl)
config.MaxEntriesInWindow = capacity

c, err := bigcache.New(context.Background(), config)
if err != nil {
return nil, err
}

return &localCache{cache: c}, err
}

// Set the k v pair to the cache
// Note that the duration is not used
func (l *localCache) Set(_ context.Context, k string, v []byte, _ time.Duration) error {
return l.cache.Set(k, v)
}

// Get the value by key
func (l *localCache) Get(_ context.Context, k string) ([]byte, error) {
v, err := l.cache.Get(k)
if err != nil && errors.Is(err, bigcache.ErrEntryNotFound) {
return nil, cache.ErrCacheMiss
}

return v, err
}

// Close the cache
func (l *localCache) Close() error {
return l.cache.Close()
}
77 changes: 77 additions & 0 deletions pkg/cache/lcahce/localCache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package lcahce

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/beiai0xff/turl/pkg/cache"
)

func TestNew(t *testing.T) {
c, err := New(1e6, 10*time.Minute)
require.NoError(t, err)
t.Cleanup(func() {
c.Close()
})
require.NotNil(t, c)
}

func Test_newLocalCache_failed(t *testing.T) {
// invalid cap
c, err := newLocalCache(-1, 10*time.Minute)
require.Error(t, err)
require.Nil(t, c)

c, err = newLocalCache(0, 10*time.Minute)
require.Error(t, err)
require.Nil(t, c)
}

func Test_newLocalCache_success(t *testing.T) {
// new cache failed
c, err := newLocalCache(1e6, 10*time.Minute)
require.NoError(t, err)
t.Cleanup(func() {
c.Close()
})

k, v := "key", []byte("value")
require.NoError(t, c.Set(context.Background(), k, v, 0))
got, err := c.Get(context.Background(), k)
require.NoError(t, err)
require.Equal(t, v, got)
}

func Test_localCache_Set(t *testing.T) {
c, err := newLocalCache(1e6, 10*time.Minute)
require.NoError(t, err)
t.Cleanup(func() {
c.Close()
})

k, v := "key", []byte("value")

require.NoError(t, c.Set(context.Background(), k, v, 10*time.Minute))
}

func Test_localCache_Get(t *testing.T) {
c, err := newLocalCache(1e6, 10*time.Minute)
require.NoError(t, err)
t.Cleanup(func() {
c.Close()
})

k, v := "key", []byte("value")

require.NoError(t, c.Set(context.Background(), k, v, 10*time.Minute))
got, err := c.Get(context.Background(), k)
require.NoError(t, err)
require.Equal(t, v, got)

got, err = c.Get(context.Background(), "empty_get")
require.ErrorIs(t, err, cache.ErrCacheMiss)
require.Nil(t, got)
}
8 changes: 4 additions & 4 deletions pkg/db/db.go → pkg/db/mysql/client.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Package db provides database operations
package db
// Package mysql provides MySQL connections
package mysql

import (
"log"
Expand All @@ -11,8 +11,8 @@ import (
"gorm.io/gorm/logger"
)

// NewDB create a new gorm db connection
func NewDB(dsn string) (*gorm.DB, error) {
// New create a new gorm db
func New(dsn string) (*gorm.DB, error) {
l := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
Expand Down
Loading

0 comments on commit 172ca52

Please sign in to comment.