注: 新しいアプリケーションを作成する際には NDB クライアント ライブラリを使用することを強くおすすめします。NDB クライアント ライブラリには、Memcache API によるエンティティの自動キャッシュなど、DB クライアント ライブラリにはないメリットがあります。古い DB クライアント ライブラリを使用している場合は、DB から NDB への移行ガイドをお読みください。
Cloud Datastore のデータ オブジェクトはエンティティと呼ばれます。エンティティは 1 つ以上の名前付きプロパティを持ち、各プロパティが 1 つ以上の値を持ちます。同じ種類のエンティティが同じプロパティを持つとは限りません。また、エンティティの特定のプロパティの値がすべて同じデータ型である必要はありません(必要に応じて、アプリケーション独自のデータモデルでこのような制限を確立して強制できます)。
Cloud Datastore はさまざまなデータ型のプロパティ値をサポートしています。たとえば、次のようなものがあります。
- 整数
- 浮動小数点
- 文字列
- 日付
- バイナリデータ
すべての型の一覧は、プロパティと値の型をご覧ください。
Cloud Datastore 内の各エンティティはキーによって一意に識別されます。キーは次のコンポーネントで構成されています。
- エンティティの名前空間。マルチテナンシーを可能にします。
- エンティティの種類。Cloud Datastore のクエリ用にエンティティを分類します。
- 個々のエンティティの識別子。次のいずれかになります。
- キー名の文字列
- 整数の数値 ID
- オプションの祖先パス。Cloud Datastore 階層内でのエンティティの位置を指定します。
アプリケーションはエンティティのキーを使用して Cloud Datastore から個々のエンティティをフェッチできます。また、エンティティのキーまたはプロパティ値に基づくクエリを発行して 1 つ以上のエンティティを取得することもできます。
Python App Engine SDK には、データ モデリング ライブラリが含まれています。このライブラリを使用して、Cloud Datastore エンティティを Python クラスのインスタンスとして表したり、これらのインスタンスをデータストアに格納して取得することができます。
Cloud Datastore 自体がエンティティの構造になんらかの制限(特定のプロパティは特定の型の値を持たなければならないなど)を課すことはありません。この役割はアプリケーションとデータ モデリング ライブラリが担います。
種類と識別子
Cloud Datastore のすべてのエンティティは特定の種類に属しています。種類とは、クエリ用にエンティティを分類するカテゴリのことです。たとえば、人事アプリケーションでは、会社の従業員が Employee という種類のエンティティで表されます。Python Datastore API では、エンティティの種類がモデルクラスによって決まります。モデルクラスは、アプリケーション内でデータ モデリング ライブラリ クラスの db.Model のサブクラスとして定義します。モデルクラスの名前が、そのエンティティの属する種類となります。2 つのアンダースコア(__)で始まる種類名はすべて予約済みであり、使用できません。
次の例では、種類が Employee であるエンティティを作成し、プロパティ値を設定した後、データストアに保存します。
import datetime
from google.appengine.ext import db
class Employee(db.Model):
first_name = db.StringProperty()
last_name = db.StringProperty()
hire_date = db.DateProperty()
attended_hr_training = db.BooleanProperty()
employee = Employee(first_name='Antonio',
last_name='Salieri')
employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True
employee.put()
Employee クラスは、データモデルに first_name、last_name、hire_date、attended_hr_training の 4 つのプロパティを宣言します。Model スーパークラスによって、Employee オブジェクトの属性がこのモデルに確実に準拠するようになります。たとえば、hire_date 属性に文字列値を割り当てようとするとランタイム エラーとなります。これは、hire_date のデータモデルが db.DateProperty と宣言されたためです。
種類に加え、各エンティティは作成時に割り当てられた識別子を持ちます。識別子はエンティティのキーの一部であるため、エンティティに永続的に割り当てられており、変更できません。識別子を割り当てる方法は 2 通りあります。
- アプリケーションからエンティティの固有のキー名文字列を指定する。
- エンティティに整数の数値 ID が自動的に割り当てられるように Cloud Datastore を設定する。
エンティティにキー名を割り当てるには、次のように、エンティティの作成時にモデルクラスのコンストラクタに対して名前付き引数 key_name を指定します。
# Create an entity with the key Employee:'asalieri'.
employee = Employee(key_name='asalieri')
Cloud Datastore で数値 ID を自動的に割り当てるには、key_name 引数を省略します。
# Create an entity with a key such as Employee:8261.
employee = Employee()
識別子の割り当て
Cloud Datastore は、次の 2 種類の自動 ID ポリシーを使用して自動 ID を生成するように設定できます。
defaultポリシーは、未使用の ID をランダムに生成します。この ID はほぼ均等に分布しています。各 ID は最大 16 桁の 10 進数になります。legacyポリシーは、連続していない小さい整数の ID を生成します。
エンティティの ID をユーザーに表示する場合や、ID の順序に従ってなんらかの処理を行う場合は、手動で割り当てることをおすすめします。
Cloud Datastore は、おおむね均一に分散した一連の未使用 ID をランダムに生成します。各 ID は最大 16 桁の 10 進数になります。
システムから割り当てられる ID 値はエンティティ グループに対して一意であることが保証されています。あるエンティティ グループまたは名前空間から別のエンティティ グループまたは名前空間にエンティティをコピーしキーの ID 部分を保持する場合は、まず ID を割り当ててその ID が Cloud Datastore による今後の割り当てで選ばれないようにしてください。
祖先パス
Cloud Datastore 内の各エンティティは、ファイル システムのディレクトリ構造と同様の階層的に構造化された空間を形成します。エンティティを作成するときに、別のエンティティを親として設定す���こともできます。新しいエンティティは、この親エンティティの子になります(ファイル システムとは異なり、親エンティティが実際に存在している必要はありません)。親を持たないエンティティは、ルート エンティティとなります。 エンティティと親との割り当ては永続的であり、エンティティの作成後は変更できません。同じ親を持つ 2 つのエンティティ、または 2 つのルート エンティティ(親を持たないエンティティ)に同じ数値 ID が割り当てられることはありません。
エンティティの親、親の親、さらにその親などは順に祖先として位置付けられ、エンティティの子、子の子、さらにその子などは順に子孫として位置付けられます。 ルート エンティティとその子孫のエンティティはすべて同じエンティティ グループに属します。 ルート エンティティから始まり、親から子を経由して対象のエンティティに至るまでの連なりをエンティティの祖先パスといいます。 エンティティを識別する完全なキーは、エンティティの祖先パスから始まってそのエンティティ自身で終わる一連の「種類と識別子のペア」で構成されます。
[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]
ルート エンティティの場合は祖先パスが空で、エンティティ自身の種類と識別子だけでキーが構成されています。
[Person:GreatGrandpa]
この概念を次の図に示します。
エンティティの親を指定するには、子エンティティの作成時にモデルクラスのコンストラクタに対して parent 引数を使用します。この引数の値には、親エンティティ自体またはそのキーを指定できます。指定するキーを取得するには、親エンティティの key() メソッドを呼び出します。次の例は、種類が Address のエンティティを作成し、Employee エンティティをその親として指定する 2 つの方法を示しています。
# Create Employee entity
employee = Employee()
employee.put()
# Set Employee as Address entity's parent directly...
address = Address(parent=employee)
# ...or using its key
e_key = employee.key()
address = Address(parent=e_key)
# Save Address entity to datastore
address.put()
トランザクションとエンティティ グループ
エンティティの作成、更新、削除は、すべてトランザクションのコンテキストで行われます。1 つのトランザクションで、このようなオペレーションが複数実行される場合もあります。トランザクションでは、データの整合性を維持するため、すべてのオペレーションを 1 つの単位として Cloud Datastore に適用します。いずれかのオペレーションが失敗した場合、すべてのオペレーションが適用されません。また同一のトランザクション内で実行されるすべての強整合性読み取り(祖先クエリまたは取得)は、データの一貫性のあるスナップショットを維持します。
上記のとおり、エンティティ グループとは、祖先から共通のルート要素までが連結された一連のエンティティです。データをエンティティ グループに編成する場合、実行可能なトランザクションの種類が制限されることがあります。
- トランザクションがアクセスするすべてのデータは、最大 25 のエンティティ グループに制限されます。
- トランザクション内でクエリを使用する場合は、適切なデータと一致する祖先フィルタを指定できるように、データをエンティティ グループに編成する必要があります。
- 1 つのエンティティ グループに対し、1 秒につき約 1 つのトランザクションという書き込みスループットの制限があります。これは高い信頼性とフォールト トレランスを実現するため、Cloud Datastore が広範な地域にわたって個々のエンティティ グループをマスターなしで同期複製する��めに生じる制限です。
多くのアプリケーションでは、相互に関連性のないデータに対する広範なビューを取得するのであれば、結果整合性(複数のエンティティ グループに対する非祖先クエリ、多少古いデータが返される場合もある)の使用で対応できます。一方、関連性の高いデータから成る単一データセットを表示または編集する場合は、強整合性の使用が適します(祖先クエリ、または単一エンティティに対する get)。このようなアプリケーションでは通常、関連性の高い個々のデータセットに対し、個別のエンティティ グループを作成すると効率的です。詳細については、強整合性に対応するデータ構造をご覧ください。
プロパティと値の型
エンティティに関連付けられたデータ値は 1 つ以上のプロパティで構成されます。 各プロパティには名前と 1 つ以上の値があります。プロパティは複数の型の値を持つことができるため、2 つのエンティティがあった場合、プロパティが同じであっても型が異な��値が存在する場合があります。プロパティにはインデックスを付けることも付けないでおくこともできます(プロパティ P で並べ替えまたはフィルタリングを行うクエリは、P にインデックスが付けられていないエンティティを無視します)。1 つのエンティティに、インデックスが付けられたプロパティを最大 20,000 個まで割り当てることができます。
| 値の型 | Python の型 | 並べ替え順 | 備考 |
|---|---|---|---|
| 整数 | intlong |
数値 | 64 ビット整数(符号付き) |
| 浮動小数点数 | float |
数値 | 64 ビット倍精度、 IEEE 754 |
| ブール値 | bool |
False<True |
|
| テキスト文字列(短い) | strunicode |
Unicode (ASCII テキストとして処理された str) |
最大 1,500 バイト |
| テキスト文字列(長い) | db.Text |
なし | 最大 1 メガバイト インデックス未登録 |
| バイト文字列(短い) | db.ByteString |
バイト順 | 最大 1,500 バイト |
| バイト文字列(長い) | db.Blob |
なし | 最大 1 メガバイト インデックス未登録 |
| 日時 | datetime.datedatetime.timedatetime.datetime |
時系列 | |
| 地理的座標 | db.GeoPt |
最初に緯度、 次に経度 |
|
| 住所 | db.PostalAddress |
Unicode | |
| 電話番号 | db.PhoneNumber |
Unicode | |
| メールアドレス | db.Email |
Unicode | |
| Google アカウント ユーザー | users.User |
メールアドレス Unicode 順 |
|
| インスタント メッセージング ハンドル | db.IM |
Unicode | |
| リンク | db.Link |
Unicode | |
| カテゴリ | db.Category |
Unicode | |
| 評価 | db.Rating |
数値 | |
| Cloud Datastore のキー | db.Key |
パス要素順 (種類、識別子、 種類、識別子...) |
|
| Blobstore のキー | blobstore.BlobKey |
バイト順 | |
| Null | NoneType |
なし |
重要: UserProperty にはメールアドレスとユーザーの一意の ID が含まれているため、これを保存しないことを強くおすすめします。ユーザーが自分のメールアドレスを変更すると、そのユーザーの古い保存済みの User と新しい User の値を比較したときに一致しなくなります。
テキスト文字列とエンコードされていないバイナリデータ(バイト文字列)については、Cloud Datastore は、次の 2 つの値の型をサポートします。
- 短い文字列(最大 1,500 バイト)は、インデックスに登録され、クエリのフィルタ条件や並び替えの順序付けに使用できます。
- 長い文字列(最大 1 メガバイト)は、インデックスに登録されず、クエリのフィルタや並べ替えの順序付けには使用できません。
Blob と呼ばれています。この型は、Blobstore API で使用される blob とは関係ありません。
型が混合した値を持つプロパティをクエリで扱う場合、内部表現に基づく決定論的な順序付けが使用されます。
- Null 値
- 固定小数点数
- 整数
- 日時型
- 評価
- ブール値
- バイト列
- バイト文字列
- Unicode 文字列
- Blobstore のキー
- 浮動小数点数
- 地理的座標
- Google アカウントのユーザー
- Cloud Datastore のキー
長いテキスト文字列と長いバイト文字列はインデックスに登録されないため、順序付けは定義されていません。
エンティティの操作
アプリケーションは Cloud Datastore API を使用してエンティティを作成、取得、更新、削除できます。エンティティの完全なキーがわかっている場合(または親のキー、種類、識別子からキーを導出できる場合)、アプリケーションはそのキーを使用してエンティティを直接操作できます。また、Cloud Datastore クエリの結果としてエンティティのキーを取得することもできます。詳細については、データストアのクエリをご覧ください。
エンティティの作成
Python で新しいエンティティを作成するには、モデルクラスのインスタンスを作成し、必要に応じてプロパティを設定したうえで、そのインスタンスの put() メソッドを呼び出してエンティティをデータストアに保存します。エンティティのキー名は、コンストラクタに key_name 引数を渡すことによって指定できます。
employee = Employee(key_name='asalieri',
first_name='Antonio',
last_name='Salieri')
employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True
employee.put()
キー名を指定しない場合は、Cloud Datastore によって数値 ID がエンティティのキーとして自動的に生成されます。
employee = Employee(first_name='Antonio',
last_name='Salieri')
employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True
employee.put()
エンティティの取得
指定したキーで識別されるエンティティを取得するには、Key オブジェクトを引数として db.get() 関数に渡します。Key オブジェクトはクラスメソッド Key.from_path() を使用して生成できます。完全なパスは、祖先パスの一連のエンティティで構成されます。各エンティティは、種類(文字列)とそれに続く識別子(キー名または数値 ID)で表されます。
address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
address = db.get(address_k)
db.get() は該当するモデルクラスのインスタンスを返します。必ず、取得するエンティティのモデルクラスをインポートしておいてください。
エンティティの更新
既存のエンティティを更新するには、オブジェクトの属性を変更してから、そのオブジェクトの put() メソッドを呼び出します。そのオブジェクト データで既存のエンティティが上書きされます。put() を呼び出すたびにオブジェクト全体が Cloud Datastore に送信されます。
プロパティを削除するには、Python オブジェクトから属性を削除します。
del address.postal_code
その後、オブジェクトを保存します。
エンティティの削除
エンティティを削除するには、エンティティのキーを指定して db.delete() 関数を実行します。
address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
db.delete(address_k)
または、エンティティ固有の delete() メソッドを呼び出します。
employee_k = db.Key.from_path('Employee', 'asalieri')
employee = db.get(employee_k)
# ...
employee.delete()
一括オペレーション
db.put()、db.get()、db.delete() 関数(およびその非同期バージョンである db.put_async()、db.get_async()、db.delete_async())にはリスト引数を指定できるため、1 回の Cloud Datastore 呼び出しで複数のエンティティを操作できます。
# A batch put.
db.put([e1, e2, e3])
# A batch get.
entities = db.get([k1, k2, k3])
# A batch delete.
db.delete([k1, k2, k3])
一括オペレーションを実行してもコストは変わりません。キーが存在するかどうかにかかわらず、一括オペレーションに含まれるすべてのキーについて課金されます。オペレーションに含まれるエンティティのサイズは、コストに影響しません。
GCP Console でのエンティティの一括削除
GCP Console では、デフォルトの名前空間にある、特定の種類のすべてのエンティティか、すべての種類のすべてのエンティティを削除できます。
- Cloud Datastore 管理ページに移動します。
- Cloud Datastore 管理機能をまだ有効にしていない場合は、[データストア管理を有効にする] をクリックします。
- 削除するエンティティの種類を選択します。
- [エンティティの削除] をクリックします。一括削除はアプリケーション内で実行されるため、割り当て量に影響することに注意してください。
空のリストの使用
NDB インターフェースでは、Cloud Datastore はこれまで静的なプロパティと動的なプロパティのどちらについても空のリストを省略されたプロパティとして書き込んでいました。下位互換性を維持するため、この動作は引き続きデフォルトになっています。この動作をグローバルに、または ListProperty ごとにオーバーライドするには、Property クラスの write_empty_list 引数をtrue に設定します。これで、空のリストが Cloud Datastore に書き込まれ、空のリストとして読み取れるようになります。
DB インターフェースでは、これまでプロパティが動的な場合は空のリストの書き込みがまったく許可されていませんでした。空のリストを書き込もうとすると、エラーが発生しました。つまり、DB の動的なプロパティについては、下位互換性のために保持する必要があるデフォルトの動作がないため、動的なモデルでは変更なしで空のリストを読み書きできます。
しかし、DB の静的なプロパティについては、空のリストが省略されたプロパティとして書き込まれていたため、下位互換性を維持するためこの動作がデフォルトで継続されます。DB の静的なプロパティで空のリストを有効にするには、Property クラスで write_empty_list 引数を true に設定します。これで、空のリストが Cloud Datastore に書き込まれるようになります。
書き込みコストについて
アプリケーションで Cloud Datastore の put オペレーションを実行する場合、Cloud Datastore でエンティティを保存するために複数回の書き込みが必要になります。このような書き込みが行われるたびに費用が発生します。エンティティの保存に必要な書き込みの回数は、SDK 開発用コンソールのデータビューアで確認できます。このセクションでは、書き込みコストの計算方法について説明します。
1 つのエンティティを保存するには最低 2 回の書き込みが必要です(エンティティ自体の保存に 1 回、EntitiesByKind 組み込みインデックスの保存に 1 回)。この組み込みインデックスは、クエリ プランナーがさまざまなクエリを処理するために使用します。Cloud Datastore では、これ以外に EntitiesByProperty と EntitiesByPropertyDesc の 2 つの組み込みインデックスが保持されます。これらのインデックスはそれぞれ、エンティティの単一のプロパティ値が昇順、降順になっており、エンティティを効率的にスキャンできるようになっています。エンティティのインデックス登録される各プロパティ値は、これらのインデックスそれぞれに書き込む必要があります。
たとえば、A、B、C というプロパティがあるエンティティについて考えてみます。
Key: 'Foo:1' (kind = 'Foo', id = 1, no parent)
A: 1, 2
B: null
C: 'this', 'that', 'theOther'
このようなエンティティに複合インデックス(後述)がないと仮定すると、このエンティティを保存するために 14 回の書き込みが必要です。
- エンティティ自体のために 1 回
EntitiesByKindインデックスのために 1 回- プロパティ A のために 4 回(2 つの値に 2 回ずつ)
- プロパティ B のために 2 回(Null 値も書き込みが必要です)
- プロパティ C のために 6 回(3 つの値に 2 回ずつ)
複合インデックスとは複数のプロパティを参照するインデックスのことで、保持するために追加の書き込みが必要です。次の複合インデックスを定義するとします。
Kind: 'Foo'
A ▲, B ▼
三角形はプロパティの並べ替え順序を示します(プロパティ A は昇順、プロパティ B は降順)。この場合、前述のエンティティを保存するには、A の値と B の値の組み合わせそれぞれに対して複合インデックスへの追加の書き込みが必要になります。
(1, null)
(2, null)
この結果、複合インデックス用に 2 回の書き込みが追加され、書き込みは合計 16 回(1+1+4+2+6+2)になります。次に、プロパティ C をインデックスに追加します。
Kind: 'Foo'
A ▲, B ▼, C ▼
この場合、エンティティを保存すると、A、B、C の値で可能な組み合わせごとに複合インデックスへの書き込みが必要になります。
(1, null, 'this')
(1, null, 'that')
(1, null, 'theOther')
(2, null, 'this')
(2, null, 'that')
(2, null, 'theOther')
この結果、書き込みの合計数は 20 回(1+1+4+2+6+6)になります。
複数の値を持つプロパティが Cloud Datastore のエンティティに多数ある場合、または複数の値を持つ 1 つのプロパティが何度も参照される場合、インデックスを保持するために必要な書き込みの回数が組み合わせにより爆発的に増加する可能性があります。このようなインデックス爆発が発生すると、インデックスの保持コストが非常に高価になる場合があります。たとえば、祖先が含まれる複合インデックスについて考えてみます。
Kind: 'Foo'
A ▲, B ▼, C ▼
Ancestor: True
このインデックスを含む単純なエンティティを保存するには、前の例と同じ回数の書き込みが必要です。ただし、このエンティティに祖先がある場合は、エンティティ自体の書き込みの他に、プロパティ値と祖先で可能な組み合わせごとに書き込みが必要になります。たとえば次のように定義されるエンティティがあるとします。
Key: 'GreatGrandpa:1/Grandpa:1/Dad:1/Foo:1' (kind = 'Foo', id = 1, parent = 'GreatGrandpa:1/Grandpa:1/Dad:1')
A: 1, 2
B: null
C: 'this', 'that', 'theOther'
このエンティティでは、次に示すプロパティと祖先の組み合わせごとに、複合インデックスへの書き込みが必要です。
(1, null, 'this', 'GreatGrandpa')
(1, null, 'this', 'Grandpa')
(1, null, 'this', 'Dad')
(1, null, 'this', 'Foo')
(1, null, 'that', 'GreatGrandpa')
(1, null, 'that', 'Grandpa')
(1, null, 'that', 'Dad')
(1, null, 'that', 'Foo')
(1, null, 'theOther', 'GreatGrandpa')
(1, null, 'theOther', 'Grandpa')
(1, null, 'theOther', 'Dad')
(1, null, 'theOther', 'Foo')
(2, null, 'this', 'GreatGrandpa')
(2, null, 'this', 'Grandpa')
(2, null, 'this', 'Dad')
(2, null, 'this', 'Foo')
(2, null, 'that', 'GreatGrandpa')
(2, null, 'that', 'Grandpa')
(2, null, 'that', 'Dad')
(2, null, 'that', 'Foo')
(2, null, 'theOther', 'GreatGrandpa')
(2, null, 'theOther', 'Grandpa')
(2, null, 'theOther', 'Dad')
(2, null, 'theOther', 'Foo')
このエンティティを Cloud Datastore に保存するには、38 回(1 + 1 + 4 + 2 + 6 + 24)の書き込みが必要です。

