go.mercari.io/datastore@v1.8.2/dsmiddleware/localcache/localcache.go (about)

     1  package localcache
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"go.mercari.io/datastore"
     9  	"go.mercari.io/datastore/dsmiddleware/storagecache"
    10  )
    11  
    12  var _ storagecache.Storage = &cacheHandler{}
    13  var _ datastore.Middleware = &cacheHandler{}
    14  
    15  const defaultExpiration = 3 * time.Minute
    16  
    17  // New in-memory localcache middleware creates and returns.
    18  func New(opts ...CacheOption) CacheHandler {
    19  	ch := &cacheHandler{
    20  		cache:  make(map[string]cacheItem),
    21  		stOpts: &storagecache.Options{},
    22  	}
    23  
    24  	for _, opt := range opts {
    25  		opt.Apply(ch)
    26  	}
    27  
    28  	s := storagecache.New(ch, ch.stOpts)
    29  	ch.Middleware = s
    30  
    31  	if ch.expireDuration == 0 {
    32  		ch.expireDuration = defaultExpiration
    33  	}
    34  	if ch.logf == nil {
    35  		ch.logf = func(ctx context.Context, format string, args ...interface{}) {}
    36  	}
    37  
    38  	return ch
    39  }
    40  
    41  // CacheHandler abstracts cache operations to Storage.
    42  type CacheHandler interface {
    43  	datastore.Middleware
    44  	storagecache.Storage
    45  
    46  	HasCache(key datastore.Key) bool
    47  	DeleteCache(ctx context.Context, key datastore.Key)
    48  	CacheKeys() []string
    49  	CacheLen() int
    50  	FlushLocalCache()
    51  }
    52  
    53  type cacheHandler struct {
    54  	datastore.Middleware
    55  	stOpts *storagecache.Options
    56  
    57  	cache          map[string]cacheItem
    58  	m              sync.Mutex
    59  	expireDuration time.Duration
    60  	logf           func(ctx context.Context, format string, args ...interface{})
    61  }
    62  
    63  // A CacheOption is an option for cache.
    64  type CacheOption interface {
    65  	Apply(*cacheHandler)
    66  }
    67  
    68  type cacheItem struct {
    69  	Key          datastore.Key
    70  	PropertyList datastore.PropertyList
    71  	setAt        time.Time
    72  	expiration   time.Duration
    73  }
    74  
    75  func (ch *cacheHandler) HasCache(key datastore.Key) bool {
    76  	_, ok := ch.cache[key.Encode()]
    77  	return ok
    78  }
    79  
    80  func (ch *cacheHandler) DeleteCache(ctx context.Context, key datastore.Key) {
    81  	ch.m.Lock()
    82  	defer ch.m.Unlock()
    83  	ch.logf(ctx, "dsmiddleware/localcache.DeleteCache: key=%s", key.String())
    84  	delete(ch.cache, key.Encode())
    85  }
    86  
    87  func (ch *cacheHandler) CacheKeys() []string {
    88  	list := make([]string, 0, len(ch.cache))
    89  	for keyStr := range ch.cache {
    90  		list = append(list, keyStr)
    91  	}
    92  
    93  	return list
    94  }
    95  
    96  func (ch *cacheHandler) CacheLen() int {
    97  	return len(ch.cache)
    98  }
    99  
   100  func (ch *cacheHandler) FlushLocalCache() {
   101  	ch.m.Lock()
   102  	defer ch.m.Unlock()
   103  	ch.cache = make(map[string]cacheItem)
   104  }
   105  
   106  func (ch *cacheHandler) SetMulti(ctx context.Context, cis []*storagecache.CacheItem) error {
   107  	ch.m.Lock()
   108  	defer ch.m.Unlock()
   109  
   110  	ch.logf(ctx, "dsmiddleware/localcache.SetMulti: len=%d", len(cis))
   111  	for idx, ci := range cis {
   112  		ch.logf(ctx, "dsmiddleware/localcache.SetMulti: idx=%d key=%s len(ps)=%d", idx, ci.Key.String(), len(ci.PropertyList))
   113  	}
   114  
   115  	now := time.Now()
   116  	for _, ci := range cis {
   117  		if ci.Key.Incomplete() {
   118  			continue
   119  		}
   120  		ch.cache[ci.Key.Encode()] = cacheItem{
   121  			Key:          ci.Key,
   122  			PropertyList: ci.PropertyList,
   123  			setAt:        now,
   124  			expiration:   ch.expireDuration,
   125  		}
   126  	}
   127  
   128  	return nil
   129  }
   130  
   131  func (ch *cacheHandler) GetMulti(ctx context.Context, keys []datastore.Key) ([]*storagecache.CacheItem, error) {
   132  	ch.m.Lock()
   133  	defer ch.m.Unlock()
   134  
   135  	now := time.Now()
   136  
   137  	ch.logf(ctx, "dsmiddleware/localcache.GetMulti: len=%d", len(keys))
   138  	for idx, key := range keys {
   139  		ch.logf(ctx, "dsmiddleware/localcache.GetMulti: idx=%d key=%s", idx, key.String())
   140  	}
   141  
   142  	resultList := make([]*storagecache.CacheItem, len(keys))
   143  	for idx, key := range keys {
   144  		if key.Incomplete() {
   145  			ch.logf(ctx, "dsmiddleware/localcache.GetMulti: idx=%d, incomplete key=%s", idx, key.String())
   146  			continue
   147  		}
   148  		cItem, ok := ch.cache[key.Encode()]
   149  		if !ok {
   150  			ch.logf(ctx, "dsmiddleware/localcache.GetMulti: idx=%d, missed key=%s", idx, key.String())
   151  			continue
   152  		}
   153  
   154  		if cItem.setAt.Add(cItem.expiration).After(now) {
   155  			ch.logf(ctx, "dsmiddleware/localcache.GetMulti: idx=%d, hit key=%s len(ps)=%d", idx, key.String(), len(cItem.PropertyList))
   156  			resultList[idx] = &storagecache.CacheItem{
   157  				Key:          key,
   158  				PropertyList: cItem.PropertyList,
   159  			}
   160  		} else {
   161  			ch.logf(ctx, "dsmiddleware/localcache.GetMulti: idx=%d, expired key=%s", idx, key.String())
   162  			delete(ch.cache, key.Encode())
   163  		}
   164  	}
   165  
   166  	return resultList, nil
   167  }
   168  
   169  func (ch *cacheHandler) DeleteMulti(ctx context.Context, keys []datastore.Key) error {
   170  	ch.m.Lock()
   171  	defer ch.m.Unlock()
   172  
   173  	ch.logf(ctx, "dsmiddleware/localcache.DeleteMulti: len=%d", len(keys))
   174  	for idx, key := range keys {
   175  		ch.logf(ctx, "dsmiddleware/localcache.DeleteMulti: idx=%d key=%s", idx, key.String())
   176  	}
   177  
   178  	for _, key := range keys {
   179  		delete(ch.cache, key.Encode())
   180  	}
   181  
   182  	return nil
   183  }