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

注: 新しいアプリケーションを作成する際には NDB クライアント ライブラリを使用することを強くおすすめします。NDB クライアント ライブラリには、Memcache API によるエンティティの自動キャッシュなど、DB クライアント ライブラリにはないメリットがあります。古い DB クライアント ライブラリを使用している場合は、DB から NDB への移行ガイドをお読みください。

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

強整合性読み取りは常に現在のデータを返します。トランザクション内で実行された場合は、単一の一貫性のあるスナップショットから得られたように見えます。ただし、クエリは強整合性を維持するために祖先フィルタを指定するか、トランザクションに参加する必要があります。トランザクションには最大で 25 のエンティティ グループを含めることができます。結果整合性読み取りにはこのような制限はなく、多くの場合に適しています。結果整合性読み取りを使用する場合は、大量のエンティティ グループ間でデータを分散できます。この結果、異なるエンティティ グループで commit を並列実行することで、書き込みスループットが向上します。ただし結果整合性読み取りがアプリケーションに適するかどうかを判断するには、その特性を理解しておく必要があります。

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

強整合性に対応するデータ構造化の方法を理解するために、App Engine ゲストブック チュートリアルの演習で 2 つの方法を比較してみます。1 つ目の方法では、グリーティングごとに新しいルート エンティティを作成します。

Java 7

protected Entity createGreeting(
    DatastoreService datastore, User user, Date date, String content) {
  // No parent key specified, so Greeting is a root entity.
  Entity greeting = new Entity("Greeting");
  greeting.setProperty("user", user);
  greeting.setProperty("date", date);
  greeting.setProperty("content", content);

  datastore.put(greeting);
  return greeting;
}

import webapp2
from google.appengine.ext import db

class Guestbook(webapp2.RequestHandler):
  def post(self):
    greeting = Greeting()
    ...

次に、エンティティの種類 Greeting でクエリを実行し、直近 10 件のグリーティングを求めます。

import webapp2
from google.appengine.ext import db

class MainPage(webapp2.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')
    greetings = db.GqlQuery("SELECT * "
                            "FROM Greeting "
                            "ORDER BY date DESC LIMIT 10")

しかし、非祖先クエリを使用しているため、このスキームでクエリの実行に使用されているレプリカには、クエリの実行時までに新しいグリーティングが反映されていない可能性があります。それでも commit の数秒以内であれば、ほぼすべての書き込みが非祖先クエリで使用できます。多くのアプリケーションでは、現在のユーザー自身による変更を処理する場合、非祖先クエリによる結果を提供するソリューションで通常は十分であり、この程度のレプリケーション レイテンシは完全に許容範囲となります。

アプリケーションにとって強整合性が重要である場合、単一の強整合性祖先クエリで読み取る必要があるすべてのエンティティ間で同じルート エンティティを識別する祖先パスを使用してエンティティを書き込むという代替方法があります。

import webapp2
from google.appengine.ext import db

class Guestbook(webapp2.RequestHandler):
  def post(self):
    guestbook_name=self.request.get('guestbook_name')
    greeting = Greeting(parent=guestbook_key(guestbook_name))
    ...

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

import webapp2
from google.appengine.ext import db

class MainPage(webapp2.RequestHandler):
  def get(self):
    self.response.out.write('<html><body>')
    guestbook_name=self.request.get('guestbook_name')

    greetings = db.GqlQuery("SELECT * "
                            "FROM Greeting "
                            "WHERE ANCESTOR IS :1 "
                            "ORDER BY date DESC LIMIT 10",
                            guestbook_key(guestbook_name))

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

このページは役立ちましたか?評価をお願いいたします。

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

Python の App Engine スタンダード環境