エンティティ、プロパティ、キー

Datastore のデータ オブジェクトをエンティティと呼びます。エンティティは 1 つ以上の名前付きプロパティを持ち、各プロパティが 1 つ以上の値を持ちます。同じ種類のエンティティが同じプロパティを持つとは限りません。また、エンティティの特定のプロパティの値がすべて同じデータ型である必要はありません(必要に応じて、アプリケーション独自のデータモデルでこのような制限を確立して強制できます)。

Datastore では、プロパティ値でさまざまなデータ型がサポートされています。サポートしているデータ型の例を次に示します。

  • 整数
  • 浮動小数点数
  • 文字列
  • 日付
  • バイナリデータ

すべての型の一覧は、プロパティと値の型をご覧ください。

Datastore 内の各エンティティは、キーによって一意に識別されます。キーは次のコンポーネントで構成されています。

  • エンティティの名前空間マルチテナンシーを可能にします。
  • エンティティの種類。Datastore クエリ用にエンティティを分類します。
  • 個々のエンティティの識別子。次のいずれかになります。
    • キー名の文字列
    • 整数の数値 ID
  • オプションの祖先パス。Datastore 階層内のエンティティの位置を指定します。

アプリケーションではエンティティのキーを使用して Datastore から個々のエンティティを取得できます。また、エンティティのキーやプロパティ値に基づくクエリを実行して 1 つ以上のエンティティを取得することもできます。

Go App Engine SDK にはパッケージが含まれています。これを使用して Datastore エンティティを Go 構造体として表すことができます。また、その構造体を Datastore に格納し、取得できます。

Datastore 自体がエンティティの構造になんらかの制限(特定のプロパティは特定の型の値を持たなければならないなど)を課すことはありません。この役割はアプリケーションが担います。

種類と識別子

Datastore の各エンティティは特定の種類に属しています。種類とは、クエリに対応できるようにエンティティを分類するものです。たとえば、人事アプリケーションでは、社内の各従業員が Employee という種類のエンティティに相当します。Go Datastore API では、datastore.Key を作成するときにエンティティの種類を指定します。2 つのアンダースコア(__)で始まる種類名はすべて予約済みであり、使用できません。

次の例では、種類が Employee のエンティティを作成し、そのプロパティ値を設定して、Datastore に保存します。

import (
	"context"
	"time"

	"google.golang.org/appengine/datastore"
)

type Employee struct {
	FirstName          string
	LastName           string
	HireDate           time.Time
	AttendedHRTraining bool
}

func f(ctx context.Context) {
	// ...
	employee := &Employee{
		FirstName: "Antonio",
		LastName:  "Salieri",
		HireDate:  time.Now(),
	}
	employee.AttendedHRTraining = true

	key := datastore.NewIncompleteKey(ctx, "Employee", nil)
	if _, err := datastore.Put(ctx, key, employee); err != nil {
		// Handle err
	}
	// ...
}

Employee 型は、データモデルに FirstNameLastNameHireDateAttendedHRTraining の 4 つのフィールドを宣言します。

種類に加え、各エンティティは作成時に割り当てられた識別子を持ちます。識別子はエンティティのキーの一部であるため、エンティティに永続的に割り当てられており、変更できません。識別子を割り当てる方法は 2 通りあります。

  • アプリケーションからエンティティの固有のキー名文字列を指定する。
  • エンティティに整数の数値 ID が自動的に割り当てられるように Datastore を設定する。

エンティティにキー名を割り当てるには、空でない stringID 引数を datastore.NewKey に渡します。

// Create a key with a key name "asalieri".
key := datastore.NewKey(
	ctx,        // context.Context
	"Employee", // Kind
	"asalieri", // String ID; empty means no string ID
	0,          // Integer ID; if 0, generate automatically. Ignored if string ID specified.
	nil,        // Parent Key; nil means no parent
)

Datastore が数値 ID を自動的に割り当てるように設定するには、空の stringID 引数を使用します。

// Create a key such as Employee:8261.
key := datastore.NewKey(ctx, "Employee", "", 0, nil)
// This is equivalent:
key = datastore.NewIncompleteKey(ctx, "Employee", nil)

識別子の割り当て

Datastore は、次の 2 種類の自動 ID ポリシーを使用して自動 ID を生成するように構成できます。

  • default ポリシーは、ほぼ均等に分布する未使用の ID をランダムに生成します。各 ID は最大 16 桁の 10 進数になります。
  • legacy ポリシーは、連続しない小さい整数の ID を作成します。

エンティティの ID をユーザーに表示する場合や、ID の順序に従ってなんらかの処理を行う場合は、手動で割り当てることをおすすめします。

Datastore では、ほぼ一様に分散されたランダムな未使用 ID のシーケンスが生成されます。各 ID は最大 16 桁の 10 進数になります。

