データの整合性

Cloud Datastore は、多数のマシン間でデータを分散し、マスターのない同期レプリケーションを広範な地理的領域で使用することで、高度な可用性、スケーラビリティ、耐久性を実現します。ただしその反面、この設計では書き込みスループットが制限されます。1 つのエンティティ グループに対し、1 秒につき 1 回程度の commit しか行えないためです。複数のエンティティ グループにまたがるクエリやトランザクションにも制限があります。このページではこうした制限事項について詳しく説明します。また、アプリケーションの書き込みスループットに対する要件を満たしつつも、強整合性をサポートするようにデータを構築するためのおすすめの方法を紹介します。

整合性のレベル

Datastore のクエリでは、次の 2 つの整合性レベルのいずれかで結果を返すことができます。

  • 強整合性を確保するクエリは、結果が最新であることが保証されます。ただし、完了までに時間がかかる場合があり、一部の状況ではサポートされないこともあります。
  • 結果整合性クエリは、一般的により高速に実行されます。ただし、最新でない結果が返される場合があります。

結果整合性クエリでは、結果の収集に使用されるインデックスも、結果整合性に基づいてアクセスされます。この結果、現時点でクエリ条件とは一致しなくなったエンティティが返される場合や、クエリ条件と一致するエンティティが省略される場合もあります。強整合性クエリでは、トランザクション上の整合性を維持するため、単一の一貫性のあるデータ スナップショットに基づいた結果が得られます。

整合性の保証

クエリでは各クエリの特性に基づき、異なる整合性レベルを保証した結果が返されます。

  • 祖先クエリ(単一のエンティティ グループに対して実行されるクエリ)は、デフォルトで強整合性を持ちますが、データストアの読み取りポリシーを設定することで(後述)、結果整合性クエリにすることもできます。
  • グローバル クエリ(単一のエンティティ グループに対して実行されないクエリ)は、常に結果整合性を持ちます。

多くのアプリケーションでは、相互に関連性のないデータに対する広範なビューを取得する場合、結果整合性(複数のエンティティ グループにまたがるグローバル クエリ、やや古いデータが返される場合もある)の使用が許容されます。また関連性の高い単一のデータセットを表示したり編集したりする場合は、強整合性(祖先クエリ、または単一エンティティの検索)の使用が好まれます。このようなアプリケーションの場合は通常、関連性の高いデータ同士をエンティティ グループにまとめると効率的です。エンティティ グループの数が多ければ、スループットが向上します。エンティティ グループの数を少なくすると、1 回の祖先クエリで読み取り可能なエンティティの量が増えます。アプリケーションではこれらを踏まえ、スループットと整合性の適切なバランスを決定する必要があります。

Datastore の読み取りポリシー

パフォーマンスを向上させるため、結果整合性を維持した結果が得られるように、クエリの読み取りポリシーを設定できます(Datastore API を使用して強整合性ポリシーを明示的に設定することもできますが、現実的な効果はありません。ポリシーの種類にかかわらず、グローバル クエリでは常に結果整合性が維持されるからです)。

結果整合性に基づく読み取りを有効にするには、クエリ オブジェクトの読み取りオプションを使用します。

C#

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Query query = new Query("Task")
{
    Filter = Filter.HasAncestor(_db.CreateKeyFactory("TaskList")
    .CreateKey("default"))
};
var results = _db.RunQuery(query,
    ReadOptions.Types.ReadConsistency.Eventual);

Go

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

ancestor := datastore.NameKey("TaskList", "default", nil)
query := datastore.NewQuery("Task").Ancestor(ancestor).EventualConsistency()

Java

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Query<Entity> query = Query.newEntityQueryBuilder()
    .setKind("Task")
    .setFilter(PropertyFilter.hasAncestor(
        datastore.newKeyFactory().setKind("TaskList").newKey("default")))
    .build();
datastore.run(query, ReadOption.eventualConsistency());

Node.js

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

// Read consistency cannot be specified in google-cloud-node.

PHP

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

