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

     1  package storagecache
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  
     7  	"go.mercari.io/datastore"
     8  )
     9  
    10  var _ datastore.Middleware = &cacheHandler{}
    11  
    12  // New storage cache (interface) middleware creates & returns.
    13  func New(s Storage, opts *Options) datastore.Middleware {
    14  	ch := &cacheHandler{
    15  		s: s,
    16  	}
    17  	if opts != nil {
    18  		ch.logf = opts.Logf
    19  		ch.filters = opts.Filters
    20  	}
    21  
    22  	if ch.logf == nil {
    23  		ch.logf = func(ctx context.Context, format string, args ...interface{}) {}
    24  	}
    25  
    26  	return ch
    27  }
    28  
    29  // Options provides common option values for storage.
    30  type Options struct {
    31  	Logf    func(ctx context.Context, format string, args ...interface{})
    32  	Filters []KeyFilter
    33  }
    34  
    35  // Storage is the abstraction of storage that holds the cache data.
    36  type Storage interface {
    37  	SetMulti(ctx context.Context, is []*CacheItem) error
    38  	// GetMulti returns slice of CacheItem of the same length as Keys of the argument.
    39  	// If not in the dsmiddleware, the value of the corresponding element is nil.
    40  	GetMulti(ctx context.Context, keys []datastore.Key) ([]*CacheItem, error)
    41  	DeleteMulti(ctx context.Context, keys []datastore.Key) error
    42  }
    43  
    44  // KeyFilter represents a function that determines if the specified Key should be cached.
    45  type KeyFilter func(ctx context.Context, key datastore.Key) bool
    46  
    47  type contextTx struct{}
    48  
    49  // CacheItem is serialized by Storage.
    50  type CacheItem struct {
    51  	Key          datastore.Key
    52  	PropertyList datastore.PropertyList
    53  }
    54  
    55  // txOps represents the type of operation in the transaction.
    56  type txOps int
    57  
    58  const (
    59  	// txPutOp represents the put operation in the transaction.
    60  	txPutOp txOps = iota
    61  	// txGetOp represents the get operation in the transaction.
    62  	txGetOp
    63  	// txDeleteOp represents the delete operation in the transaction.
    64  	txDeleteOp
    65  )
    66  
    67  // txOpLog is a log of operations within a transaction.
    68  type txOpLog struct {
    69  	Ops          txOps
    70  	Key          datastore.Key
    71  	PendingKey   datastore.PendingKey
    72  	PropertyList datastore.PropertyList
    73  }
    74  
    75  type cacheHandler struct {
    76  	s       Storage
    77  	m       sync.Mutex
    78  	logf    func(ctx context.Context, format string, args ...interface{})
    79  	filters []KeyFilter
    80  }
    81  
    82  func (ch *cacheHandler) target(ctx context.Context, key datastore.Key) bool {
    83  	for _, f := range ch.filters {
    84  		// If false comes back even once, it is not cached
    85  		if !f(ctx, key) {
    86  			return false
    87  		}
    88  	}
    89  
    90  	return true
    91  }
    92  
    93  func (ch *cacheHandler) AllocateIDs(info *datastore.MiddlewareInfo, keys []datastore.Key) ([]datastore.Key, error) {
    94  	return info.Next.AllocateIDs(info, keys)
    95  }
    96  
    97  func (ch *cacheHandler) PutMultiWithoutTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) ([]datastore.Key, error) {
    98  	keys, err := info.Next.PutMultiWithoutTx(info, keys, psList)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	cis := make([]*CacheItem, 0, len(keys))
   104  	for idx, key := range keys {
   105  		if key.Incomplete() {
   106  			// 発生し得ないはずだが他のMiddlewareがやらかすかもしれないので
   107  			continue
   108  		} else if !ch.target(info.Context, key) {
   109  			continue
   110  		}
   111  		cis = append(cis, &CacheItem{
   112  			Key:          key,
   113  			PropertyList: psList[idx],
   114  		})
   115  	}
   116  	if len(cis) == 0 {
   117  		return keys, nil
   118  	}
   119  	err = ch.s.SetMulti(info.Context, cis)
   120  	if err != nil {
   121  		ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.SetMulti err=%s", err.Error())
   122  	}
   123  
   124  	return keys, nil
   125  }
   126  
   127  func (ch *cacheHandler) PutMultiWithTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) ([]datastore.PendingKey, error) {
   128  	pKeys, err := info.Next.PutMultiWithTx(info, keys, psList)
   129  
   130  	ch.m.Lock()
   131  	defer ch.m.Unlock()
   132  
   133  	txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog)
   134  	if !ok {
   135  		txOpMap = make(map[datastore.Transaction][]*txOpLog)
   136  		info.Context = context.WithValue(info.Context, contextTx{}, txOpMap)
   137  	}
   138  
   139  	logs := txOpMap[info.Transaction]
   140  	for idx, key := range keys {
   141  		if !ch.target(info.Context, key) {
   142  			continue
   143  		}
   144  		log := &txOpLog{
   145  			Ops:          txPutOp,
   146  			PropertyList: psList[idx],
   147  		}
   148  		if key.Incomplete() {
   149  			log.PendingKey = pKeys[idx]
   150  		} else {
   151  			log.Key = key
   152  		}
   153  		logs = append(logs, log)
   154  	}
   155  	txOpMap[info.Transaction] = logs
   156  
   157  	return pKeys, err
   158  }
   159  
   160  func (ch *cacheHandler) GetMultiWithoutTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) error {
   161  	// strategy summary
   162  	//   When we have a dsmiddleware, don't get it.
   163  	//   When we don't have a dsmiddleware, passes arguments to next strategy.
   164  
   165  	// 最終的に各所からかき集めてきたdatastore.PropertyListを統合してpsListにする
   166  	// 1. psListをkeysと同じ長さまで伸長し、任意の場所にindexアクセスできるようにする
   167  	// 2. 全てのtargetであるkeysについてキャッシュに問い合わせをし、結果があった場合psListに代入する
   168  	// 3. キャッシュに無かったものを後段に問い合わせる 結果があった場合psListに代入し、次回のためにキャッシュにも入れる
   169  
   170  	// step 1
   171  	for len(psList) < len(keys) {
   172  		psList = append(psList, nil)
   173  	}
   174  
   175  	{ // step 2
   176  		filteredIdxList := make([]int, 0, len(keys))
   177  		filteredKey := make([]datastore.Key, 0, len(keys))
   178  		for idx, key := range keys {
   179  			if ch.target(info.Context, key) {
   180  				filteredIdxList = append(filteredIdxList, idx)
   181  				filteredKey = append(filteredKey, key)
   182  			}
   183  		}
   184  
   185  		if len(filteredKey) != 0 {
   186  			cis, err := ch.s.GetMulti(info.Context, filteredKey)
   187  			if err != nil {
   188  				ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.GetMulti err=%s", err.Error())
   189  
   190  				return info.Next.GetMultiWithoutTx(info, keys, psList)
   191  			}
   192  
   193  			for idx, ci := range cis {
   194  				if ci != nil {
   195  					baseIdx := filteredIdxList[idx]
   196  					psList[baseIdx] = ci.PropertyList
   197  				}
   198  			}
   199  		}
   200  	}
   201  
   202  	var errs []error
   203  	{ // step 3
   204  		missingIdxList := make([]int, 0, len(keys))
   205  		missingKey := make([]datastore.Key, 0, len(keys))
   206  		for idx, ps := range psList {
   207  			if ps == nil {
   208  				missingIdxList = append(missingIdxList, idx)
   209  				missingKey = append(missingKey, keys[idx])
   210  			}
   211  		}
   212  
   213  		if len(missingKey) != 0 {
   214  			cis := make([]*CacheItem, 0, len(missingKey))
   215  
   216  			missingPsList := make([]datastore.PropertyList, len(missingKey))
   217  			err := info.Next.GetMultiWithoutTx(info, missingKey, missingPsList)
   218  			if merr, ok := err.(datastore.MultiError); ok {
   219  				errs = make([]error, len(keys))
   220  				for idx, err := range merr {
   221  					baseIdx := missingIdxList[idx]
   222  					if err != nil {
   223  						errs[baseIdx] = err
   224  						continue
   225  					}
   226  					psList[baseIdx] = missingPsList[idx]
   227  					if ch.target(info.Context, missingKey[idx]) {
   228  						cis = append(cis, &CacheItem{
   229  							Key:          missingKey[idx],
   230  							PropertyList: missingPsList[idx],
   231  						})
   232  					}
   233  				}
   234  			} else if err != nil {
   235  				return err
   236  			} else {
   237  				for idx := range missingKey {
   238  					baseIdx := missingIdxList[idx]
   239  					psList[baseIdx] = missingPsList[idx]
   240  					if ch.target(info.Context, missingKey[idx]) {
   241  						cis = append(cis, &CacheItem{
   242  							Key:          missingKey[idx],
   243  							PropertyList: missingPsList[idx],
   244  						})
   245  					}
   246  				}
   247  			}
   248  
   249  			if len(cis) != 0 {
   250  				err := ch.s.SetMulti(info.Context, cis)
   251  				if err != nil {
   252  					ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.SetMulti err=%s", err.Error())
   253  				}
   254  			}
   255  		}
   256  	}
   257  
   258  	if len(errs) != 0 {
   259  		return datastore.MultiError(errs)
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  func (ch *cacheHandler) GetMultiWithTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) error {
   266  	err := info.Next.GetMultiWithTx(info, keys, psList)
   267  
   268  	// strategy summary
   269  	//   don't add to dsmiddleware in tx. It may be complicated and become a spot of bugs
   270  
   271  	ch.m.Lock()
   272  	defer ch.m.Unlock()
   273  
   274  	txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog)
   275  	if !ok {
   276  		txOpMap = make(map[datastore.Transaction][]*txOpLog)
   277  		info.Context = context.WithValue(info.Context, contextTx{}, txOpMap)
   278  	}
   279  
   280  	logs := txOpMap[info.Transaction]
   281  	for _, key := range keys {
   282  		if !ch.target(info.Context, key) {
   283  			continue
   284  		}
   285  		log := &txOpLog{
   286  			Ops: txGetOp,
   287  			Key: key,
   288  		}
   289  		logs = append(logs, log)
   290  	}
   291  	txOpMap[info.Transaction] = logs
   292  
   293  	return err
   294  }
   295  
   296  func (ch *cacheHandler) DeleteMultiWithoutTx(info *datastore.MiddlewareInfo, keys []datastore.Key) error {
   297  	err := info.Next.DeleteMultiWithoutTx(info, keys)
   298  
   299  	filteredKeys := make([]datastore.Key, 0, len(keys))
   300  	for _, key := range keys {
   301  		if !ch.target(info.Context, key) {
   302  			continue
   303  		}
   304  
   305  		filteredKeys = append(filteredKeys, key)
   306  	}
   307  	if len(filteredKeys) == 0 {
   308  		return err
   309  	}
   310  
   311  	sErr := ch.s.DeleteMulti(info.Context, filteredKeys)
   312  	if sErr != nil {
   313  		ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.DeleteMulti err=%s", sErr.Error())
   314  	}
   315  
   316  	return err
   317  }
   318  
   319  func (ch *cacheHandler) DeleteMultiWithTx(info *datastore.MiddlewareInfo, keys []datastore.Key) error {
   320  	err := info.Next.DeleteMultiWithTx(info, keys)
   321  
   322  	ch.m.Lock()
   323  	defer ch.m.Unlock()
   324  
   325  	txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog)
   326  	if !ok {
   327  		txOpMap = make(map[datastore.Transaction][]*txOpLog)
   328  		info.Context = context.WithValue(info.Context, contextTx{}, txOpMap)
   329  	}
   330  
   331  	logs := txOpMap[info.Transaction]
   332  	for _, key := range keys {
   333  		if !ch.target(info.Context, key) {
   334  			continue
   335  		}
   336  
   337  		log := &txOpLog{
   338  			Ops: txDeleteOp,
   339  			Key: key,
   340  		}
   341  		logs = append(logs, log)
   342  	}
   343  	txOpMap[info.Transaction] = logs
   344  
   345  	return err
   346  }
   347  
   348  func (ch *cacheHandler) PostCommit(info *datastore.MiddlewareInfo, tx datastore.Transaction, commit datastore.Commit) error {
   349  
   350  	ch.m.Lock()
   351  	defer ch.m.Unlock()
   352  
   353  	txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog)
   354  	if !ok {
   355  		return info.Next.PostCommit(info, tx, commit)
   356  	}
   357  
   358  	logs := txOpMap[tx]
   359  
   360  	keys := make([]datastore.Key, len(logs))
   361  	for idx, log := range logs {
   362  		switch log.Ops {
   363  		case txPutOp:
   364  			key := log.Key
   365  			if log.PendingKey != nil {
   366  				key = commit.Key(log.PendingKey)
   367  			}
   368  			keys[idx] = key
   369  
   370  		case txGetOp, txDeleteOp:
   371  			keys[idx] = log.Key
   372  		}
   373  	}
   374  
   375  	filteredKeys := make([]datastore.Key, 0, len(keys))
   376  	for _, key := range keys {
   377  		if !ch.target(info.Context, key) {
   378  			continue
   379  		}
   380  
   381  		filteredKeys = append(filteredKeys, key)
   382  	}
   383  	if len(filteredKeys) == 0 {
   384  		return info.Next.PostCommit(info, tx, commit)
   385  	}
   386  
   387  	// don't pass txCtx to appengine.APICall
   388  	// otherwise, `transaction context has expired` will be occur
   389  	baseCtx := info.Client.Context()
   390  	sErr := ch.s.DeleteMulti(baseCtx, filteredKeys)
   391  	nErr := info.Next.PostCommit(info, tx, commit)
   392  	if sErr != nil {
   393  		ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.DeleteMulti err=%s", sErr.Error())
   394  	}
   395  
   396  	return nErr
   397  }
   398  
   399  func (ch *cacheHandler) PostRollback(info *datastore.MiddlewareInfo, tx datastore.Transaction) error {
   400  	ch.m.Lock()
   401  	defer ch.m.Unlock()
   402  
   403  	txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog)
   404  	if !ok {
   405  		return info.Next.PostRollback(info, tx)
   406  	}
   407  
   408  	delete(txOpMap, tx)
   409  
   410  	return info.Next.PostRollback(info, tx)
   411  }
   412  
   413  func (ch *cacheHandler) Run(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump) datastore.Iterator {
   414  	return info.Next.Run(info, q, qDump)
   415  }
   416  
   417  func (ch *cacheHandler) GetAll(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump, psList *[]datastore.PropertyList) ([]datastore.Key, error) {
   418  	return info.Next.GetAll(info, q, qDump, psList)
   419  }
   420  
   421  func (ch *cacheHandler) Next(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump, iter datastore.Iterator, ps *datastore.PropertyList) (datastore.Key, error) {
   422  	return info.Next.Next(info, q, qDump, iter, ps)
   423  }
   424  
   425  func (ch *cacheHandler) Count(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump) (int, error) {
   426  	return info.Next.Count(info, q, qDump)
   427  }