祖先パス

Cloud Datastore 内の各エンティティは、ファイル システムのディレクトリ構造と同様の階層的に構造化された空間を形成します。エンティティを作成するときに、別のエンティティを親として設定することもできます。新しいエンティティは、この親エンティティの子になります(ファイル システムとは異なり、親エンティティが実際に存在している必要はありません)。親を持たないエンティティは「ルート エンティティ」となります。エンティティと親との割り当ては永続的であり、エンティティの作成後は変更できません。同じ親を持つ 2 つのエンティティ、または 2 つのルート エンティティ(親を持たないエンティティ)に同じ数値 ID が割り当てられることはありません。

エンティティの親、親の親、さらにその親などは順に「祖先」として位置付けられ、エンティティの子、子の子、さらにその子などは順に「子孫」として位置付けられます。ルート エンティティとその子孫のエンティティは、すべて同じエンティティ グループに属します。ルート エンティティから始まり、親から子を経由して対象のエンティティに至るまでのエンティティの連なりのことを、エンティティの「祖先パス」といいます。エンティティを識別する完全なキーは、エンティティの祖先パスから始まってそのエンティティ自身で終わる一連の「種類と識別子のペア」で構成されています。

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

ルート エンティティの場合は祖先パスが空で、エンティティ自身の種類と識別子だけでキーが構成されています。

[Person:GreatGrandpa]

このコンセプトを次の図に示します。

エンティティ グループ内のルート エンティティと子エンティティの関係を示す図

エンティティの親を指定するには、datastore.NewKey に対して parent 引数を使用します。この引数の値は、親エンティティのキーである必要があります。次の例では、種類が Address のエンティティを作成し、Employee エンティティを親として指定しています。

// Create Employee entity
employee := &Employee{ /* ... */ }
employeeKey, err := datastore.Put(ctx, datastore.NewIncompleteKey(ctx, "Employee", nil), employee)

// Use Employee as Address entity's parent
// and save Address entity to datastore
address := &Address{ /* ... */ }
addressKey := datastore.NewIncompleteKey(ctx, "Address", employeeKey)
_, err = datastore.Put(ctx, addressKey, address)

トランザクションとエンティティ グループ

エンティティの作成、更新、削除は、すべてトランザクションのコンテキストで行われます。1 つのトランザクションで、このようなオペレーションが複数実行される場合もあります。トランザクションでは、データの整合性を維持するため、すべてのオペレーションを 1 つの単位として Datastore に適用します。いずれかのオペレーションが失敗した場合、すべてのオペレーションが適用されません。また、同一のトランザクション内で実行されるすべての強整合性読み取り(祖先クエリまたは取得)は、データの整合性のあるスナップショットを維持します。

上記のとおり、エンティティ グループとは、祖先から共通のルート要素までが連結された一連のエンティティです。データをエンティティ グループに編成する場合、実行可能なトランザクションの種類が制限されることがあります。

  • トランザクションがアクセスするすべてのデータは、最大 25 のエンティティ グループに制限されます。
  • トランザクション内でクエリを使用する場合は、適切なデータと一致する祖先フィルタを指定できるように、データをエンティティ グループに編成する必要があります。
  • 1 つのエンティティ グループに対し、1 秒につき約 1 つのトランザクションという書き込みスループットの制限があります。これは高い信頼性とフォールト トレラントを実現するため、Datastore が広範な地域にわたって個々のエンティティ グループをマスターなしで同期複製するために生じる制限です。

多くのアプリケーションでは、相互に関連性のないデータに対する広範なビューを取得するのであれば、結果整合性(複数のエンティティ グループに対する非祖先クエリ、多少古いデータが返される場合もある)の使用で対応できます。一方、関連性の高いデータからなる単一データセットを表示または編集する場合は、強整合性の使用が適します(祖先クエリ、または単一エンティティに対する get)。このようなアプリケーションでは通常、関連性の高い個々のデータセットに対し、個別のエンティティ グループを作成すると効率的です。詳細については、強整合性に対応するデータ構造をご覧ください。

プロパティと値の型

エンティティに関連付けられたデータ値は 1 つ以上のプロパティで構成されます。各プロパティには名前と 1 つ以上の値があります。プロパティは異なる型の複数の値を持つことができるため、2 つのエンティティの同じプロパティに異なる型の値が存在する場合もあります。プロパティはインデックス付けされている場合とされていない場合があります(プロパティ P で並べ替えまたはフィルタリングを行うクエリでは、P がインデックス付けされていないエンティティは無視されます)。1 つのエンティティに、インデックスが付けられたプロパティを最大 20,000 個まで割り当てることができます。

サポートされている値の型は、次のとおりです。

