Skip to content

Commit

Permalink
Replace allegro/bigcache with something lighter.
Browse files Browse the repository at this point in the history
In high frequency environment the allegro bigcache caused significant
memory usage overhead. This commit replaces it with something much lighter.
  • Loading branch information
lukaszraczylo committed Apr 8, 2023
1 parent 7af4e1f commit 7d3abf5
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 50 deletions.
18 changes: 2 additions & 16 deletions base.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"fmt"
"time"

"github.com/allegro/bigcache"
"github.com/akyoto/cache"
"github.com/lukaszraczylo/go-simple-graphql/utils/concurrency"
"github.com/lukaszraczylo/go-simple-graphql/utils/logger"

Expand Down Expand Up @@ -113,21 +113,7 @@ func (b *BaseClient) SetOutput(output string) {

func (b *BaseClient) enableCache() {
var err error
b.cache.client, err = bigcache.NewBigCache(
// bigcache.DefaultConfig(time.Duration(b.cache.ttl) * time.Second)
bigcache.Config{
Shards: 1024,
LifeWindow: time.Duration(b.cache.ttl) * time.Second,
CleanWindow: time.Duration(b.cache.ttl) * 2 * time.Second,
OnRemove: func(key string, entry []byte) {
b.Logger.Debug(b, "Removing cache entry;", "key", key)
},
MaxEntriesInWindow: 1000 * 10 * 60,
MaxEntrySize: 500,
Verbose: true,
HardMaxCacheSize: 8192,
},
)
b.cache.client = cache.New(time.Duration(b.cache.ttl) * time.Second * 2)
if err != nil {
fmt.Println(">> Error while creating cache client;", "error", err.Error())
panic(err)
Expand Down
6 changes: 3 additions & 3 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package gql

func (c *BaseClient) cacheLookup(hash string) []byte {
if c.cache.client != nil {
g, err := c.cache.client.Get(hash)
if err == nil {
obj, found := c.cache.client.Get(hash)
if found {
c.Logger.Debug(c, "Cache hit;", "hash", hash)
return g
return obj.([]byte)
}
// if error is not nil it means that hash is not present in cache
c.Logger.Debug(c, "Cache miss;", "hash", hash)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/lukaszraczylo/go-simple-graphql
go 1.20

require (
github.com/allegro/bigcache v1.2.1
github.com/akyoto/cache v1.0.6
github.com/avast/retry-go/v4 v4.3.3
github.com/gookit/goutil v0.6.8
github.com/json-iterator/go v1.1.12
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/akyoto/cache v1.0.6 h1:5XGVVYoi2i+DZLLPuVIXtsNIJ/qaAM16XT0LaBaXd2k=
github.com/akyoto/cache v1.0.6/go.mod h1:WfxTRqKhfgAG71Xh6E3WLpjhBtZI37O53G4h5s+3iM4=
github.com/avast/retry-go/v4 v4.3.3 h1:G56Bp6mU0b5HE1SkaoVjscZjlQb0oy4mezwY/cGH19w=
github.com/avast/retry-go/v4 v4.3.3/go.mod h1:rg6XFaiuFYII0Xu3RDbZQkxCofFwruZKW8oEF1jpWiU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down
10 changes: 8 additions & 2 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,17 @@ func (c *BaseClient) decodeResponse(jsonData []byte) any {

func (c *BaseClient) parseQueryHeaders(queryHeaders map[string]interface{}) (returnHeaders map[string]interface{}, cache_enabled bool, headers_modified bool) {
returnHeaders = make(map[string]interface{})
var err error

for k, v := range queryHeaders {
if k == "gqlcache" {
cache_enabled, _ = strconv.ParseBool(fmt.Sprintf("%v", v))
headers_modified = true
cache_enabled, err = strconv.ParseBool(fmt.Sprintf("%v", v))
if err != nil {
c.Logger.Error(c, "Can't parse gqlcache header", "error", err.Error())
}
if cache_enabled != c.cache.enabled {
headers_modified = true
}
continue
}
if k == "gqlretries" {
Expand Down
2 changes: 1 addition & 1 deletion query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,8 @@ func TestBaseClient_QueryCacheRandomizedRaceViaHeader(t *testing.T) {
}

for i := 0; i < 10; i++ {
tt.args.queryHeaders["gqlcache"] = rand.Intn(2) == 0
go func() {
tt.args.queryHeaders["gqlcache"] = rand.Intn(2) == 0
_, err := c.Query(tt.args.queryContent, tt.args.queryVariables, tt.args.queryHeaders)
if !tt.wantErr {
assert.NoError(t, err)
Expand Down
6 changes: 2 additions & 4 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func (q *queryExecutor) execute() {
if cachedResponse != nil {
q.client.Logger.Debug(q.client, "Found cached response")
q.result.data = q.client.decodeResponse(cachedResponse)
return // return cached response
} else {
q.client.Logger.Debug(q.client, "No cached response found")
}
Expand All @@ -94,10 +95,7 @@ func (q *queryExecutor) execute() {
}

if q.should_cache {
err = q.client.cache.client.Set(q.hash, jsonData)
if err != nil {
q.client.Logger.Error(q.client, "Error while setting cache key;", "error", err.Error())
}
q.client.cache.client.Set(q.hash, jsonData, time.Duration(q.client.cache.ttl)*time.Second)
}

q.result.data = q.client.decodeResponse(jsonData)
Expand Down
5 changes: 2 additions & 3 deletions structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import (
"context"
"net/http"

"github.com/akyoto/cache"
"github.com/lukaszraczylo/go-simple-graphql/utils/concurrency"
"github.com/lukaszraczylo/go-simple-graphql/utils/logger"

"github.com/allegro/bigcache"
)

type cacheStore struct {
client *bigcache.BigCache
client *cache.Cache
ttl int
enabled bool
}
Expand Down
26 changes: 10 additions & 16 deletions utils/logger/logger.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package logger

import (
"encoding/json"
"reflect"

"github.com/lukaszraczylo/go-simple-graphql/utils/helpers"
"github.com/lukaszraczylo/go-simple-graphql/utils/ordered_map"

jsoniter "github.com/json-iterator/go"
)

var json = jsoniter.ConfigCompatibleWithStandardLibrary

const (
reset = "\033[0m"
redBold = "\033[1;31m"
Expand All @@ -34,33 +28,33 @@ type Config struct {
LogLevel LogLevel
}

func Serialize(v any) (string, error) {
marshal, err := json.Marshal(internalSerialize(v))
func Serialize(v interface{}) ([]byte, error) {
b, err := json.Marshal(internalSerialize(v))
if err != nil {
return "", err
return nil, err
}
return helpers.BytesToString(marshal), nil
return b, nil
}

func internalSerialize(v any) any {
func internalSerialize(v interface{}) interface{} {
if v != nil {
switch reflect.TypeOf(v).Kind() {
case reflect.Struct:
r := ordered_map.New()
r := make(map[string]interface{})
name := reflect.TypeOf(v).Name()
if name != "" {
r.Set("_", reflect.TypeOf(v).String())
r["_"] = reflect.TypeOf(v).String()
}
for i := 0; i < reflect.ValueOf(v).NumField(); i++ {
r.Set(reflect.TypeOf(v).Field(i).Name, internalSerialize(reflect.ValueOf(v).Field(i).Interface()))
r[reflect.TypeOf(v).Field(i).Name] = internalSerialize(reflect.ValueOf(v).Field(i).Interface())
}
return r
case reflect.Ptr:
if !reflect.ValueOf(v).IsNil() {
return internalSerialize(reflect.ValueOf(v).Elem().Interface())
}
case reflect.Slice:
var r []any
var r []interface{}
tmpSlice := reflect.ValueOf(v)
for i := 0; i < tmpSlice.Len(); i++ {
r = append(r, internalSerialize(tmpSlice.Index(i).Interface()))
Expand Down
61 changes: 59 additions & 2 deletions utils/ordered_map/ordered_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,67 @@ type customJsonEncoder struct {
}

func (e *customJsonEncoder) Encode(v interface{}) error {
data, err := json.Marshal(v)
if v == nil {
e.buf.WriteString("null")
return nil
}

switch val := v.(type) {
case *OrderedMap:
data, err := json.Marshal(val.values)
if err != nil {
return err
}
e.buf.Write(data)
default:
data, err := json.Marshal(val)
if err != nil {
return err
}
e.buf.Write(data)
}
return nil
}

func (o OrderedMap) Keys() []string {
return o.keys
}

func (o OrderedMap) Values() []interface{} {
var values []interface{}
for _, k := range o.keys {
values = append(values, o.values[k])
}
return values
}

func (o *OrderedMap) Delete(key string) {
delete(o.values, key)
for i, k := range o.keys {
if k == key {
o.keys = append(o.keys[:i], o.keys[i+1:]...)
break
}
}
}

func (o OrderedMap) Exists(key string) bool {
_, exists := o.values[key]
return exists
}

func (o *OrderedMap) UnmarshalJSON(data []byte) error {
var values map[string]interface{}
err := json.Unmarshal(data, &values)
if err != nil {
return err
}
e.buf.Write(data)

o.keys = []string{}
o.values = map[string]interface{}{}
for k, v := range values {
o.Set(k, v)
}

return nil
}

0 comments on commit 7d3abf5

Please sign in to comment.