$query = $datastore->query()
    ->kind('Task')
    ->hasAncestor($datastore->key('TaskList', 'default'));
$result = $datastore->runQuery($query, ['readConsistency' => 'EVENTUAL']);

Python

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

# Read consistency cannot be specified in google-cloud-python.

Ruby

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

ancestor_key = datastore.key "TaskList", "default"

query = datastore.query("Task").
        ancestor(ancestor_key)

tasks = datastore.run query, consistency: :eventual

トランザクションと整合性に関する考慮事項

データストアの commit は、トランザクションであるかトランザクションのコンテキスト内で実行され、トランザクションの一連の変更がすべて適用されるかまたは一切適用されないかのどちらか)、非トランザクションであるか(一連の変更すべてが一括して適用 / 非適用とされるわけではない)のどちらかとなります。

1 つのトランザクションには、作成、更新、削除の変更をいくつでも含めることができます。トランザクションではデータの整合性を維持するため、そのトランザクション内のすべての変更を一括して Datastore に適用します。いずれかの変更が失敗した場合は、どの変更も適用されません。また同一のトランザクション内で実行されるすべての強整合性読み取り(祖先クエリまたは lookup 処理)は、単一の一貫性あるデータ スナップショットに依存します。強整合性クエリでは、祖先フィルタを指定する必要があります。トランザクションに参加するクエリは、常に強整合性を維持します。トランザクションには最大 25 のエンティティ グループを使用できます。結果整合性読み取りにはこのような制限はなく、多くの場合に適しています。結果整合性読み取りを使用する場合は、大量のエンティティ グループ間でデータを分散できる可能性があります。この結果、異なるエンティティ グループ間で commit を並列実行することで、書き込みスループットが向上します。ただし結果整合性読み取りがアプリケーションに適するかどうかを判断するには、その特性を理解しておく必要があります。

  • 結果整合性読み取りによる結果は、最新のトランザクションを反映していない場合があります。このタイプの読み取りでは、読み取りを実行しているレプリカが最新であることが保証されないためです。代わりに結果整合性読み取りでは、クエリの実行時にこのレプリカで使用可能な任意のデータが使用されます。
  • 複数のエンティティ グループを対象として commit されたトランザクションは、すべてではなく一部のエンティティにしか適用されていないように見える場合があります。しかし、1 つのトランザクションが 1 つのエンティティ内で部分的に適用されたように見えることはありません。
  • クエリ結果には、フィルタ条件に適合しないはずのエンティティが含まれる場合や、フィルタ条件に適合するはずのエンティティが除外される場合があります。これはインデックスを読み取るために使用されるスナップショットが、エンティティを読み取るために使用されるスナップショットのバージョンと異なる場合があるためです。

整合性に対応するデータ構造

強整合性に対応するデータ構造化の方法を理解するため、単純なタスクリスト アプリケーションを作成する 2 つの方法を比較してみます。1 つ目の方法では、個々のエンティティをそれぞれ独自の新規エンティティ グループ内に作成します(つまりどのエンティティもルート エンティティとなる)。

C#

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Entity task = new Entity()
{
    Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
    ["category"] = "Personal",
    ["done"] = false,
    ["priority"] = 4,
    ["description"] = "Learn Cloud Datastore"
};

Go

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

type Task struct {
	Category        string
	Done            bool
	Priority        float64
	Description     string `datastore:",noindex"`
	PercentComplete float64
	Created         time.Time
}
task := &Task{
	Category:        "Personal",
	Done:            false,
	Priority:        4,
	Description:     "Learn Cloud Datastore",
	PercentComplete: 10.0,
	Created:         time.Now(),
}

Java

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Entity task = Entity.newBuilder(taskKey)
    .set("category", "Personal")
    .set("done", false)
    .set("priority", 4)
    .set("description", "Learn Cloud Datastore")
    .build();

Node.js

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

const task = {
  category: 'Personal',
  done: false,
  priority: 4,
  description: 'Learn Cloud Datastore'
};

PHP

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

