GAEのDatastore(Cloud Datastore)の自動採番はプロジェクト移行したら初期化される？

***追記＆注意(2017/10/25):***
本記事内でauto_id_policy=legacyで実験して発生した問題を提起していますが、もしかしたらscatteredだと問題が発生しないのではないか、という疑惑が生じています。scatteredで実験するよい方法が思いつかないので、とりいそぎ追記のみ（無責任でごめんなさい）

***追記＆注意(2017/11/29):***
新しく追加された[Exporting and Importing Entities](https://cloud.google.com/datastore/docs/export-import-entities)使うとID衝突防げるのでは？という噂。未検証です。情報求ム。

-----

近々Google Cloud Platform東京リージョンが発表されそうですね。
どうやらGAEやCloud Datastoreも対応されそう。＼(^o^)／

東京リージョンが正式公開されたら既存のGAEアプリケーション＆Datastoreを移行したい人も多いと思います。
・・・が、ここでちょっと一つだけ気になることが。。

***ID自動採番（またはallocate_ids）を利用しているアプリケーション＆Datastoreを新しいプロジェクトに移行した場合、移行先プロジェクトでは[Id sequences table](https://cloud.google.com/appengine/articles/storage_breakdown#anc-idsequencestable)がリセットされてしまい、Keyが競合してしまうのではないか？***

ということで実験してみました。

## 実験その１

まず、自動採番が「現在保存されていないKeyから自動的に探してきて払い出してくれる」機能なのかどうか、を検証します。
もしそうならばプロジェクト間移行しても問題ないはず。。

### 作戦

（かなり姑息ですが・・）自動採番が払い出しそうなIDを先回りして明示的に保存しておいて、それが自動採番で払い出されるかどうかを見てみます。

現在のデフォルト採番ポリシーはscatteredで、53bit近い数値にほぼランダムに分散されてしまう為、上記作戦を実行するのは難しいです。なので、あまり分散されないlegacyポリシーを指定して実験します。

app.yaml

```yaml
auto_id_policy: legacy
```

自動採番でEntityを保存するAPIと、パラメータでIDを指定して保存するAPIを用意しました。

まず自動採番APIを２回ほど実行しました。すると、下記2レコードが保存されました。

<img width="141" alt="スクリーンショット 2016-11-02 21.59.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/31437/98ca8ca8-07f4-fc69-4c3b-b1d7b60452b5.png">

どうやら次に`2000003`が来そうなので、パラメータ指定APIで先回りしてID=2000003のentityを保存しておきました。

<img width="159" alt="スクリーンショット 2016-11-02 22.03.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/31437/2303377c-c520-ad7c-b382-4cbf89078c13.png">

そして再度自動採番APIを実行！

### 結果

```
API error 1 (datastore_v3: BAD_REQUEST): the id allocated for a new entity was already in use, please try again: app: "xxxxxxx"
path <
  Element {
    type: "Test"
    id: 2000003
  }
>
```

おぉ。予想通り2000003が払い出されましたが、エラーになりました！

これは半分想定内、半分想定外でした。。

どうやら、

- ID自動採番は未保存IDを探してきて払い出してくれる機能ではなく、何らかの採番用情報を元に採番する機能（単純なインクリメントではない）
- 同一Kindで自動採番と明示的ID指定保存を併用した場合、IDがバッティングする危険性がある
- 自動採番時に保存済みIDが払い出された場合はその時点でエラーとなる

みたいなカンジです。

### 対策

同じKindで自動採番と明示的ID指定は併用しないようにすれば回避できます。

## 実験その２

Datastore Adminのrestore&backup機能で他のプロジェクトにデータを移行することができます。
その時自動採番用の情報は一緒に移行されるのでしょうか？
自動採番情報が移行されないと移行先プロジェクトで新しく採番されるIDは移行元のデータとバッティングしそうです。

試してみます。

### 作戦

※これもデフォルト採番ポリシーで。

1. 移行元アプリで何十件か自動生成でentityを保存しておく
1. そのデータをDatastore AdminからGCSへbackup
1. 移行先プロジェクトへrestore
1. 移行先プロジェクトで自動採番entityを数件保存

### 結果

```
API error 1 (datastore_v3: BAD_REQUEST): the id allocated for a new entity was already in use, please try again: app: "xxxxxxxx"
path <
  Element {
    type: "Test"
    id: 4000001
  }
>
```

やはり、移行元で払い出し済のIDが再度払い出されてエラーになりました。。(-_-)

自動採番用の情報は新プロジェクトには引き継がれない様です。

### 対策

とりあえずエラーは起こしてくれるので、重複でIDが払い出されて既存データを上書きしてしまう最悪の事態は免れそうです。

将来のデータ移行の可能性を考えた場合、IDの自動採番を行う処理はリトライを書いておいた方がよいかもしれません。

現在のデフォルト採番ポリシーだとIDは分散される為おそらくエラーが出る頻度はかなり低いのではないかと推測します。リトライをちゃんと書いておけばそれほど大きな問題にはならないかと思います（確証はないですが）。

ちなみにGAE/Goだとdatastore.RunInTransaction使っていればerror時に3回まで勝手にリトライします(^^)
リトライする処理は冪等になるよう気をつけてね！！

### 最後に・・

今回legacy allocate ID policyで実験しましたが、default(scattered) policyだったらバッティングしない・・・みたいなことはありえるかな・・・
（大金を投入する以外に）scatteredで確かめる方法がちょっと思いつかない(´･ω･`)

あと、backup&restore以外に、アプリケーション＆Datastoreをまるごとコピーする機能ってなかったかな？
何となく昔あった様な記憶が・・・