go.mercari.io/datastore@v1.8.2/dsmiddleware/dsmemcache/dsmemcache.go (about) 1 package dsmemcache 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/gob" 7 "time" 8 9 "github.com/bradfitz/gomemcache/memcache" 10 "go.mercari.io/datastore" 11 "go.mercari.io/datastore/dsmiddleware/storagecache" 12 ) 13 14 var _ storagecache.Storage = &cacheHandler{} 15 var _ datastore.Middleware = &cacheHandler{} 16 17 // New dsmemcache middleware creates & returns. 18 func New(client *memcache.Client, opts ...CacheOption) interface { 19 datastore.Middleware 20 storagecache.Storage 21 } { 22 ch := &cacheHandler{ 23 client: client, 24 stOpts: &storagecache.Options{}, 25 } 26 27 for _, opt := range opts { 28 opt.Apply(ch) 29 } 30 31 s := storagecache.New(ch, ch.stOpts) 32 ch.Middleware = s 33 34 if ch.logf == nil { 35 ch.logf = func(ctx context.Context, format string, args ...interface{}) {} 36 } 37 if ch.cacheKey == nil { 38 ch.cacheKey = func(key datastore.Key) string { 39 return "mercari:dsmemcache:" + key.Encode() 40 } 41 } 42 43 return ch 44 } 45 46 type cacheHandler struct { 47 datastore.Middleware 48 stOpts *storagecache.Options 49 50 client *memcache.Client 51 expireDuration time.Duration 52 logf func(ctx context.Context, format string, args ...interface{}) 53 cacheKey func(key datastore.Key) string 54 } 55 56 // A CacheOption is an cache option for a dsmemcache middleware. 57 type CacheOption interface { 58 Apply(*cacheHandler) 59 } 60 61 func (ch *cacheHandler) SetMulti(ctx context.Context, cis []*storagecache.CacheItem) error { 62 63 ch.logf(ctx, "dsmiddleware/dsmemcache.SetMulti: incoming len=%d", len(cis)) 64 65 for _, ci := range cis { 66 if ci.Key.Incomplete() { 67 panic("incomplete key incoming") 68 } 69 var buf bytes.Buffer 70 enc := gob.NewEncoder(&buf) 71 if err := enc.Encode(ci.PropertyList); err != nil { 72 ch.logf(ctx, "dsmiddleware/dsmemcache.SetMulti: gob.Encode error key=%s err=%s", ci.Key.String(), err.Error()) 73 continue 74 } 75 item := &memcache.Item{ 76 Key: ch.cacheKey(ci.Key), 77 Value: buf.Bytes(), 78 Expiration: int32(ch.expireDuration.Seconds()), 79 } 80 if err := ch.client.Set(item); err != nil { 81 return err 82 } 83 } 84 85 return nil 86 } 87 88 func (ch *cacheHandler) GetMulti(ctx context.Context, keys []datastore.Key) ([]*storagecache.CacheItem, error) { 89 ch.logf(ctx, "dsmiddleware/dsmemcache.GetMulti: incoming len=%d", len(keys)) 90 91 resultList := make([]*storagecache.CacheItem, len(keys)) 92 93 cacheKeys := make([]string, 0, len(keys)) 94 for _, key := range keys { 95 cacheKeys = append(cacheKeys, ch.cacheKey(key)) 96 } 97 itemMap, err := ch.client.GetMulti(cacheKeys) 98 99 if err != nil { 100 ch.logf(ctx, "dsmiddleware/dsmemcache: error on dsmemcache.GetMulti %s", err.Error()) 101 } 102 103 hit, miss := 0, 0 104 for idx, key := range keys { 105 item, ok := itemMap[ch.cacheKey(key)] 106 if !ok { 107 resultList[idx] = nil 108 miss++ 109 continue 110 } 111 buf := bytes.NewBuffer(item.Value) 112 dec := gob.NewDecoder(buf) 113 var ps datastore.PropertyList 114 err = dec.Decode(&ps) 115 if err != nil { 116 resultList[idx] = nil 117 ch.logf(ctx, "dsmiddleware/dsmemcache.GetMulti: gob.Decode error key=%s err=%s", key.String(), err.Error()) 118 miss++ 119 continue 120 } 121 122 resultList[idx] = &storagecache.CacheItem{ 123 Key: key, 124 PropertyList: ps, 125 } 126 hit++ 127 } 128 129 ch.logf(ctx, "dsmiddleware/dsmemcache.GetMulti: hit=%d miss=%d", hit, miss) 130 131 return resultList, nil 132 } 133 134 func (ch *cacheHandler) DeleteMulti(ctx context.Context, keys []datastore.Key) error { 135 ch.logf(ctx, "dsmiddleware/dsmemcache.DeleteMulti: incoming len=%d", len(keys)) 136 for _, key := range keys { 137 err := ch.client.Delete(ch.cacheKey(key)) 138 if err != nil { 139 ch.logf(ctx, "dsmiddleware/dsmemcache: error on dsmemcache.DeleteMulti %s", err.Error()) 140 } 141 } 142 143 return nil 144 }