【初心者向け】3分でわかるGoogle Cloud Datastoreのデータ構造とCRUD処理


遅くなりましたが、この記事は 色んなデータストア触ってみる Advent Calendar 2019 の25日目の記事です。

はじめに

Google Cloud Datastoreとは、Google Cloud Platform(GCP)上で提供されている、キー・バリュー型のNoSQLデータベースです。
※ NoSQLの詳細はこちらが参考になると思います。
NoSQLについて勉強する。

初めてCloud Datastoreを触る機会がありデータ構造の理解に苦戦したので、今までよく使っていたSQLと比較して、データ構造についてまとめてみました。
また、クエリもCloud DatastoreのAPIが複数あり、どれを使うべきか悩んでいたので、それぞれのAPIの違いについてもプログラム例を交えて記載しています。

Cloud Datastoreを初めて触るときとかに参考になればと思います。

(途中のプログラム例はpythonで書いていますのでご了承ください。)

用語

RDBとCloud Datastoreの用語の対応表です。
Cloud Datastoreにしかない概念とかは後述します。

RDB Cloud Datastore
Schema Namespace
Table Kind
Row Entity
Column Property
Primary key Key

データ構造

下図は、Namespace(Tasks)とKind(User, Project, Task), Entityの関係の概念図です。
大枠はRDBと変わりないように見えますが、Entity間の関係性などが少しややこしいのでその辺りについて後述します。

エンティティグループ

各エンティティ間は階層構造をしており、ルートエンティティ(下図でいうUserのエンティティ)とその配下に紐づいているエンティティのまとまりをエンティティグループといいます。

祖先パス

エンティティグループのルートエンティティから始まり、親から子を経由して対象のエンティティに至るまでの連なりを、エンティティの祖先パスといいます。
各エンティティは祖先パスをキーとして、親となるエンティティを指定できます。
上図の左下のエンティティでいうと、祖先パスは[User:'1827464394', Project:'A', Task:'blog']になります。

エンティティグループの利点と注意点

祖先パスを用いたエンティティグループ内のクエリでは、最後に更新された値が返されることが保証される(強整合性)という利点があります。
下図のように、各データセンターに分散されたエンティティでも、エンティティグループ内では常に同期されるようになっているためです。

ただし、注意点として、同じエンティティグループに対する更新は1秒間に1回と制限されています。
エンティティグループに対してこの制限を超える回数の更新を行った場合、同期によるレイテンシ(遅延)が増加し、タイムアウトになってパフォーマンスが低下します。
つまり、エンティティグループは更新頻度と強整合性の必要性を考慮した上で設計する必要があります。

トランザクション

エンティティグループ内では強整合性が保たれると前述しましたが、複数のエンティティグループ間で強整合性が必要になる場合はトランザクションを用います。
トランザクションは最大25個のエンティティグループに対して強整合性を保つことができます。

エンティティの特徴

RDBのレコードと異なるエンティティの特徴としては次のようなものがあります。

  • 同じ Kind のエンティティが異なるプロパティを持つ場合がある
  • 別々のエンティティが同じ名前のプロパティを持ちながら、値の型は異なる場合がある

さまざまなクエリ

Google Cloudで提供されているクエリにはさまざまなAPIがあり、それぞれの特徴を理解して適切なAPIを選択する必要があります。

強整合性が保たれるクエリは?

それぞれのAPIで強整合性か否かが異なるため、下記に整合性についてまとめています。
結論から言うと、強整合性が必要な場合は、祖先クエリまたはキーによる検索を用いる必要があります。

Cloud Datastore API エンティティ値の読み取り インデックスの読み取り
グローバルクエリ 結果整合性 結果整合性
キーのみのグローバルクエリ なし 結果整合性
祖先クエリ 強整合性 強整合性
キーによる検索 強整合性 なし

<補足>
強整合性:データの更新の際にデータベースをロックすることによって、常に最新の値を返すことが保証される
結果整合性:データの更新でデータベースがロックされることはないため、データが同期されるまでは返される値が最新でない場合がある