値の型 Go の型 並べ替え順
整数 int
int8
int16
int32
int64
数値 64 ビット整数(符号付き)
浮動小数点数 float32
float64
数値 64 ビット倍精度、
IEEE 754
ブール値 bool false<true
文字列(短い) string Unicode
最大 1,500 バイト。1,500 バイトを超える値は実行時にエラーになります。
文字列(長い) stringnoindex を含む) なし 最大 1 メガバイト

インデックス付けなし
バイトスライス(短い) datastore.ByteString バイト順 最大 1,500 バイト。1,500 バイトを超える値は実行時にエラーになります。
バイトスライス(長い) []byte なし 最大 1 メガバイト

インデックス付けなし
日時 time.Time 時系列
地理的座標 appengine.GeoPoint 緯度、
経度の順
Datastore のキー *datastore.Key パス要素順
(種類、識別子、
種類、識別子...)
Blobstore のキー appengine.BlobKey バイト順

struct または slice を使用して、プロパティを集約することもできます。詳細については、Datastore のリファレンスをご覧ください。

型が混合した値を持つプロパティをクエリで扱う場合、Datastore では内部表現に基づく決定論的な順序付けが使用されます。

  1. Null 値
  2. 固定小数点数
    • 整数
    • 日時型
  3. ブール値
  4. バイト列
    • バイトスライス(短い)
    • Unicode 文字列
    • Blobstore のキー
  5. 浮動小数点数
  6. 地理的座標
  7. Datastore のキー

長いバイトスライスと長い文字列はインデックス付けされないため、順序付けは定義されていません。

エンティティの操作

アプリケーションは Datastore API を使用してエンティティを作成、取得、更新、削除できます。エンティティの完全なキーがわかっている場合(または親のキー、種類、識別子からキーを導出できる場合)、アプリケーションはそのキーを使用してエンティティを直接操作できます。また、Datastore クエリの結果としてエンティティのキーを取得することもできます。詳細については、Datastore のクエリをご覧ください。

エンティティの作成

Go で新しいエンティティを作成するには、Go 構造体のインスタンスを作成し、そのフィールドに値を設定したうえで、datastore.Put を呼び出して Datastore に保存します。Datastore にはエクスポートされたフィールド(大文字で始まるもの)のみが保存されます。エンティティのキー名を指定するには、空でない stringID 引数を datastore.NewKey に渡します。

employee := &Employee{
	FirstName: "Antonio",
	LastName:  "Salieri",
	HireDate:  time.Now(),
}
employee.AttendedHRTraining = true
key := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
_, err = datastore.Put(ctx, key, employee)

空のキー名を指定するか、datastore.NewIncompleteKey を使用すると、Datastore によって数値 ID がエンティティのキーとして自動的に生成されます。

employee := &Employee{
	FirstName: "Antonio",
	LastName:  "Salieri",
	HireDate:  time.Now(),
}
employee.AttendedHRTraining = true
key := datastore.NewIncompleteKey(ctx, "Employee", nil)
_, err = datastore.Put(ctx, key, employee)

エンティティの取得

指定したキーで識別されるエンティティを取得するには、*datastore.Key を引数として datastore.Get 関数に渡します。datastore.NewKey を生成するには、*datastore.Key 関数を使用します。

employeeKey := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
addressKey := datastore.NewKey(ctx, "Address", "", 1, employeeKey)
var addr Address
err = datastore.Get(ctx, addressKey, &addr)

datastore.Get は適切な Go 構造体のインスタンスを設定します。

エンティティの更新

既存のエンティティを更新するには、構造体の属性を変更してから datastore.Put を呼び出します。このデータで既存のエンティティが上書きされます。datastore.Put を呼び出すたびに、オブジェクト全体が Datastore に送信されます。

エンティティの削除

エンティティを削除するには、エンティティのキーを指定して、datastore.Delete 関数を実行します。

key := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
err = datastore.Delete(ctx, key)

バッチ オペレーション

datastore.Putdatastore.Getdatastore.Delete には、datastore.PutMultidatastore.GetMultidatastore.DeleteMulti という一括バリアントがあります。これらを使用すると、1 回の Datastore 呼び出しで複数のエンティティを処理できます。

// A batch put.
_, err = datastore.PutMulti(ctx, []*datastore.Key{k1, k2, k3}, []interface{}{e1, e2, e3})

// A batch get.
var entities = make([]*T, 3)
err = datastore.GetMulti(ctx, []*datastore.Key{k1, k2, k3}, entities)

// A batch delete.
err = datastore.DeleteMulti(ctx, []*datastore.Key{k1, k2, k3})

一括オペレーションを実行してもコストは変わりません。キーが存在するかどうかにかかわらず、バッチ オペレーションに含まれるすべてのキーについて課金されます。オペレーションに含まれるエンティティのサイズは、コストに影響しません。