From fd9050ec4508a12ff64a365980764f617c50fdfb Mon Sep 17 00:00:00 2001 From: castaneai Date: Sat, 23 Dec 2023 14:25:13 +0900 Subject: [PATCH] feat: ticket cache --- go.mod | 2 + go.sum | 4 ++ loadtest/cmd/frontend/main.go | 13 +++-- loadtest/go.mod | 2 + loadtest/go.sum | 4 ++ pkg/statestore/ticketcache.go | 91 ++++++++++++++++++++++++++++++ pkg/statestore/ticketcache_test.go | 38 +++++++++++++ 7 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 pkg/statestore/ticketcache.go create mode 100644 pkg/statestore/ticketcache_test.go diff --git a/go.mod b/go.mod index 8a96a15..ab3240c 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( ) require ( + github.com/Code-Hex/go-generics-cache v1.3.1 // indirect github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.3.0 // indirect @@ -32,6 +33,7 @@ require ( go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20220328175248-053ad81199eb // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.14.0 // indirect golang.org/x/text v0.13.0 // indirect diff --git a/go.sum b/go.sum index 7fbc324..b359725 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Code-Hex/go-generics-cache v1.3.1 h1:i8rLwyhoyhaerr7JpjtYjJZUcCbWOdiYO3fZXLiEC4g= +github.com/Code-Hex/go-generics-cache v1.3.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4= github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= @@ -72,6 +74,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/exp v0.0.0-20220328175248-053ad81199eb h1:pC9Okm6BVmxEw76PUu0XUbOTQ92JX11hfvqTjAV3qxM= +golang.org/x/exp v0.0.0-20220328175248-053ad81199eb/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= diff --git a/loadtest/cmd/frontend/main.go b/loadtest/cmd/frontend/main.go index c244ec7..c236dd6 100644 --- a/loadtest/cmd/frontend/main.go +++ b/loadtest/cmd/frontend/main.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + cache "github.com/Code-Hex/go-generics-cache" "github.com/kelseyhightower/envconfig" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/redis/rueidis" @@ -27,9 +28,10 @@ const ( ) type config struct { - RedisAddr string `envconfig:"REDIS_ADDR" default:"127.0.0.1:6379"` - AssignmentRedisAddr string `envconfig:"REDIS_ADDR_ASSIGNMENT"` - Port string `envconfig:"PORT" default:"50504"` + RedisAddr string `envconfig:"REDIS_ADDR" default:"127.0.0.1:6379"` + AssignmentRedisAddr string `envconfig:"REDIS_ADDR_ASSIGNMENT"` + Port string `envconfig:"PORT" default:"50504"` + TicketCacheTTL time.Duration `envconfig:"TICKET_CACHE_TTL" default:"10s"` } func main() { @@ -53,7 +55,10 @@ func main() { } opts = append(opts, statestore.WithSeparatedAssignmentRedis(asRedis)) } - store := statestore.NewRedisStore(redis, opts...) + ticketCache := cache.New[string, *pb.Ticket]() + store := statestore.NewStoreWithTicketCache( + statestore.NewRedisStore(redis, opts...), ticketCache, + statestore.WithTicketCacheTTL(conf.TicketCacheTTL)) sv := grpc.NewServer() pb.RegisterFrontendServiceServer(sv, minimatch.NewFrontendService(store)) diff --git a/loadtest/go.mod b/loadtest/go.mod index c51c94f..673d2ee 100644 --- a/loadtest/go.mod +++ b/loadtest/go.mod @@ -18,6 +18,7 @@ require ( ) require ( + github.com/Code-Hex/go-generics-cache v1.3.1 // indirect github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect github.com/alicebob/miniredis/v2 v2.31.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -39,6 +40,7 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect + golang.org/x/exp v0.0.0-20220328175248-053ad81199eb // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.4.0 // indirect golang.org/x/sys v0.14.0 // indirect diff --git a/loadtest/go.sum b/loadtest/go.sum index 4e8c3b3..114207d 100644 --- a/loadtest/go.sum +++ b/loadtest/go.sum @@ -1,3 +1,5 @@ +github.com/Code-Hex/go-generics-cache v1.3.1 h1:i8rLwyhoyhaerr7JpjtYjJZUcCbWOdiYO3fZXLiEC4g= +github.com/Code-Hex/go-generics-cache v1.3.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4= github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= @@ -83,6 +85,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/exp v0.0.0-20220328175248-053ad81199eb h1:pC9Okm6BVmxEw76PUu0XUbOTQ92JX11hfvqTjAV3qxM= +golang.org/x/exp v0.0.0-20220328175248-053ad81199eb/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/statestore/ticketcache.go b/pkg/statestore/ticketcache.go new file mode 100644 index 0000000..0fcc689 --- /dev/null +++ b/pkg/statestore/ticketcache.go @@ -0,0 +1,91 @@ +package statestore + +import ( + "context" + "time" + + cache "github.com/Code-Hex/go-generics-cache" + "open-match.dev/open-match/pkg/pb" +) + +type ticketCacheOptions struct { + ttl time.Duration +} + +func defaultTicketCacheOptions() *ticketCacheOptions { + return &ticketCacheOptions{ + ttl: 10 * time.Second, + } +} + +type TicketCacheOption interface { + apply(options *ticketCacheOptions) +} + +type ticketCacheOptionFunc func(options *ticketCacheOptions) + +func (f ticketCacheOptionFunc) apply(options *ticketCacheOptions) { + f(options) +} + +func WithTicketCacheTTL(ttl time.Duration) TicketCacheOption { + return ticketCacheOptionFunc(func(options *ticketCacheOptions) { + options.ttl = ttl + }) +} + +// StoreWithTicketCache caches GetTicket results in-memory with TTL +type StoreWithTicketCache struct { + origin StateStore + ticketCache *cache.Cache[string, *pb.Ticket] + options *ticketCacheOptions +} + +func NewStoreWithTicketCache(origin StateStore, ticketCache *cache.Cache[string, *pb.Ticket], opts ...TicketCacheOption) *StoreWithTicketCache { + options := defaultTicketCacheOptions() + for _, o := range opts { + o.apply(options) + } + return &StoreWithTicketCache{ + origin: origin, + ticketCache: ticketCache, + options: options, + } +} + +func (s *StoreWithTicketCache) CreateTicket(ctx context.Context, ticket *pb.Ticket) error { + return s.origin.CreateTicket(ctx, ticket) +} + +func (s *StoreWithTicketCache) DeleteTicket(ctx context.Context, ticketID string) error { + s.ticketCache.Delete(ticketID) + return s.origin.DeleteTicket(ctx, ticketID) +} + +func (s *StoreWithTicketCache) GetTicket(ctx context.Context, ticketID string) (*pb.Ticket, error) { + if ticket, hit := s.ticketCache.Get(ticketID); hit { + return ticket, nil + } + ticket, err := s.origin.GetTicket(ctx, ticketID) + if err != nil { + return nil, err + } + s.ticketCache.Set(ticketID, ticket, cache.WithExpiration(s.options.ttl)) + return ticket, nil +} + +func (s *StoreWithTicketCache) GetAssignment(ctx context.Context, ticketID string) (*pb.Assignment, error) { + return s.origin.GetAssignment(ctx, ticketID) +} + +func (s *StoreWithTicketCache) GetActiveTickets(ctx context.Context, limit int64) ([]*pb.Ticket, error) { + return s.origin.GetActiveTickets(ctx, limit) +} + +func (s *StoreWithTicketCache) ReleaseTickets(ctx context.Context, ticketIDs []string) error { + return s.origin.ReleaseTickets(ctx, ticketIDs) +} + +func (s *StoreWithTicketCache) AssignTickets(ctx context.Context, asgs []*pb.AssignmentGroup) error { + return s.origin.AssignTickets(ctx, asgs) +} diff --git a/pkg/statestore/ticketcache_test.go b/pkg/statestore/ticketcache_test.go new file mode 100644 index 0000000..658369b --- /dev/null +++ b/pkg/statestore/ticketcache_test.go @@ -0,0 +1,38 @@ +package statestore + +import ( + "context" + "testing" + "time" + + cache "github.com/Code-Hex/go-generics-cache" + "github.com/alicebob/miniredis/v2" + "github.com/stretchr/testify/require" + "open-match.dev/open-match/pkg/pb" +) + +func TestTicketCache(t *testing.T) { + mr := miniredis.RunT(t) + ticketCache := cache.New[string, *pb.Ticket]() + ttl := 500 * time.Millisecond + redisStore := newTestRedisStore(t, mr.Addr()) + store := NewStoreWithTicketCache(redisStore, ticketCache, WithTicketCacheTTL(ttl)) + ctx := context.Background() + + require.NoError(t, store.CreateTicket(ctx, &pb.Ticket{Id: "t1"})) + t1, err := store.GetTicket(ctx, "t1") + require.NoError(t, err) + require.Equal(t, "t1", t1.Id) + + require.NoError(t, redisStore.DeleteTicket(ctx, "t1")) + + // it can be retrieved from the cache even if deleted + t1, err = store.GetTicket(ctx, "t1") + require.NoError(t, err) + require.Equal(t, "t1", t1.Id) + + time.Sleep(ttl + 10*time.Millisecond) + + t1, err = store.GetTicket(ctx, "t1") + require.Error(t, err, ErrTicketNotFound) +}