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  }