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  }