4種のAPIそれぞれについて、エンティティグループの図の左下のエンティティを取得するプログラム例を交えて説明します。

グローバルクエリ

祖先を指定しないクエリ。結果整合性のため強整合性は担保されません。

from google.cloud import datastore
client = datastore.Client()

query = client.query(kind='Task')
query.add_filter('task', '=', 'post a blog')

# 一部のプロパティのみ取得する場合(射影クエリ)
# query.projection = ['task', 'status']

# 複数件取得する際に並び替えする場合
# query.order = ['created_at']

entities = list(query.fetch())

キーのみのグローバルクエリ

クエリに一致するエンティティの"キーのみ"が返されるグローバルクエリ。
結果整合性で機能するため、強整合性は担保されません。

query = client.query(kind='Task')
query.add_filter('task', '=', 'post a blog')
query.keys_only()
entities = list(query.fetch())

祖先クエリ

祖先パスを指定したエンティティグループに対するクエリで、強整合性があります。

ancestor = client.key('User', '1827464394', 'Project', 'A', 'Task', 'blog')
query = client.query(ancestor=ancestor)
entities = list(query.fetch())

キーによる検索

キーによって指定されたエンティティが返されるクエリで強整合性があります。
ただ、条件で絞ったりするフィルタリングはできません。

key = client.key('User', '1827464394', 'Project', 'A', 'Task', 'blog')
entity = client.get(key)

エンティティのCRUD

これまでは取得のクエリしか触れてこなかったので、作成・更新・削除を含めたCRUD処理をまとめておきます。
キーを指定して各エンティティもしくは複数のエンティティに対して処理する方法です。
(簡単のためキーは単純なキーにしています。)

作成

一つのエンティティの作成

key = client.key('Task', 'write a qiita')
task = datastore.Entity(key)
task.update({
  'status': 'Doing'
  'date': '20191231'
})
client.put(task)

複数エンティティの作成

key1 = client.key('Task', 'write a qiita')
key2 = client.key('Task', 'read a book')

task1 = datastore.Entity(key1)
task2 = datastore.Entity(key2)

task1.update({
  'status': 'Doing'
  'date': '20191224'
})
task2.update({
  'status': 'Todo'
  'date': '20191231'
})

client.put_multi([task1, task2])

取得

一つのエンティティの取得(キーによる検索)

key = client.key('Task', 'write a qiita')
task = client.get(key)

複数エンティティの取得

key1 = client.key('Task', 'write a qiita')
key2 = client.key('Task', 'read a book')
tasks = client.get_multi([key1, key2])

更新

一つのエンティティの更新

key = client.key('Task', 'write a qiita')
task = client.get(key)
task['status'] = 'Done'
# 全プロパティを更新する場合
# user.update({
#   'status': 'Done',
#   'date': '20191225'
# })
client.put(task)

複数エンティティの更新

key1 = client.key('Task', 'write a qiita')
key2 = client.key('Task', 'read a book')

task1 = datastore.Entity(key1)
task2 = datastore.Entity(key2)

task1['status'] = 'Done'
task2['status'] = 'Doing'

client.put_multi([task1, task2])

削除

一つのエンティティの削除

key = client.key('Task', 'write a qiita')
client.delete(key)

複数エンティティの削除

key1 = client.key('Task', 'write a qiita')
key2 = client.key('Task', 'read a book')
tasks = client.delete_multi([key1, key2])

最後に

Cloud Datastoreマスターへの第一歩としては、エンティティグループの概念の理解が重要になってきます。
ここで紹介したものだけでなく、Cloud Datastoreのpythonライブラリも複数あるので、今後はそれらの違いとかも試してみたいです。
何かご指摘などあればコメントいただければと思います。

参考

Google Cloud公式ページ
https://cloud.google.com/datastore/docs/

Google Cloud Datastore API公式ドキュメント(Python)
https://googleapis.dev/python/datastore/latest/index.html

Googleが公開している論文
"Megastore: Providing Scalable, Highly Available Storage for Interactive Services"
http://cidrdb.org/cidr2011/Papers/CIDR11_Paper32.pdf