Cloud Datastoreで出会った制限系エラー（再現コード有り）

## はじめに
GCPのCloud Datastore利用時にハマったエラーについてまとめてみました。
エラーから逆引きできるようにエラーを節名にしています。
今回GAEは使用していないのでご注意ください。

- 今回利用したdatastore用struct

```golang
type Entity struct {
	Value        string `datastore:"-"`
	NoIndexValue string `datastore:",noindex"`
	Sort         string `datastore:"-"`
}
```
---
### cannot write more than 500 entities in a single call
- 原因
	- エラーにも書いてあるように500件以上の書き込み
	- 500件以上の読み込みの際にも似たようなエラーが発生

- 対策
	- 1リクエスト500件以内に収める

- ヒトコト
	- 1リクエスト制限には件数だけでなく、リクエストサイズの制限もあるため注意

- 再現サンプルコード

```golang
func main() {
	ctx := context.Background()
	dsClient, err := datastore.NewClient(ctx, "")
	if err != nil {
		panic(err)
	}

	//num := 500 // OK 500件以内の場合はエラーが発生せず
	num := 501 // NG

	keys := make([]*datastore.Key, num)
	putEntities := make([]*Entity, num)
	for i := range keys {
		str := strconv.Itoa(i)
		keys[i] = datastore.NameKey("Entity", str, nil)
		putEntities[i] = &Entity{Value:str}
	}
	if _, err := dsClient.PutMulti(ctx, keys, putEntities); err != nil {
		// ここでエラー
		// rpc error: code = InvalidArgument desc = cannot write more than 500 entities in a single call
		panic(err)
	}
}
```
---
### operating on too many entity groups in a single transaction.
- 原因
	- 1トランザクション内で、26以上のエンティティを変更した

- 対策
	- 1トランザクション、25以内に抑える

- ヒトコト
	- エラー内容に具体的な数値が分かりにくいため注意

- 再現サンプルコード

```golang
func main() {
	ctx := context.Background()
	dsClient, err := datastore.NewClient(ctx, "")
	if err != nil {
		panic(err)
	}

	//num := 25 // OK 25件以内の場合はエラーが発生せず
	num := 26 // NG

	// データ更新
	tx, err := dsClient.NewTransaction(ctx)
	if err != nil {
		panic(err)
	}
	for i:=0; i<num; i++ {
		str := strconv.Itoa(i)
		key := datastore.NameKey("Entity", str, nil)
		e := new(Entity)
		if err := tx.Get(key, e); err != nil {
			// この箇所でエラー
			// rpc error: code = InvalidArgument desc = operating on too many entity groups in a single transaction.
			panic(err)
		}
		e.Value = e.Value + "update"
		if _, err := tx.Put(key, e); err != nil {
			panic(err)
		}
	}
	if _, err := tx.Commit(); err!=nil {
		panic(err)
	}
}
```
---
### datastore: string property too long to index for a Property with Name "XXX"
- 原因
	- インデックス有りの項目に対し、1500 Byteを超えて書き込もうとした

- 対策
	- 1500Byte以内に抑える
	- インデックス無しの項目に変更する

```golang
Value string `datastore:",noindex"`
```

- ヒトコト
	- これもエラー内容に具体的な数値が分かりにくいため注意

- 再現サンプルコード

```golang
func main() {
	ctx := context.Background()
	dsClient, err := datastore.NewClient(ctx, "")
	if err != nil {
		panic(err)
	}

	strLength := 1501 // NG
	//strLength := 1500 // OK

	k := datastore.NameKey("Entity", "stringID", nil)
	e := &Entity{
		Value: strings.Repeat(" ", strLength),
	}

	if _, err := dsClient.Put(ctx, k, e); err != nil {
		// datastore: string property too long to index for a Property with Name "Value"
		panic(err)
	}
}
```

---
### The value of property "Value" is longer than 1048487 bytes.
- 原因
	- 1つの項目に1MBを超えて書き込もうとした

- 対策
	- 1MB以内に抑える

- ヒトコト
	- datastoreのエミュレータではこのエラーなく登録できるので注意

- 再現サンプルコード

```golang
func main() {
	ctx := context.Background()
	dsClient, err := datastore.NewClient(ctx, "")
	if err != nil {
		panic(err)
	}

	//strLength := 1048488 // NG
	strLength := 1048400 // OK

	k := datastore.NameKey("Entity", "stringID", nil)
	e := &Entity{
		NoIndexValue: strings.Repeat(" ", strLength),
	}

	if _, err := dsClient.Put(ctx, k, e); err != nil {
		// rpc error: code = InvalidArgument desc = The value of property "Value" is longer than 1048487 bytes.
		panic(err)
	}
}
```
---
### no matching index found.
- 原因
	- 複数のkeyでのソートや、絞り込みとソートを行った

- 対策
	- 複合インデックスを追加(`gcloud datastore create-indexes`コマンドを実行)
		- https://cloud.google.com/datastore/docs/concepts/indexes

- ヒトコト
	- Datastoreエミュレータを利用している場合には `WEB-INF/index.yaml` に複合インデックス用の定義ファイルが出力される

- 再現サンプルコード

```golang
func main() {
	ctx := context.Background()
	dsClient, err := datastore.NewClient(ctx, "")
	if err != nil {
		panic(err)
	}

	q := datastore.NewQuery("Entity").
		Filter("Value =", "1").
		Order("Sort")

	var resultEntities []*Entity
	if _, err := dsClient.GetAll(ctx, q, &resultEntities); err != nil {
		// rpc error: code = FailedPrecondition desc = no matching index found. recommended index is:
		// - kind: Entity
		//   properties:
		//   - name: Value
		//   - name: Sort
		panic(err)
	}

	for _, entity := range resultEntities {
		fmt.Printf("%#v\n", entity)
	}
}
```

```golang
func main() {
	ctx := context.Background()
	dsClient, err := datastore.NewClient(ctx, "")
	if err != nil {
		panic(err)
	}

	q := datastore.NewQuery("Entity").
		Order("Sort").
		Order("Value")

	var resultEntities []*Entity
	if _, err := dsClient.GetAll(ctx, q, &resultEntities); err != nil {
		// rpc error: code = FailedPrecondition desc = no matching index found. recommended index is:
		// - kind: Entity
		//   properties:
		//   - name: Sort
		//   - name: Value
		panic(err)
	}

	for _, entity := range resultEntities {
		fmt.Printf("%#v\n", entity)
	}
}
```
---
### さいごに
エラーを見ただけでは直感的には分かりにくく、解決に時間がかかることもあったためまとめてみました。
同じエラーに遭遇した際に、本記事を見ることで少しでも助けになれば幸いです。