Datastoreと結果整合性
私のようにRDBMSに慣れきったエンジニアには意外なことですが、Datastoreでは格納したデータがすぐに取り出せない状況があり得ます。もちろん一度格納したデータの永続性は保証されているわけですが、自分がたった今書き込んだデータが直後に取り出せなかったり、古いデータを取り出してしまう可能性があるのです。このような一貫性モデルを結果整合性(Eventual Consistency)と呼びます。
RDBMSで言えば、マスタースレーブ構成のときにスレーブからデータを読み込む状態に近いと言えるでしょう。スレーブから読み込んだデータは最新のデータとは限らないわけですが、これを忘れるとレプリケーション遅延が大きいときだけ再現するイヤなバグの原因になってしまいます。
Datastoreの一貫性モデルについては下記の公式ドキュメントが参考になります。
上の文書にもある通り、Datastoreからの読み込みはクエリの種類によって強整合性が保証されているものと結果整合性が適用されるものとに分かれています。
Cloud Datastore API | エンティティ値の読み取り | インデックスの読み取り |
---|---|---|
グローバルクエリ | 結果整合性 ① | 結果整合性 ② |
キーオンリークエリ | N/A | 結果整合性 |
祖先クエリ | 強整合性 ③ | 強整合性 |
キーによる検索(get()) | 強整合性 ④ | N/A |
これによれば、キーを明示的に指定した検索と祖先指定ありの検索は常に最新の結果が取得できる(=強整合性)ことがわかります。それ以外の通常のクエリは最初に紹介したように最新の結果が得られるとは限りません。
いま格納したデータが検索できない状況を観測してみる
しかし、趣味プログラマがGAEで遊んでいる程度の範囲だと結果整合性による影響の実感はないかもしれません。そこで、Datastoreに登録した直後に同じデータが検索できるかどうか、次の4つの場合について実験してみました。これらは上の表の①〜④に対応しています。
- 通常クエリ
datastore.NewQuery("testkind").Filter("name =", uniqueName)
- プロジェクションクエリ
datastore.NewQuery("testkind").Project("value").Filter("name =", uniqueName)
- 祖先クエリ
datastore.NewQuery("testkind").Ancestor(ancestorKey).Filter("name =", uniqueName)
- キー指定でのエンティティ取得
datastore.Get(ctx, k, &d)
それぞれ数千回ほど試行してみましたが、①と②についてはときどき最新のデータが読み取れないことがありました。その場合も、大抵は数回のリトライで正しいデータが取得できていました。これらについては一貫性モデルが結果整合性であることが確認できたわけです。
とはいえ、格納したデータが即座に読み取れない状況は100回試して1回起こるかどうかくらいの頻度でした。GAEをお試しで使っているくらいの段階では一貫性モデルの違いについて実感がなくても不思議はないと言えそうです。
また、③と④については必ず最新のデータが取得できました。少なくとも実験の範囲では強整合性に矛盾しない結果が得られたわけです。
下記に、上の実験を100回行ったときの読み込み時間をまとめました。①と②はリトライの時間も含んでいます(100回のうち①で1回、②では2回リトライが発生していました)。こうして並べてみると、祖先クエリは整合性担保のために遅くなることがあるのかもしれません。
最短[ms] | 平均[ms] | 最長[ms] | |
---|---|---|---|
①通常クエリ | 15.95 | 18.91 | 52.63 |
②プロジェクションクエリ | 14.56 | 18.03 | 55.75 |
③祖先クエリ | 17.05 | 21.92 | 118.38 |
④キー指定でのエンティティ取得 | 16.56 | 18.91 | 28.61 |
今回実験に利用したGAE/Goのアプリケーションは下記URLに公開してあります。また、実験は東京リージョンで行いました。再現実験をしたい方はご利用ください。
参考URL
enakai00さんがGCE+Datastore環境でほとんど同じ実験をされています。私の実験結果に比べると直後にデータが取得できない頻度が高そうですが、リージョンの違いなのか、GAEとGCEでの違いなのか、何らかの環境の差だろうと想像しています。