$task = $datastore->entity('Task', [
    'category' => 'Personal',
    'done' => false,
    'priority' => 4,
    'description' => 'Learn Cloud Datastore'
]);

Python

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

task = datastore.Entity(client.key('Task'))
task.update({
    'category': 'Personal',
    'done': False,
    'priority': 4,
    'description': 'Learn Cloud Datastore'
})

Ruby

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

task = datastore.entity "Task" do |t|
  t["category"] = "Personal"
  t["done"] = false
  t["priority"] = 4
  t["description"] = "Learn Cloud Datastore"
end

次にエンティティの種類 Task に対してクエリを実行し、優先度が 4 以上となる未処理のタスクを優先度順に降順で並べ替えるように指示します。

C#

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Query query = new Query("Task")
{
    Filter = Filter.And(Filter.Equal("done", false),
        Filter.GreaterThanOrEqual("priority", 4)),
    Order = { { "priority", PropertyOrder.Types.Direction.Descending } }
};

Go

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

query := datastore.NewQuery("Task").
	Filter("Done =", false).
	Filter("Priority >=", 4).
	Order("-Priority")

Java

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Query<Entity> query = Query.newEntityQueryBuilder()
    .setKind("Task")
    .setFilter(CompositeFilter.and(
        PropertyFilter.eq("done", false), PropertyFilter.ge("priority", 4)))
    .setOrderBy(OrderBy.desc("priority"))
    .build();

Node.js

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

const query = datastore.createQuery('Task')
  .filter('done', '=', false)
  .filter('priority', '>=', 4)
  .order('priority', {
    descending: true
  });

PHP

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

$query = $datastore->query()
    ->kind('Task')
    ->filter('done', '=', false)
    ->filter('priority', '>=', 4)
    ->order('priority', Query::ORDER_DESCENDING);

Python

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

query = client.query(kind='Task')
query.add_filter('done', '=', False)
query.add_filter('priority', '>=', 4)
query.order = ['-priority']

Ruby

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

query = datastore.query("Task").
        where("done", "=", false).
        where("priority", ">=", 4).
        order("priority", :desc)

しかし、ここでは(祖先クエリではなく)結果整合性クエリを使用しているため、クエリの結果に新規エンティティが含まれていない可能性があります。それでも commit 直後であれば、結果整合性クエリでもほぼすべての書き込みが有効です。多くのアプリケーションでは、現在のユーザー自身による変更を処理する場合、結果整合性クエリによる結果を提供するソリューションで通常は十分であり、この程度のレイテンシは完全に許容範囲となります。

強整合性を実現する場合は、祖先パスを持つエンティティを作成する方法がより効果的です。祖先パスは、作成された各エンティティがグループ化されるための共通のルート エンティティを識別します。この例では、種類が TaskList、名前が default となる祖先パスを使用します。

C#

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Key taskListKey = _db.CreateKeyFactory("TaskList").CreateKey("default");
Key taskKey = new KeyFactory(taskListKey, "Task").CreateKey("sampleTask");
Entity task = new Entity()
{
    Key = taskKey,
    ["category"] = "Personal",
    ["done"] = false,
    ["priority"] = 4,
    ["description"] = "Learn Cloud Datastore"
};

Go

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

parentKey := datastore.NameKey("TaskList", "default", nil)
key := datastore.IncompleteKey("Task", parentKey)

task := Task{
	Category:    "Personal",
	Done:        false,
	Priority:    4,
	Description: "Learn Cloud Datastore",
}

// A complete key is assigned to the entity when it is Put.
var err error
key, err = client.Put(ctx, key, &task)

Java

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Key taskKey = datastore.newKeyFactory()
    .addAncestors(PathElement.of("TaskList", "default"))
    .setKind("Task")
    .newKey("sampleTask");
Entity task = Entity.newBuilder(taskKey)
    .set("category", "Personal")
    .set("done", false)
    .set("priority", 4)
    .set("description", "Learn Cloud Datastore")
    .build();

Node.js

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

