From da333b0b1e939165eba8ee22dff966fc2bed1e56 Mon Sep 17 00:00:00 2001 From: boojack Date: Sun, 7 Aug 2022 08:09:43 +0800 Subject: [PATCH] chore: add store cache service --- api/cache.go | 22 +++++++++++++++ go.mod | 8 +++++- go.sum | 8 ++++++ store/cache.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++ store/memo.go | 25 ++++++++++++++++ store/resource.go | 21 ++++++++++++++ store/shortcut.go | 25 ++++++++++++++++ store/store.go | 5 ++++ store/user.go | 25 ++++++++++++++++ 9 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 api/cache.go create mode 100644 store/cache.go diff --git a/api/cache.go b/api/cache.go new file mode 100644 index 00000000..302b5522 --- /dev/null +++ b/api/cache.go @@ -0,0 +1,22 @@ +package api + +// CacheNamespace is the type of a cache. +type CacheNamespace string + +const ( + // UserCache is the cache type of users. + UserCache CacheNamespace = "u" + // MemoCache is the cache type of memos. + MemoCache CacheNamespace = "m" + // ShortcutCache is the cache type of shortcuts. + ShortcutCache CacheNamespace = "s" + // ResourceCache is the cache type of resources. + ResourceCache CacheNamespace = "r" +) + +// CacheService is the service for caches. +type CacheService interface { + FindCache(namespace CacheNamespace, id int, entry interface{}) (bool, error) + UpsertCache(namespace CacheNamespace, id int, entry interface{}) error + DeleteCache(namespace CacheNamespace, id int) +} diff --git a/go.mod b/go.mod index 94539d3d..4a907a26 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/valyala/fasttemplate v1.2.1 // indirect golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 golang.org/x/net v0.0.0-20210917221730-978cfadd31cf // indirect - golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect + golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect ) @@ -26,7 +26,13 @@ require ( ) require ( + github.com/VictoriaMetrics/fastcache v1.10.0 github.com/gorilla/securecookie v1.1.1 github.com/gorilla/sessions v1.2.1 github.com/labstack/echo-contrib v0.12.0 ) + +require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/golang/snappy v0.0.4 // indirect +) diff --git a/go.sum b/go.sum index 530251b8..11327421 100644 --- a/go.sum +++ b/go.sum @@ -37,12 +37,15 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= +github.com/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY= +github.com/VictoriaMetrics/fastcache v1.10.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -51,6 +54,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/casbin/casbin/v2 v2.40.6/go.mod h1:sEL80qBYTbd+BPeL4iyvwYzFT3qwLaESq5aFKVLbLfA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -120,6 +125,7 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -435,6 +441,8 @@ golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 h1:D1v9ucDTYBtbz5vNuBbAhIMAGhQhJ6Ym5ah3maMVNX4= +golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/store/cache.go b/store/cache.go new file mode 100644 index 00000000..e511cbbb --- /dev/null +++ b/store/cache.go @@ -0,0 +1,72 @@ +package store + +import ( + "bytes" + "encoding/binary" + "encoding/gob" + "fmt" + + "github.com/VictoriaMetrics/fastcache" + "github.com/usememos/memos/api" +) + +var ( + // 64 MiB. + cacheSize = 1024 * 1024 * 64 + _ api.CacheService = (*CacheService)(nil) +) + +// CacheService implements a cache. +type CacheService struct { + cache *fastcache.Cache +} + +// NewCacheService creates a cache service. +func NewCacheService() *CacheService { + return &CacheService{ + cache: fastcache.New(cacheSize), + } +} + +// FindCache finds the value in cache. +func (s *CacheService) FindCache(namespace api.CacheNamespace, id int, entry interface{}) (bool, error) { + buf1 := []byte{0, 0, 0, 0, 0, 0, 0, 0} + binary.LittleEndian.PutUint64(buf1, uint64(id)) + + buf2, has := s.cache.HasGet(nil, append([]byte(namespace), buf1...)) + if has { + dec := gob.NewDecoder(bytes.NewReader(buf2)) + if err := dec.Decode(entry); err != nil { + return false, fmt.Errorf("failed to decode entry for cache namespace: %s, error: %w", namespace, err) + } + return true, nil + } + + return false, nil +} + +// UpsertCache upserts the value to cache. +func (s *CacheService) UpsertCache(namespace api.CacheNamespace, id int, entry interface{}) error { + buf1 := []byte{0, 0, 0, 0, 0, 0, 0, 0} + binary.LittleEndian.PutUint64(buf1, uint64(id)) + + var buf2 bytes.Buffer + enc := gob.NewEncoder(&buf2) + if err := enc.Encode(entry); err != nil { + return fmt.Errorf("failed to encode entry for cache namespace: %s, error: %w", namespace, err) + } + s.cache.Set(append([]byte(namespace), buf1...), buf2.Bytes()) + + return nil +} + +// DeleteCache deletes the cache. +func (s *CacheService) DeleteCache(namespace api.CacheNamespace, id int) { + buf1 := []byte{0, 0, 0, 0, 0, 0, 0, 0} + binary.LittleEndian.PutUint64(buf1, uint64(id)) + + _, has := s.cache.HasGet(nil, append([]byte(namespace), buf1...)) + if has { + s.cache.Del(append([]byte(namespace), buf1...)) + } +} diff --git a/store/memo.go b/store/memo.go index 87c9ddaa..ff1fdf65 100644 --- a/store/memo.go +++ b/store/memo.go @@ -54,6 +54,10 @@ func (s *Store) CreateMemo(create *api.MemoCreate) (*api.Memo, error) { return nil, err } + if err := s.cache.UpsertCache(api.MemoCache, memo.ID, memo); err != nil { + return nil, err + } + return memo, nil } @@ -68,6 +72,10 @@ func (s *Store) PatchMemo(patch *api.MemoPatch) (*api.Memo, error) { return nil, err } + if err := s.cache.UpsertCache(api.MemoCache, memo.ID, memo); err != nil { + return nil, err + } + return memo, nil } @@ -91,6 +99,17 @@ func (s *Store) FindMemoList(find *api.MemoFind) ([]*api.Memo, error) { } func (s *Store) FindMemo(find *api.MemoFind) (*api.Memo, error) { + if find.ID != nil { + memo := &api.Memo{} + has, err := s.cache.FindCache(api.MemoCache, *find.ID, memo) + if err != nil { + return nil, err + } + if has { + return memo, nil + } + } + list, err := findMemoRawList(s.db, find) if err != nil { return nil, err @@ -105,6 +124,10 @@ func (s *Store) FindMemo(find *api.MemoFind) (*api.Memo, error) { return nil, err } + if err := s.cache.UpsertCache(api.MemoCache, memo.ID, memo); err != nil { + return nil, err + } + return memo, nil } @@ -114,6 +137,8 @@ func (s *Store) DeleteMemo(delete *api.MemoDelete) error { return FormatError(err) } + s.cache.DeleteCache(api.MemoCache, delete.ID) + return nil } diff --git a/store/resource.go b/store/resource.go index b98cb227..68a45bf5 100644 --- a/store/resource.go +++ b/store/resource.go @@ -51,6 +51,10 @@ func (s *Store) CreateResource(create *api.ResourceCreate) (*api.Resource, error resource := resourceRaw.toResource() + if err := s.cache.UpsertCache(api.ResourceCache, resource.ID, resource); err != nil { + return nil, err + } + return resource, nil } @@ -69,6 +73,17 @@ func (s *Store) FindResourceList(find *api.ResourceFind) ([]*api.Resource, error } func (s *Store) FindResource(find *api.ResourceFind) (*api.Resource, error) { + if find.ID != nil { + resource := &api.Resource{} + has, err := s.cache.FindCache(api.ResourceCache, *find.ID, resource) + if err != nil { + return nil, err + } + if has { + return resource, nil + } + } + list, err := findResourceList(s.db, find) if err != nil { return nil, err @@ -80,6 +95,10 @@ func (s *Store) FindResource(find *api.ResourceFind) (*api.Resource, error) { resource := list[0].toResource() + if err := s.cache.UpsertCache(api.ResourceCache, resource.ID, resource); err != nil { + return nil, err + } + return resource, nil } @@ -89,6 +108,8 @@ func (s *Store) DeleteResource(delete *api.ResourceDelete) error { return err } + s.cache.DeleteCache(api.ResourceCache, delete.ID) + return nil } diff --git a/store/shortcut.go b/store/shortcut.go index aac77693..2c83ec06 100644 --- a/store/shortcut.go +++ b/store/shortcut.go @@ -47,6 +47,10 @@ func (s *Store) CreateShortcut(create *api.ShortcutCreate) (*api.Shortcut, error shortcut := shortcutRaw.toShortcut() + if err := s.cache.UpsertCache(api.ShortcutCache, shortcut.ID, shortcut); err != nil { + return nil, err + } + return shortcut, nil } @@ -58,6 +62,10 @@ func (s *Store) PatchShortcut(patch *api.ShortcutPatch) (*api.Shortcut, error) { shortcut := shortcutRaw.toShortcut() + if err := s.cache.UpsertCache(api.ShortcutCache, shortcut.ID, shortcut); err != nil { + return nil, err + } + return shortcut, nil } @@ -76,6 +84,17 @@ func (s *Store) FindShortcutList(find *api.ShortcutFind) ([]*api.Shortcut, error } func (s *Store) FindShortcut(find *api.ShortcutFind) (*api.Shortcut, error) { + if find.ID != nil { + shortcut := &api.Shortcut{} + has, err := s.cache.FindCache(api.ShortcutCache, *find.ID, shortcut) + if err != nil { + return nil, err + } + if has { + return shortcut, nil + } + } + list, err := findShortcutList(s.db, find) if err != nil { return nil, err @@ -87,6 +106,10 @@ func (s *Store) FindShortcut(find *api.ShortcutFind) (*api.Shortcut, error) { shortcut := list[0].toShortcut() + if err := s.cache.UpsertCache(api.ShortcutCache, shortcut.ID, shortcut); err != nil { + return nil, err + } + return shortcut, nil } @@ -96,6 +119,8 @@ func (s *Store) DeleteShortcut(delete *api.ShortcutDelete) error { return FormatError(err) } + s.cache.DeleteCache(api.ShortcutCache, delete.ID) + return nil } diff --git a/store/store.go b/store/store.go index b7f7d8b2..82f2f884 100644 --- a/store/store.go +++ b/store/store.go @@ -3,6 +3,7 @@ package store import ( "database/sql" + "github.com/usememos/memos/api" "github.com/usememos/memos/server/profile" ) @@ -10,12 +11,16 @@ import ( type Store struct { db *sql.DB profile *profile.Profile + cache api.CacheService } // New creates a new instance of Store func New(db *sql.DB, profile *profile.Profile) *Store { + cacheService := NewCacheService() + return &Store{ db: db, profile: profile, + cache: cacheService, } } diff --git a/store/user.go b/store/user.go index ba25c2d4..0c7776b4 100644 --- a/store/user.go +++ b/store/user.go @@ -51,6 +51,10 @@ func (s *Store) CreateUser(create *api.UserCreate) (*api.User, error) { user := userRaw.toUser() + if err := s.cache.UpsertCache(api.UserCache, user.ID, user); err != nil { + return nil, err + } + return user, nil } @@ -62,6 +66,10 @@ func (s *Store) PatchUser(patch *api.UserPatch) (*api.User, error) { user := userRaw.toUser() + if err := s.cache.UpsertCache(api.UserCache, user.ID, user); err != nil { + return nil, err + } + return user, nil } @@ -80,6 +88,17 @@ func (s *Store) FindUserList(find *api.UserFind) ([]*api.User, error) { } func (s *Store) FindUser(find *api.UserFind) (*api.User, error) { + if find.ID != nil { + user := &api.User{} + has, err := s.cache.FindCache(api.UserCache, *find.ID, user) + if err != nil { + return nil, err + } + if has { + return user, nil + } + } + list, err := findUserList(s.db, find) if err != nil { return nil, err @@ -93,6 +112,10 @@ func (s *Store) FindUser(find *api.UserFind) (*api.User, error) { user := list[0].toUser() + if err := s.cache.UpsertCache(api.UserCache, user.ID, user); err != nil { + return nil, err + } + return user, nil } @@ -102,6 +125,8 @@ func (s *Store) DeleteUser(delete *api.UserDelete) error { return FormatError(err) } + s.cache.DeleteCache(api.UserCache, delete.ID) + return nil }