Example #1
0
func TestGetMultiLockReturnEntitySetValueFail(t *testing.T) {
	c, closeFunc := NewContext(t)
	defer closeFunc()

	type testEntity struct {
		IntVal int64
	}

	keys := []*datastore.Key{}
	entities := []testEntity{}
	for i := int64(1); i < 3; i++ {
		keys = append(keys, datastore.NewKey(c, "Entity", "", i, nil))
		entities = append(entities, testEntity{i})
	}

	if _, err := nds.PutMulti(c, keys, entities); err != nil {
		t.Fatal(err)
	}

	// Fail to unmarshal test.
	memcacheGetChan := make(chan func(c context.Context, keys []string) (
		map[string]*memcache.Item, error), 2)
	memcacheGetChan <- memcache.GetMulti
	memcacheGetChan <- func(c context.Context,
		keys []string) (map[string]*memcache.Item, error) {
		items, err := memcache.GetMulti(c, keys)
		if err != nil {
			return nil, err
		}
		pl := datastore.PropertyList{
			datastore.Property{"One", 1, false, false},
		}
		value, err := nds.MarshalPropertyList(pl)
		if err != nil {
			return nil, err
		}
		items[keys[0]].Flags = nds.EntityItem
		items[keys[0]].Value = value
		items[keys[1]].Flags = nds.EntityItem
		items[keys[1]].Value = value
		return items, nil
	}
	nds.SetMemcacheGetMulti(func(c context.Context,
		keys []string) (map[string]*memcache.Item, error) {
		f := <-memcacheGetChan
		return f(c, keys)
	})

	response := make([]testEntity, len(keys))
	if err := nds.GetMulti(c, keys, response); err != nil {
		t.Fatal(err)
	}
	defer nds.SetMemcacheGetMulti(memcache.GetMulti)

	for i := 0; i < len(keys); i++ {
		if entities[i].IntVal != response[i].IntVal {
			t.Fatal("IntVal not equal")
		}
	}
}
Example #2
0
// GetMulti fetches dst ([]Kind or []*Kind) by multiple keys
func (d *Driver) GetMulti(keys []string, dst interface{}) error {
	var errors = make([]error, 0)
	hits, err := memcache.GetMulti(d.ctx, keys)
	if err == nil {
		dstValue := reflect.ValueOf(dst)
		for i, key := range keys {
			if item, ok := hits[key]; ok {
				objValue := dstValue.Index(i)
				if objValue.Kind() == reflect.Ptr {
					if string(item.Value) == jsonNullValue {
						continue
					}
					obj := reflect.New(objValue.Type().Elem()).Interface()
					if err = json.Unmarshal(item.Value, obj); err != nil {
						errors = append(errors, fmt.Errorf("Could not unmarshal JSON: %v", err))
						continue
					}
					objValue.Set(reflect.ValueOf(obj))
				} else {
					obj := objValue.Interface()
					if err = json.Unmarshal(item.Value, &obj); err != nil {
						errors = append(errors, fmt.Errorf("Could not unmarshal JSON: %v", err))
						continue
					}
				}
			}
		}
		if len(errors) > 0 {
			return fmt.Errorf("MultiError: %v", errors)
		}
		return nil
	}
	return err
}
Example #3
0
// bool means 'found'
func BytesFromShardedMemcache(c context.Context, key string) ([]byte, bool) {
	keys := []string{}
	for i := 0; i < 32; i++ {
		keys = append(keys, fmt.Sprintf("=%d=%s", i*chunksize, key))
	}

	if items, err := memcache.GetMulti(c, keys); err != nil {
		log.Errorf(c, "fdb memcache multiget: %v", err)
		return nil, false

	} else {
		b := []byte{}
		for i := 0; i < 32; i++ {
			if item, exists := items[keys[i]]; exists == false {
				break
			} else {
				log.Infof(c, " #=== Found '%s' !", item.Key)
				b = append(b, item.Value...)
			}
		}

		log.Infof(c, " #=== Final read len: %d", len(b))

		if len(b) > 0 {
			return b, true
		} else {
			return nil, false
		}
	}
}
Example #4
0
func TestGetMultiLockReturnUnknown(t *testing.T) {
	c, closeFunc := NewContext(t)
	defer closeFunc()

	type testEntity struct {
		IntVal int64
	}

	keys := []*datastore.Key{}
	entities := []testEntity{}
	for i := int64(1); i < 3; i++ {
		keys = append(keys, datastore.NewKey(c, "Entity", "", i, nil))
		entities = append(entities, testEntity{i})
	}

	if _, err := nds.PutMulti(c, keys, entities); err != nil {
		t.Fatal(err)
	}

	memcacheGetChan := make(chan func(c context.Context, keys []string) (
		map[string]*memcache.Item, error), 2)
	memcacheGetChan <- memcache.GetMulti
	memcacheGetChan <- func(c context.Context,
		keys []string) (map[string]*memcache.Item, error) {
		items, err := memcache.GetMulti(c, keys)
		if err != nil {
			return nil, err
		}

		// Unknown lock values.
		items[keys[0]].Flags = 23
		items[keys[1]].Flags = 24
		return items, nil
	}
	nds.SetMemcacheGetMulti(func(c context.Context,
		keys []string) (map[string]*memcache.Item, error) {
		f := <-memcacheGetChan
		return f(c, keys)
	})

	response := make([]testEntity, len(keys))
	if err := nds.GetMulti(c, keys, response); err != nil {
		t.Fatal(err)
	}
	defer nds.SetMemcacheGetMulti(memcache.GetMulti)

	for i := 0; i < len(keys); i++ {
		if entities[i].IntVal != response[i].IntVal {
			t.Fatal("IntVal not equal")
		}
	}
}
Example #5
0
func (m mcImpl) GetMulti(keys []string, cb mc.RawItemCB) error {
	realItems, err := memcache.GetMulti(m.aeCtx, keys)
	if err != nil {
		return err
	}
	for _, k := range keys {
		itm := realItems[k]
		if itm == nil {
			cb(nil, memcache.ErrCacheMiss)
		} else {
			cb(mcItem{itm}, nil)
		}
	}
	return nil
}
Example #6
0
func TestGetMultiPaths(t *testing.T) {
	expectedErr := errors.New("expected error")

	type memcacheGetMultiFunc func(c context.Context,
		keys []string) (map[string]*memcache.Item, error)
	memcacheGetMultiFail := func(c context.Context,
		keys []string) (map[string]*memcache.Item, error) {
		return nil, expectedErr
	}

	type memcacheAddMultiFunc func(c context.Context,
		items []*memcache.Item) error
	memcacheAddMultiFail := func(c context.Context,
		items []*memcache.Item) error {
		return expectedErr
	}

	type memcacheCompareAndSwapMultiFunc func(c context.Context,
		items []*memcache.Item) error
	memcacheCompareAndSwapMultiFail := func(c context.Context,
		items []*memcache.Item) error {
		return expectedErr
	}

	type datastoreGetMultiFunc func(c context.Context,
		keys []*datastore.Key, vals interface{}) error
	datastoreGetMultiFail := func(c context.Context,
		keys []*datastore.Key, vals interface{}) error {
		return expectedErr
	}

	type marshalFunc func(pl datastore.PropertyList) ([]byte, error)
	marshalFail := func(pl datastore.PropertyList) ([]byte, error) {
		return nil, expectedErr
	}

	type unmarshalFunc func(data []byte, pl *datastore.PropertyList) error
	/*
	   unmarshalFail := func(data []byte, pl *datastore.PropertyList) error {
	       return expectedErr
	   }
	*/

	c, closeFunc := NewContext(t)
	defer closeFunc()

	type testEntity struct {
		IntVal int64
	}

	keysVals := func(c context.Context, count int64) (
		[]*datastore.Key, []testEntity) {

		keys, vals := make([]*datastore.Key, count), make([]testEntity, count)
		for i := int64(0); i < count; i++ {
			keys[i] = datastore.NewKey(c, "Entity", "", i+1, nil)
			vals[i] = testEntity{i + 1}
		}
		return keys, vals
	}

	tests := []struct {
		description string

		// Number of keys used to as GetMulti params.
		keyCount int64

		// Number of times GetMulti is called.
		callCount int

		// There are 2 memcacheGetMulti calls for every GetMulti call.
		memcacheGetMultis           []memcacheGetMultiFunc
		memcacheAddMulti            memcacheAddMultiFunc
		memcacheCompareAndSwapMulti memcacheCompareAndSwapMultiFunc

		datastoreGetMulti datastoreGetMultiFunc

		marshal marshalFunc
		// There are 2 unmarshal calls for every GetMultiCall.
		unmarshals []unmarshalFunc

		expectedErrs []error
	}{
		{
			"no errors",
			20,
			1,
			[]memcacheGetMultiFunc{
				memcache.GetMulti,
				memcache.GetMulti,
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			datastore.GetMulti,
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{nil},
		},
		{
			"datastore unknown error",
			2,
			1,
			[]memcacheGetMultiFunc{
				memcache.GetMulti,
				memcache.GetMulti,
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			datastoreGetMultiFail,
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{expectedErr},
		},
		{
			"datastore unknown multierror",
			2,
			1,
			[]memcacheGetMultiFunc{
				memcache.GetMulti,
				memcache.GetMulti,
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			func(c context.Context,
				keys []*datastore.Key, vals interface{}) error {

				me := make(appengine.MultiError, len(keys))
				for i := range me {
					me[i] = expectedErr
				}
				return me
			},
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{
				appengine.MultiError{expectedErr, expectedErr},
			},
		},
		{
			"marshal error",
			5,
			1,
			[]memcacheGetMultiFunc{
				memcache.GetMulti,
				memcache.GetMulti,
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			datastore.GetMulti,
			marshalFail,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{nil},
		},
		{
			"total memcache fail",
			20,
			1,
			[]memcacheGetMultiFunc{
				memcacheGetMultiFail,
				memcacheGetMultiFail,
			},
			memcacheAddMultiFail,
			memcacheCompareAndSwapMultiFail,
			datastore.GetMulti,
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{nil},
		},
		{
			"lock memcache fail",
			20,
			1,
			[]memcacheGetMultiFunc{
				memcache.GetMulti,
				memcacheGetMultiFail,
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			datastore.GetMulti,
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{nil},
		},
		{
			"memcache corrupt",
			2,
			2,
			[]memcacheGetMultiFunc{
				// Charge memcache.
				memcache.GetMulti,
				memcache.GetMulti,
				// Corrupt memcache.
				func(c context.Context, keys []string) (
					map[string]*memcache.Item, error) {
					items, err := memcache.GetMulti(c, keys)
					// Corrupt items.
					for _, item := range items {
						item.Value = []byte("corrupt string")
					}
					return items, err
				},
				memcache.GetMulti,
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			datastore.GetMulti,
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{nil, nil},
		},
		{
			"memcache flag corrupt",
			2,
			2,
			[]memcacheGetMultiFunc{
				// Charge memcache.
				memcache.GetMulti,
				memcache.GetMulti,
				// Corrupt memcache flags.
				func(c context.Context, keys []string) (
					map[string]*memcache.Item, error) {
					items, err := memcache.GetMulti(c, keys)
					// Corrupt flags with unknown number.
					for _, item := range items {
						item.Flags = 56
					}
					return items, err
				},
				memcache.GetMulti,
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			datastore.GetMulti,
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{nil, nil},
		},
		{
			"lock memcache value fail",
			20,
			1,
			[]memcacheGetMultiFunc{
				memcache.GetMulti,
				func(c context.Context, keys []string) (
					map[string]*memcache.Item, error) {
					items, err := memcache.GetMulti(c, keys)
					// Corrupt flags with unknown number.
					for _, item := range items {
						item.Value = []byte("corrupt value")
					}
					return items, err
				},
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			datastore.GetMulti,
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{nil},
		},
		{
			"lock memcache value none item",
			2,
			1,
			[]memcacheGetMultiFunc{
				memcache.GetMulti,
				func(c context.Context, keys []string) (
					map[string]*memcache.Item, error) {
					items, err := memcache.GetMulti(c, keys)
					// Corrupt flags with unknown number.
					for _, item := range items {
						item.Flags = nds.NoneItem
					}
					return items, err
				},
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			datastore.GetMulti,
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{
				appengine.MultiError{
					datastore.ErrNoSuchEntity,
					datastore.ErrNoSuchEntity,
				},
			},
		},
		{
			"memcache get no entity unmarshal fail",
			2,
			1,
			[]memcacheGetMultiFunc{
				memcache.GetMulti,
				func(c context.Context, keys []string) (
					map[string]*memcache.Item, error) {
					items, err := memcache.GetMulti(c, keys)
					// Corrupt flags with unknown number.
					for _, item := range items {
						item.Flags = nds.EntityItem
					}
					return items, err
				},
			},
			memcache.AddMulti,
			memcache.CompareAndSwapMulti,
			datastore.GetMulti,
			nds.MarshalPropertyList,
			[]unmarshalFunc{
				nds.UnmarshalPropertyList,
				nds.UnmarshalPropertyList,
			},
			[]error{nil},
		},
	}

	for _, test := range tests {
		t.Log("Start", test.description)

		keys, putVals := keysVals(c, test.keyCount)
		if _, err := nds.PutMulti(c, keys, putVals); err != nil {
			t.Fatal(err)
		}

		memcacheGetChan := make(chan memcacheGetMultiFunc,
			len(test.memcacheGetMultis))

		for _, fn := range test.memcacheGetMultis {
			memcacheGetChan <- fn
		}

		nds.SetMemcacheGetMulti(func(c context.Context, keys []string) (
			map[string]*memcache.Item, error) {
			fn := <-memcacheGetChan
			return fn(c, keys)
		})

		nds.SetMemcacheAddMulti(test.memcacheAddMulti)
		nds.SetMemcacheCompareAndSwapMulti(test.memcacheCompareAndSwapMulti)

		nds.SetDatastoreGetMulti(test.datastoreGetMulti)

		nds.SetMarshal(test.marshal)

		unmarshalChan := make(chan unmarshalFunc,
			len(test.unmarshals))

		for _, fn := range test.unmarshals {
			unmarshalChan <- fn
		}

		nds.SetUnmarshal(func(data []byte, pl *datastore.PropertyList) error {
			fn := <-unmarshalChan
			return fn(data, pl)
		})

		for i := 0; i < test.callCount; i++ {
			getVals := make([]testEntity, test.keyCount)
			err := nds.GetMulti(c, keys, getVals)

			expectedErr := test.expectedErrs[i]

			if expectedErr == nil {
				if err != nil {
					t.Fatal(err)
				}

				for i := range getVals {
					if getVals[i].IntVal != putVals[i].IntVal {
						t.Fatal("incorrect IntVal")
					}
				}
				continue
			}

			if err == nil {
				t.Fatal("expected error")
			}
			expectedMultiErr, isMultiErr := expectedErr.(appengine.MultiError)

			if isMultiErr {
				me, ok := err.(appengine.MultiError)
				if !ok {
					t.Fatal("expected appengine.MultiError but got", err)
				}

				if len(me) != len(expectedMultiErr) {
					t.Fatal("appengine.MultiError length incorrect")
				}

				for i, e := range me {
					if e != expectedMultiErr[i] {
						t.Fatal("non matching errors", e, expectedMultiErr[i])
					}

					if e == nil {
						if getVals[i].IntVal != putVals[i].IntVal {
							t.Fatal("incorrect IntVal")
						}
					}
				}
			}
		}

		// Reset App Engine API calls.
		nds.SetMemcacheGetMulti(memcache.GetMulti)
		nds.SetMemcacheAddMulti(memcache.AddMulti)
		nds.SetMemcacheCompareAndSwapMulti(memcache.CompareAndSwapMulti)
		nds.SetDatastoreGetMulti(datastore.GetMulti)
		nds.SetMarshal(nds.MarshalPropertyList)
		nds.SetUnmarshal(nds.UnmarshalPropertyList)

		if err := nds.DeleteMulti(c, keys); err != nil {
			t.Fatal(err)
		}
		t.Log("End", test.description)
	}
}
Example #7
0
func index(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	keys := make([]string, modulus)
	for i := range keys {
		keys[i] = fmt.Sprintf(keyPart, i*distance)
	}

	items, err := memcache.GetMulti(ctx, keys)
	if err != nil {
		return
	}

	ars := allrequestStats{}
	for _, v := range items {
		t := statsPart{}
		err := gob.NewDecoder(bytes.NewBuffer(v.Value)).Decode(&t)
		if err != nil {
			continue
		}
		r := requestStats(t)
		ars = append(ars, &r)
	}
	sort.Sort(reverse{ars})

	requestByID := make(map[int]*requestStats, len(ars))
	idByRequest := make(map[*requestStats]int, len(ars))
	requests := make(map[int]*statByName)
	byRequest := make(map[int]map[string]cVal)
	for i, v := range ars {
		idx := i + 1
		requestByID[idx] = v
		idByRequest[v] = idx
		requests[idx] = &statByName{
			RequestStats: v,
		}
		byRequest[idx] = make(map[string]cVal)
	}

	requestByPath := make(map[string][]int)
	byCount := make(map[string]cVal)
	byRPC := make(map[skey]cVal)
	for _, t := range ars {
		id := idByRequest[t]

		requestByPath[t.Path] = append(requestByPath[t.Path], id)

		for _, r := range t.RPCStats {
			rpc := r.Name()

			v := byRequest[id][rpc]
			v.count++
			v.cost += r.Cost
			byRequest[id][rpc] = v

			v = byCount[rpc]
			v.count++
			v.cost += r.Cost
			byCount[rpc] = v

			v = byRPC[skey{rpc, t.Path}]
			v.count++
			v.cost += r.Cost
			byRPC[skey{rpc, t.Path}] = v
		}
	}

	for k, v := range byRequest {
		stats := statsByName{}
		for rpc, s := range v {
			stats = append(stats, &statByName{
				Name:  rpc,
				Count: s.count,
				Cost:  s.cost,
			})
		}
		sort.Sort(reverse{stats})
		requests[k].SubStats = stats
	}

	statsByRPC := make(map[string]statsByName)
	pathStats := make(map[string]statsByName)
	for k, v := range byRPC {
		statsByRPC[k.a] = append(statsByRPC[k.a], &statByName{
			Name:  k.b,
			Count: v.count,
			Cost:  v.cost,
		})
		pathStats[k.b] = append(pathStats[k.b], &statByName{
			Name:  k.a,
			Count: v.count,
			Cost:  v.cost,
		})
	}
	for k, v := range statsByRPC {
		sort.Sort(reverse{v})
		statsByRPC[k] = v
	}

	pathStatsByCount := statsByName{}
	for k, v := range pathStats {
		total := 0
		var cost int64
		for _, stat := range v {
			total += stat.Count
			cost += stat.Cost
		}
		sort.Sort(reverse{v})

		pathStatsByCount = append(pathStatsByCount, &statByName{
			Name:       k,
			Count:      total,
			Cost:       cost,
			SubStats:   v,
			Requests:   len(requestByPath[k]),
			RecentReqs: requestByPath[k],
		})
	}
	sort.Sort(reverse{pathStatsByCount})

	allStatsByCount := statsByName{}
	for k, v := range byCount {
		allStatsByCount = append(allStatsByCount, &statByName{
			Name:     k,
			Count:    v.count,
			Cost:     v.cost,
			SubStats: statsByRPC[k],
		})
	}
	sort.Sort(reverse{allStatsByCount})

	v := struct {
		Env                 map[string]string
		Requests            map[int]*statByName
		RequestStatsByCount map[int]*statByName
		AllStatsByCount     statsByName
		PathStatsByCount    statsByName
	}{
		Env: map[string]string{
			"APPLICATION_ID": appengine.AppID(ctx),
		},
		Requests:         requests,
		AllStatsByCount:  allStatsByCount,
		PathStatsByCount: pathStatsByCount,
	}

	_ = templates.ExecuteTemplate(w, "main", v)
}
Example #8
0
// GetMulti is a batch version of Get.
//
// dst must be a *[]S, *[]*S, *[]I, []S, []*S, or []I, for some struct type S,
// or some interface type I. If *[]I or []I, each element must be a struct pointer.
func (g *Goon) GetMulti(dst interface{}) error {
	keys, err := g.extractKeys(dst, false) // don't allow incomplete keys on a Get request
	if err != nil {
		return err
	}

	v := reflect.Indirect(reflect.ValueOf(dst))

	if g.inTransaction {
		// todo: support getMultiLimit in transactions
		return datastore.GetMulti(g.Context, keys, v.Interface())
	}

	var dskeys []*datastore.Key
	var dsdst []interface{}
	var dixs []int

	var memkeys []string
	var mixs []int

	g.cacheLock.RLock()
	for i, key := range keys {
		m := memkey(key)
		vi := v.Index(i)

		if vi.Kind() == reflect.Struct {
			vi = vi.Addr()
		}

		if s, present := g.cache[m]; present {
			if vi.Kind() == reflect.Interface {
				vi = vi.Elem()
			}

			reflect.Indirect(vi).Set(reflect.Indirect(reflect.ValueOf(s)))
		} else {
			memkeys = append(memkeys, m)
			mixs = append(mixs, i)
			dskeys = append(dskeys, key)
			dsdst = append(dsdst, vi.Interface())
			dixs = append(dixs, i)
		}
	}
	g.cacheLock.RUnlock()

	if len(memkeys) == 0 {
		return nil
	}

	multiErr, any := make(appengine.MultiError, len(keys)), false

	tc, cf := context.WithTimeout(g.Context, MemcacheGetTimeout)
	memvalues, err := memcache.GetMulti(tc, memkeys)
	cf()
	if appengine.IsTimeoutError(err) {
		g.timeoutError(err)
		err = nil
	} else if err != nil {
		g.error(err) // timing out or another error from memcache isn't something to fail over, but do log it
		// No memvalues found, prepare the datastore fetch list already prepared above
	} else if len(memvalues) > 0 {
		// since memcache fetch was successful, reset the datastore fetch list and repopulate it
		dskeys = dskeys[:0]
		dsdst = dsdst[:0]
		dixs = dixs[:0]
		// we only want to check the returned map if there weren't any errors
		// unlike the datastore, memcache will return a smaller map with no error if some of the keys were missed

		for i, m := range memkeys {
			d := v.Index(mixs[i]).Interface()
			if v.Index(mixs[i]).Kind() == reflect.Struct {
				d = v.Index(mixs[i]).Addr().Interface()
			}
			if s, present := memvalues[m]; present {
				err := deserializeStruct(d, s.Value)
				if err == datastore.ErrNoSuchEntity {
					any = true // this flag tells GetMulti to return multiErr later
					multiErr[mixs[i]] = err
				} else if err != nil {
					g.error(err)
					return err
				} else {
					g.putMemory(d)
				}
			} else {
				dskeys = append(dskeys, keys[mixs[i]])
				dsdst = append(dsdst, d)
				dixs = append(dixs, mixs[i])
			}
		}
		if len(dskeys) == 0 {
			if any {
				return realError(multiErr)
			}
			return nil
		}
	}

	goroutines := (len(dskeys)-1)/getMultiLimit + 1
	var wg sync.WaitGroup
	wg.Add(goroutines)
	for i := 0; i < goroutines; i++ {
		go func(i int) {
			defer wg.Done()
			var toCache []interface{}
			var exists []byte
			lo := i * getMultiLimit
			hi := (i + 1) * getMultiLimit
			if hi > len(dskeys) {
				hi = len(dskeys)
			}
			gmerr := datastore.GetMulti(g.Context, dskeys[lo:hi], dsdst[lo:hi])
			if gmerr != nil {
				any = true // this flag tells GetMulti to return multiErr later
				merr, ok := gmerr.(appengine.MultiError)
				if !ok {
					g.error(gmerr)
					for j := lo; j < hi; j++ {
						multiErr[j] = gmerr
					}
					return
				}
				for i, idx := range dixs[lo:hi] {
					if merr[i] == nil {
						toCache = append(toCache, dsdst[lo+i])
						exists = append(exists, 1)
					} else {
						if merr[i] == datastore.ErrNoSuchEntity {
							toCache = append(toCache, dsdst[lo+i])
							exists = append(exists, 0)
						}
						multiErr[idx] = merr[i]
					}
				}
			} else {
				toCache = append(toCache, dsdst[lo:hi]...)
				exists = append(exists, bytes.Repeat([]byte{1}, hi-lo)...)
			}
			if len(toCache) > 0 {
				if err := g.putMemcache(toCache, exists); err != nil {
					g.error(err)
					// since putMemcache() gives no guarantee it will actually store the data in memcache
					// we log and swallow this error
				}

			}
		}(i)
	}
	wg.Wait()
	if any {
		return realError(multiErr)
	}
	return nil
}