go.mercari.io/datastore@v1.8.2/dsmiddleware/rediscache/rediscache.go (about) 1 package rediscache 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/gob" 7 "errors" 8 "time" 9 10 "github.com/gomodule/redigo/redis" 11 "go.mercari.io/datastore" 12 "go.mercari.io/datastore/dsmiddleware/storagecache" 13 ) 14 15 var _ storagecache.Storage = &cacheHandler{} 16 var _ datastore.Middleware = &cacheHandler{} 17 18 const defaultExpiration = 15 * time.Minute 19 20 // New Redis cache middleware creates & returns. 21 func New(conn redis.Conn, opts ...CacheOption) interface { 22 datastore.Middleware 23 storagecache.Storage 24 } { 25 26 // I want to make ch.dsmiddleware accessible from the test 27 ch := &cacheHandler{ 28 conn: conn, 29 stOpts: &storagecache.Options{}, 30 expireDuration: defaultExpiration, 31 } 32 33 for _, opt := range opts { 34 opt.Apply(ch) 35 } 36 37 s := storagecache.New(ch, ch.stOpts) 38 ch.Middleware = s 39 40 if ch.logf == nil { 41 ch.logf = func(ctx context.Context, format string, args ...interface{}) {} 42 } 43 if ch.cacheKey == nil { 44 ch.cacheKey = func(key datastore.Key) string { 45 return "mercari:rediscache:" + key.Encode() 46 } 47 } 48 49 return ch 50 } 51 52 type cacheHandler struct { 53 datastore.Middleware 54 stOpts *storagecache.Options 55 56 conn redis.Conn 57 expireDuration time.Duration 58 logf func(ctx context.Context, format string, args ...interface{}) 59 cacheKey func(key datastore.Key) string 60 } 61 62 // A CacheOption is an cache option for a Redis cache middleware. 63 type CacheOption interface { 64 Apply(*cacheHandler) 65 } 66 67 func (ch *cacheHandler) SetMulti(ctx context.Context, cis []*storagecache.CacheItem) error { 68 69 ch.logf(ctx, "dsmiddleware/rediscache.SetMulti: incoming len=%d", len(cis)) 70 71 err := ch.conn.Send("MULTI") 72 if err != nil { 73 ch.logf(ctx, `dsmiddleware/rediscache.SetMulti: conn.Send("MULTI") err=%s`, err.Error()) 74 return err 75 } 76 77 cnt := 0 78 for _, ci := range cis { 79 if ci.Key.Incomplete() { 80 panic("incomplete key incoming") 81 } 82 var buf bytes.Buffer 83 enc := gob.NewEncoder(&buf) 84 err := enc.Encode(ci.PropertyList) 85 if err != nil { 86 ch.logf(ctx, "dsmiddleware/rediscache.SetMulti: gob.Encode error key=%s err=%s", ci.Key.String(), err.Error()) 87 continue 88 } 89 cacheKey := ch.cacheKey(ci.Key) 90 cacheValue := buf.Bytes() 91 92 if ch.expireDuration <= 0 { 93 err = ch.conn.Send("SET", cacheKey, cacheValue) 94 if err != nil { 95 ch.logf(ctx, `dsmiddleware/rediscache.SetMulti: conn.Send("SET", "%s", ...) err=%s`, cacheKey, err.Error()) 96 return err 97 } 98 } else { 99 err = ch.conn.Send("SET", cacheKey, cacheValue, "PX", int64(ch.expireDuration/time.Millisecond)) 100 if err != nil { 101 ch.logf(ctx, `dsmiddleware/rediscache.SetMulti: conn.Send("SET", "%s", ..., "PX", %d) err=%s`, cacheKey, ch.expireDuration/time.Millisecond, err.Error()) 102 return err 103 } 104 } 105 cnt++ 106 } 107 108 ch.logf(ctx, "dsmiddleware/rediscache.SetMulti: len=%d", cnt) 109 110 _, err = ch.conn.Do("EXEC") 111 if err != nil { 112 ch.logf(ctx, `dsmiddleware/rediscache.SetMulti: conn.Send("EXEC") err=%s`, err.Error()) 113 return err 114 } 115 116 return nil 117 } 118 119 func (ch *cacheHandler) GetMulti(ctx context.Context, keys []datastore.Key) ([]*storagecache.CacheItem, error) { 120 121 ch.logf(ctx, "dsmiddleware/rediscache.GetMulti: incoming len=%d", len(keys)) 122 123 err := ch.conn.Send("MULTI") 124 if err != nil { 125 ch.logf(ctx, `dsmiddleware/rediscache.GetMulti: conn.Send("MULTI") err=%s`, err.Error()) 126 return nil, err 127 } 128 129 resultList := make([]*storagecache.CacheItem, len(keys)) 130 131 for idx, key := range keys { 132 cacheKey := ch.cacheKey(key) 133 resultList[idx] = &storagecache.CacheItem{ 134 Key: key, 135 } 136 err := ch.conn.Send("GET", cacheKey) 137 if err != nil { 138 ch.logf(ctx, `dsmiddleware/rediscache.GetMulti: conn.Send("GET", "%s") err=%s`, cacheKey, err.Error()) 139 return nil, err 140 } 141 } 142 143 resp, err := ch.conn.Do("EXEC") 144 if err != nil { 145 ch.logf(ctx, `dsmiddleware/rediscache.GetMulti: conn.Do("EXEC") err=%s`, err.Error()) 146 return nil, err 147 } 148 bs, err := redis.ByteSlices(resp, nil) 149 if err != nil { 150 ch.logf(ctx, `dsmiddleware/rediscache.GetMulti: redis.ByteSlices err=%s`, err.Error()) 151 return nil, err 152 } 153 154 hit := 0 155 miss := 0 156 for idx, b := range bs { 157 if len(b) == 0 { 158 resultList[idx] = nil 159 miss++ 160 continue 161 } 162 buf := bytes.NewBuffer(b) 163 dec := gob.NewDecoder(buf) 164 var ps datastore.PropertyList 165 err = dec.Decode(&ps) 166 if err != nil { 167 resultList[idx] = nil 168 ch.logf(ctx, "dsmiddleware/rediscache.GetMulti: gob.Decode error key=%s err=%s", keys[idx].String(), err.Error()) 169 miss++ 170 continue 171 } 172 173 if !resultList[idx].Key.Equal(keys[idx]) { 174 ch.logf(ctx, "dsmiddleware/rediscache.GetMulti: key equality check failed") 175 return nil, errors.New("dsmiddleware/rediscache.GetMulti: key equality check failed") 176 } 177 178 resultList[idx].PropertyList = ps 179 hit++ 180 } 181 182 ch.logf(ctx, "dsmiddleware/rediscache.GetMulti: hit=%d miss=%d", hit, miss) 183 184 return resultList, nil 185 } 186 187 func (ch *cacheHandler) DeleteMulti(ctx context.Context, keys []datastore.Key) error { 188 ch.logf(ctx, "dsmiddleware/rediscache.DeleteMulti: incoming len=%d", len(keys)) 189 190 err := ch.conn.Send("MULTI") 191 if err != nil { 192 ch.logf(ctx, `dsmiddleware/rediscache.DeleteMulti: conn.Send("MULTI") err=%s`, err.Error()) 193 return err 194 } 195 196 for _, key := range keys { 197 cacheKey := ch.cacheKey(key) 198 199 err = ch.conn.Send("DEL", cacheKey) 200 if err != nil { 201 ch.logf(ctx, `dsmiddleware/rediscache.DeleteMulti: conn.Send("DEL", "%s") err=%s`, cacheKey, err.Error()) 202 return err 203 } 204 } 205 206 _, err = ch.conn.Do("EXEC") 207 if err != nil { 208 ch.logf(ctx, `dsmiddleware/rediscache.DeleteMulti: conn.Send("EXEC") err=%s`, err.Error()) 209 return err 210 } 211 212 return nil 213 }