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 }