const task = {
  key: taskKey,
  data: {
    category: 'Personal',
    done: false,
    priority: 4,
    description: 'Learn Cloud Datastore'
  }
};

PHP

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

$parentKey = $datastore->key('TaskList', 'default');
$key = $datastore->key('Task')->ancestorKey($parentKey);
$task = $datastore->entity(
    $key,
    [
        'Category' => 'Personal',
        'Done' => false,
        'Priority' => 4,
        'Description' => 'Learn Cloud Datastore'
    ]
);

Python

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

key_with_parent = client.key(
    'TaskList', 'default', 'Task', 'sample_task')

task = datastore.Entity(key=key_with_parent)

task.update({
    'category': 'Personal',
    'done': False,
    'priority': 4,
    'description': 'Learn Cloud Datastore'
})

Ruby

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

task_key = datastore.key [["TaskList", "default"], ["Task", "sampleTask"]]

task = datastore.entity task_key do |t|
  t["category"] = "Personal"
  t["done"] = false
  t["priority"] = 4
  t["description"] = "Learn Cloud Datastore"
end

これで、共通のルート エンティティによって識別されるエンティティ グループ内で強整合性の祖先クエリを実行できます。

C#

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Query query = new Query("Task")
{
    Filter = Filter.HasAncestor(_db.CreateKeyFactory("TaskList")
        .CreateKey("default"))
};

Go

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

ancestor := datastore.NameKey("TaskList", "default", nil)
query := datastore.NewQuery("Task").Ancestor(ancestor)

Java

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

Query<Entity> query = Query.newEntityQueryBuilder()
    .setKind("Task")
    .setFilter(PropertyFilter.hasAncestor(
        datastore.newKeyFactory().setKind("TaskList").newKey("default")))
    .build();

Node.js

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

const ancestorKey = datastore.key(['TaskList', 'default']);

const query = datastore.createQuery('Task')
  .hasAncestor(ancestorKey);

PHP

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

$ancestorKey = $datastore->key('TaskList', 'default');
$query = $datastore->query()
    ->kind('Task')
    ->hasAncestor($ancestorKey);

Python

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

ancestor = client.key('TaskList', 'default')
query = client.query(kind='Task', ancestor=ancestor)

Ruby

Cloud Datastore クライアントのインストールと作成の詳細については、Cloud Datastore クライアント ライブラリをご覧ください。

ancestor_key = datastore.key "TaskList", "default"

query = datastore.query("Task").
        ancestor(ancestor_key)

この方法では、1 つのタスクリストにつき 1 つのエンティティ グループに書き込むことで、強整合性を実現できます。ただしタスクリストへの変更は、1 秒あたりわずか 1 回の書き込みに制限されます(エンティティ グループに対してサポートされる制限)。書き込みの使用率が高くなると予想されるアプリケーションの場合は、他の手段を使用することを検討してください。たとえばアプリケーションが、ユーザーがメッセージを公開メッセージ ボードに投稿できるゲストブックであるとします。最新の投稿は、有効期限を設定して Memcache 内に保管し、Memcache と Datastore 内の最新投稿を表示する方法や、Cookie に投稿をキャッシュして、何らかの状態を URL などに完全に格納する方法が考えられます。現在のユーザーがアプリケーションに投稿している間に、このユーザーにデータを提供するキャッシュ ソリューションを見つけることが目標となります。トランザクション内で lookup、祖先クエリ(読み取りポリシーが結果整合性に設定されていないことが前提)、または何らかの処理を実行する場合は、常に最新の書き込みデータが表示されることに留意してください。

その他のトランザクション使用例については、こちらをご覧ください。

トランザクションに対するエンティティ グループの制限

データをエンティティ グループに編成すると、実行可能なトランザクションの種類が制限されることがあります。

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

エンティティとインデックスの更新方法の詳細については、トランザクション分離の記事を参照してください。

外出先でもリソースをモニタリング

Google Cloud Console アプリを入手して、プロジェクトの管理にお役立てください。

フィードバックを送信...

Cloud Datastore のドキュメント