github.com/pbberlin/tools@v0.0.0-20160910141205-7aa5421c2169/dsu/distributed_unancestored/sharded_counter.go (about)

     1  package distributed_unancestored
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  
     7  	"github.com/pbberlin/tools/dsu"
     8  	"github.com/pbberlin/tools/util"
     9  	"golang.org/x/net/context"
    10  	"google.golang.org/appengine/datastore"
    11  	"google.golang.org/appengine/memcache"
    12  
    13  	"time"
    14  
    15  	aelog "google.golang.org/appengine/log"
    16  )
    17  
    18  var updateSamplingFrequency = map[string]int{}
    19  var ll = 0
    20  
    21  const (
    22  	defaultNumShards = 4
    23  	// data store "entity kind" - storing number of shards
    24  	//	 - equal for all counter names
    25  	dsKindNumShards = "ShardsNumber"
    26  )
    27  
    28  const (
    29  	// data store "entity kind" - storing one part of a counter value
    30  	// 		differentiated by its fields Name, ShardId
    31  	dsKindShard = "ShardData"
    32  	batchSize   = 11
    33  )
    34  
    35  type WrapShardData struct {
    36  	Name    string // to which variable - i.e. "/guestbook/list" the value belongs; needs query index
    37  	ShardId int    // partition id from "sh001" to "sh999" - could also be an int
    38  	I       int    // The value
    39  }
    40  
    41  // memcache key for number of shards
    42  func mcKeyShardsTotal(valName string) string {
    43  	return dsKindNumShards + "__" + valName
    44  }
    45  
    46  // memcache key for the value of valName
    47  func mcKey(valName string) string {
    48  	return dsKindShard + "__" + valName
    49  }
    50  
    51  //  datastore key for a single shard
    52  //  We want an equal distribuation of the keys.
    53  //  We want to avoid "clustering" of datastore "tablet servers"
    54  //  But the mapping still needs to be deterministic
    55  func keySingleShard(valName string, shardKey int) string {
    56  	prefix := ""
    57  	iter := shardKey
    58  	for {
    59  		mod := iter % 24
    60  		r1 := mod + 'a'
    61  		prefix += fmt.Sprintf("%c", r1)
    62  		iter = iter / 24
    63  		if iter < 24 {
    64  			break
    65  		}
    66  	}
    67  	return prefix + "__" + valName + "__" + util.Itos(shardKey)
    68  }
    69  
    70  // Count retrieves the value of the named counter.
    71  // Either from memcache - or from datastore
    72  func Count(c context.Context, valName string) (retVal int, err error) {
    73  
    74  	wi := dsu.WrapInt{}
    75  	errMc := dsu.McacheGet(c, mcKey(valName), &wi)
    76  	if errMc == false {
    77  		aelog.Errorf(c, "%v", errMc)
    78  	}
    79  	retVal = wi.I
    80  	if retVal > 0 {
    81  		if ll > 2 {
    82  			aelog.Infof(c, "found counter %s = %v in memcache; return", mcKey(valName), wi.I)
    83  		}
    84  		retVal = 0
    85  	}
    86  
    87  Loop1:
    88  	for j := 0; j < 1333; j++ {
    89  
    90  		q := datastore.NewQuery(dsKindShard)
    91  
    92  		q = q.Filter("Name =", valName)
    93  
    94  		// because we have "hashed" the keys, we can no longer
    95  		// range query them by key -
    96  		//q = q.Filter("__key__ >=", valName+shardId )
    97  		//q = q.Filter("__key__ < ",stringspb.IncrementString(valName+shardId) )
    98  
    99  		q = q.Order("Name")
   100  		q = q.Order("-ShardId")
   101  		q = q.Limit(-1)
   102  		q = q.Limit(batchSize)
   103  		q = q.Offset(j * batchSize)
   104  		cntr := 0
   105  		iter := q.Run(c)
   106  		for {
   107  			var sd WrapShardData
   108  			_, err = iter.Next(&sd)
   109  
   110  			if err == datastore.Done {
   111  				if ll > 2 {
   112  					aelog.Infof(c, "       No Results (any more)  %v", err)
   113  				}
   114  				err = nil
   115  				if cntr == 0 {
   116  					if ll > 2 {
   117  						aelog.Infof(c, "  Leaving Loop1")
   118  					}
   119  					break Loop1
   120  				}
   121  				break
   122  			}
   123  			cntr++
   124  			retVal += sd.I
   125  			if ll > 2 {
   126  				aelog.Infof(c, "        %2vth shard: %v %v %4v - %4v", cntr, sd.Name, sd.ShardId, sd.I, retVal)
   127  			}
   128  		}
   129  		if ll > 2 {
   130  			aelog.Infof(c, "   %2v shards found - sum %4v", cntr, retVal)
   131  		}
   132  
   133  	}
   134  
   135  	dsu.McacheSet(c, mcKey(valName), retVal)
   136  	return
   137  
   138  }
   139  
   140  // Increment increments the named counter.
   141  func Increment(c context.Context, valName string) error {
   142  
   143  	// Get counter config.
   144  	shardsTotal := dsu.WrapInt{}
   145  	dsu.McacheGet(c, mcKeyShardsTotal(valName), &shardsTotal)
   146  	if shardsTotal.I < 1 {
   147  		ckey := datastore.NewKey(c, dsKindNumShards, mcKeyShardsTotal(valName), 0, nil)
   148  		errTx := datastore.RunInTransaction(c,
   149  			func(c context.Context) error {
   150  				err := datastore.Get(c, ckey, &shardsTotal)
   151  				if err == datastore.ErrNoSuchEntity {
   152  					shardsTotal.I = defaultNumShards
   153  					_, err = datastore.Put(c, ckey, &shardsTotal)
   154  				}
   155  				return err
   156  			}, nil)
   157  		if errTx != nil {
   158  			return errTx
   159  		}
   160  		dsu.McacheSet(c, mcKeyShardsTotal(valName), dsu.WrapInt{shardsTotal.I})
   161  	}
   162  
   163  	// pick random counter and increment it
   164  	errTx := datastore.RunInTransaction(c,
   165  		func(c context.Context) error {
   166  			shardId := rand.Intn(shardsTotal.I)
   167  			dsKey := datastore.NewKey(c, dsKindShard, keySingleShard(valName, shardId), 0, nil)
   168  			var sd WrapShardData
   169  			err := datastore.Get(c, dsKey, &sd)
   170  			// A missing entity and a present entity will both work.
   171  			if err != nil && err != datastore.ErrNoSuchEntity {
   172  				return err
   173  			}
   174  			sd.Name = valName
   175  			sd.ShardId = shardId
   176  			sd.I++
   177  			_, err = datastore.Put(c, dsKey, &sd)
   178  			if ll > 2 {
   179  				aelog.Infof(c, "ds put %v %v", dsKey, sd)
   180  			}
   181  			return err
   182  		}, nil)
   183  	if errTx != nil {
   184  		return errTx
   185  	}
   186  
   187  	memcache.Increment(c, mcKey(valName), 1, 0)
   188  
   189  	// collect number of updates
   190  	//    per valName per instance in memory
   191  	//    for every interval of 10 minutes
   192  	//
   193  	//  a batch job checks if the number of shards should be increased or decreased
   194  	//    and truncates this map
   195  	updateSamplingFrequency[valName+util.TimeMarker()[:len("2006-01-02 15:0")]] += 1
   196  
   197  	return nil
   198  }
   199  
   200  // AdjustShards increases the number of shards for the named counter to n.
   201  // It will never decrease the number of shards.
   202  func AdjustShards(c context.Context, valName string, n int) error {
   203  	ckey := datastore.NewKey(c, dsKindNumShards, valName, 0, nil)
   204  	return datastore.RunInTransaction(c, func(c context.Context) error {
   205  		shardsTotal := dsu.WrapInt{}
   206  		mod := false
   207  		err := datastore.Get(c, ckey, &shardsTotal)
   208  		if err == datastore.ErrNoSuchEntity {
   209  			shardsTotal.I = n
   210  			mod = true
   211  		} else if err != nil {
   212  			return err
   213  		}
   214  		if shardsTotal.I < n {
   215  			shardsTotal.I = n
   216  			mod = true
   217  		}
   218  		if mod {
   219  			_, err = datastore.Put(c, ckey, &shardsTotal)
   220  		}
   221  		return err
   222  	}, nil)
   223  }
   224  
   225  func init() {
   226  	rand.Seed(time.Now().UnixNano())
   227  }