Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): in-memory cache support for property #970

Open
wants to merge 17 commits into
base: feat/in-memory-cache-system-geojson
Choose a base branch
from
2 changes: 2 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 h1:xvqufLtNVwAhN8NMyWklVgxnWohi+wtMGQMhtxexlm0=
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f h1:Sk0u0gIncQaQD23zAoAZs2DNi2u2l5UTLi4CmCBL5v8=
Expand All @@ -192,6 +193,7 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/lint v0.0.0-20170918230701-e5d664eb928e h1:ior8LN6127GsA53E9mD9nH/oP/LVbJplmLH5V8o+/Uk=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand Down
24 changes: 18 additions & 6 deletions server/e2e/gql_nlslayer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,19 +672,23 @@ func moveInfoboxBlock(e *httpexpect.Expect, layerId, infoboxBlockId string, inde
return requestBody, res, res.Path("$.data.moveNLSInfoboxBlock.infoboxBlockId").Raw().(string)
}

func TestInfoboxBlocksCRUD(t *testing.T) {
mr, err := miniredis.Run()
if err != nil {
t.Fatal(err)
func infoboxBlocksCRUD(t *testing.T, isUseRedis bool) {
redisAddress := ""
if isUseRedis {
mr, err := miniredis.Run()
if err != nil {
t.Fatal(err)
}
defer mr.Close()
redisAddress = mr.Addr()
}
defer mr.Close()

e := StartServer(t, &config.Config{
Origins: []string{"https://example.com"},
AuthSrv: config.AuthSrvConfig{
Disabled: true,
},
RedisHost: mr.Addr(),
RedisHost: redisAddress,
}, true, baseSeeder)

pId := createProject(e)
Expand Down Expand Up @@ -743,6 +747,14 @@ func TestInfoboxBlocksCRUD(t *testing.T) {
Path("$.data.node.newLayers[0].infobox.blocks").Equal([]any{})
}

func TestInfoboxBlocksCRUD(t *testing.T) {
infoboxBlocksCRUD(t, false)
}

func TestInfoboxBlocksCRUDWithRedis(t *testing.T) {
infoboxBlocksCRUD(t, true)
}

func addCustomProperties(
e *httpexpect.Expect,
layerId string,
Expand Down
2 changes: 1 addition & 1 deletion server/internal/usecase/interactor/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func NewContainer(r *repo.Container, g *gateway.Container,
Plugin: NewPlugin(r, g),
Policy: NewPolicy(r),
Project: NewProject(r, g),
Property: NewProperty(r, g),
Property: NewProperty(r, g, redisAdapter),
Published: published,
Scene: NewScene(r, g),
Tag: NewTag(r),
Expand Down
204 changes: 202 additions & 2 deletions server/internal/usecase/interactor/property.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import (
"context"
"errors"

"github.com/go-redis/redis/v8"
"github.com/reearth/reearth/server/internal/usecase"
"github.com/reearth/reearth/server/internal/usecase/gateway"
"github.com/reearth/reearth/server/internal/usecase/interfaces"
"github.com/reearth/reearth/server/internal/usecase/repo"
"github.com/reearth/reearth/server/pkg/id"
"github.com/reearth/reearth/server/pkg/property"
"github.com/reearth/reearth/server/pkg/value"
"github.com/reearth/reearthx/usecasex"
"github.com/vmihailenco/msgpack/v5"
)

type Property struct {
Expand All @@ -24,9 +27,10 @@ type Property struct {
assetRepo repo.Asset
file gateway.File
transaction usecasex.Transaction
redis gateway.RedisGateway
}

func NewProperty(r *repo.Container, gr *gateway.Container) interfaces.Property {
func NewProperty(r *repo.Container, gr *gateway.Container, redis gateway.RedisGateway) interfaces.Property {
return &Property{
commonSceneLock: commonSceneLock{sceneLockRepo: r.SceneLock},
propertyRepo: r.Property,
Expand All @@ -37,6 +41,7 @@ func NewProperty(r *repo.Container, gr *gateway.Container) interfaces.Property {
assetRepo: r.Asset,
transaction: r.Transaction,
file: gr.File,
redis: redis,
}
}

Expand Down Expand Up @@ -88,10 +93,20 @@ func (i *Property) UpdateValue(ctx context.Context, inp interfaces.UpdatePropert
}
}()

p, err = i.propertyRepo.FindByID(ctx, inp.PropertyID)
propertyCache, err := getPropertyFromCache(ctx, i.redis, property.PropertyCacheKey(inp.PropertyID))
if err != nil {
return nil, nil, nil, nil, err
}

if propertyCache == nil {
p, err = i.propertyRepo.FindByID(ctx, inp.PropertyID)
if err != nil {
return nil, nil, nil, nil, err
}
} else {
p = propertyCache
}

if err := i.CanWriteScene(p.Scene(), operator); err != nil {
return nil, nil, nil, nil, err
}
Expand All @@ -116,6 +131,12 @@ func (i *Property) UpdateValue(ctx context.Context, inp interfaces.UpdatePropert
}

tx.Commit()

err = setPropertyToCache(ctx, i.redis, property.PropertyCacheKey(p.ID()), p)
if err != nil {
return nil, nil, nil, nil, err
}

return p, pgl, pg, field, nil
}

Expand Down Expand Up @@ -432,3 +453,182 @@ func (i *Property) UpdateItems(ctx context.Context, inp interfaces.UpdatePropert

return p, nil
}

type PropertyForRedis struct {
ID string `msgpack:"ID"`
Scene string `msgpack:"Scene"`
Schema string `msgpack:"Schema"`
Items []GroupForRedis `msgpack:"Items"`
}

type GroupForRedis struct {
ID string `msgpack:"ID"`
SchemaGroup string `msgpack:"SchemaGroup"`
Fields []*FieldForRedis `msgpack:"Fields"`
}

type FieldForRedis struct {
Field string `msgpack:"Field"`
Links *LinksForRedis `msgpack:"Links,omitempty"`
V *OptionalValueForRedis `msgpack:"V,omitempty"`
}

type LinksForRedis struct {
Links []*LinkForRedis `msgpack:"Links"`
}

type LinkForRedis struct {
Dataset *string `msgpack:"Dataset,omitempty"`
Schema *string `msgpack:"Schema,omitempty"`
Field *string `msgpack:"Field,omitempty"`
}

type OptionalValueForRedis struct {
Type string `msgpack:"Type"`
Value *ValueForRedis `msgpack:"Value,omitempty"`
}

type ValueForRedis struct {
P map[string]interface{} `msgpack:"P"`
V interface{} `msgpack:"V"`
T string `msgpack:"T"`
}

func getPropertyFromCache(ctx context.Context, redisClient any, cacheKey string) (*property.Property, error) {
redisAdapter, ok := checkRedisClient(redisClient)
if !ok {
return nil, nil
}

val, err := redisAdapter.GetValue(ctx, cacheKey)
if err != nil {
if err == redis.Nil {
return nil, nil
}
return nil, err
}

var p PropertyForRedis
if err := msgpack.Unmarshal([]byte(val), &p); err != nil {
return nil, err
}

propertyDomain, err := convertPropertyFromRedis(p)
if err != nil {
return nil, err
}

return propertyDomain, nil
}

func setPropertyToCache(ctx context.Context, redisClient any, cacheKey string, data *property.Property) error {
redisAdapter, ok := checkRedisClient(redisClient)
if !ok {
return nil
}

propertyForRedis := convertPropertyToRedis(data)

serializedData, err := msgpack.Marshal(propertyForRedis)
if err != nil {
return err
}

return redisAdapter.SetValue(ctx, cacheKey, serializedData)
}

func convertPropertyToRedis(p *property.Property) PropertyForRedis {
ptr := &property.Pointer{}
fields := p.Items()[0].Fields(ptr)

fieldsForRedis := make([]*FieldForRedis, 0, len(fields))
for _, field := range fields {
valueForRedis := ValueForRedis{
P: map[string]interface{}{},
V: field.TypeAndValue().Value().Value(),
T: string(field.TypeAndValue().Value().Type()),
}

optionalValueForRedis := OptionalValueForRedis{
Type: string(field.TypeAndValue().Type()),
Value: &valueForRedis,
}

fieldForRedis := FieldForRedis{
Field: field.Field().String(),
Links: nil,
V: &optionalValueForRedis,
}

fieldsForRedis = append(fieldsForRedis, &fieldForRedis)
}

groupForRedis := GroupForRedis{
ID: p.Items()[0].ID().String(),
SchemaGroup: p.Items()[0].SchemaGroup().String(),
Fields: fieldsForRedis,
}

propertyForRedis := PropertyForRedis{
ID: p.ID().String(),
Scene: p.Scene().String(),
Schema: p.Schema().String(),
Items: []GroupForRedis{groupForRedis},
}

return propertyForRedis
}

func convertPropertyFromRedis(p PropertyForRedis) (*property.Property, error) {

fieldsDomain := make([]*property.Field, 0, len(p.Items[0].Fields))
for _, field := range p.Items[0].Fields {

var v interface{}
if field.Field == "padding" {
m := field.V.Value.V.(map[string]interface{})
v = property.Spacing{
Top: m["Top"].(float64),
Bottom: m["Bottom"].(float64),
Left: m["Left"].(float64),
Right: m["Right"].(float64),
}
} else {
v = field.V.Value.V
}

valueDomain := value.New(
property.DefaultTypes(),
v,
value.Type(field.V.Value.T),
)

optionalValueDomain := property.NewOptionalValue(
property.ValueType(field.V.Type),
property.NewValue(valueDomain),
)

fieldDomain := property.NewFieldDomain(
property.FieldID(field.Field),
nil,
optionalValueDomain,
)

fieldsDomain = append(fieldsDomain, fieldDomain)
}

groupDomain := property.NewGroup().
ID(property.MustItemID(p.Items[0].ID)).
SchemaGroup(property.SchemaGroupID(p.Items[0].SchemaGroup)).
Fields(fieldsDomain).
MustBuild()

propertyDomain := property.New().
ID(property.MustID(p.ID)).
Scene(property.MustSceneID(p.Scene)).
Schema(property.MustSchemaID(p.Schema)).
Items([]property.Item{groupDomain}).
MustBuild()

return propertyDomain, nil
}
8 changes: 8 additions & 0 deletions server/pkg/property/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ type Field struct {
v *OptionalValue
}

func NewFieldDomain(field FieldID, links *Links, v *OptionalValue) *Field {
return &Field{
field: field,
links: links,
v: v,
}
}

func (p *Field) Clone() *Field {
if p == nil {
return nil
Expand Down
4 changes: 4 additions & 0 deletions server/pkg/property/property.go
Original file line number Diff line number Diff line change
Expand Up @@ -641,3 +641,7 @@ func (p *Property) updateSchema(s SchemaID) bool {
func (p *Property) SetSchema(schema SchemaID) {
p.schema = schema.Clone()
}

func PropertyCacheKey(id ID) string {
return fmt.Sprintf("Property:%s", id)
}
14 changes: 14 additions & 0 deletions server/pkg/property/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ var types = value.TypePropertyMap{
value.Type(ValueTypeTimeline): &typePropertyTimeline{},
}

func NewValue(v *value.Value) *Value {
if v == nil {
return nil
}

return &Value{
v: *v,
}
}

func (vt ValueType) Valid() bool {
if _, ok := types[value.Type(vt)]; ok {
return true
Expand Down Expand Up @@ -245,3 +255,7 @@ func ValueFromStringOrNumber(s string) *Value {

return ValueTypeString.ValueFrom(s)
}

func DefaultTypes() value.TypePropertyMap {
return types
}
15 changes: 15 additions & 0 deletions server/pkg/value/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ type Value struct {
t Type
}

func New(
p TypePropertyMap,
v interface{},
t Type,
) *Value {
if t == TypeUnknown {
return nil
}
return &Value{
p: p,
v: v,
t: t,
}
}

func (v *Value) IsEmpty() bool {
return v == nil || v.t == TypeUnknown || v.v == nil
}
Expand Down
Loading