5

Google App Engine で、キー名のない祖先関係を使用するときにデータが正しいことを確認するのに少し問題があります。

もう少し説明させてください。親エンティティカテゴリがあり、子エンティティアイテムを作成したいと考えています。カテゴリ名とアイテム名を受け取り、存在しない場合は両方のエンティティを作成する関数を作成したいと思います。最初に 1 つのトランザクションを作成し、必要に応じてキー名を使用して両方のトランザクションを作成しましたが、これはうまくいきました。ただし、変更が必要になる可能性があるため、名前をキーとして使用したくないことに気付き、トランザクション内でこれを実行しようとしました。

def add_item_txn(category_name, item_name):
  category_query = db.GqlQuery("SELECT * FROM Category WHERE name=:category_name", category_name=category_name)
category = category_query.get()
if not category:
    category = Category(name=category_name, count=0)

item_query = db.GqlQuery("SELECT * FROM Item WHERE name=:name AND ANCESTOR IS :category", name=item_name, category=category)
item_results = item_query.fetch(1)
if len(item_results) == 0:
  item = Item(parent=category, name=name)

db.run_in_transaction(add_item_txn, "foo", "bar")

これを実行しようとしたときにわかったのは、トランザクションでクエリを実行できないため、App Engine がこれを拒否することです: Only ancestor queries are allowed inside transactions.

これに対処する方法についてGoogleが提供するを見ると:

def decrement(key, amount=1):
    counter = db.get(key)
    counter.count -= amount
    if counter.count < 0:    # don't let the counter go negative
        raise db.Rollback()
    db.put(counter)

q = db.GqlQuery("SELECT * FROM Counter WHERE name = :1", "foo")
counter = q.get()
db.run_in_transaction(decrement, counter.key(), amount=5)

カテゴリのフェッチをトランザクションの前に移動しようとしました:

def add_item_txn(category_key, item_name):
    category = category_key.get()
    item_query = db.GqlQuery("SELECT * FROM Item WHERE name=:name AND ANCESTOR IS :category", name=item_name, category=category)
    item_results = item_query.fetch(1)
    if len(item_results) == 0:
         item = Item(parent=category, name=name)

category_query = db.GqlQuery("SELECT * FROM Category WHERE name=:category_name", category_name="foo")
category = category_query.get()
if not category:
    category = Category(name=category_name, count=0)
db.run_in_transaction(add_item_txn, category.key(), "bar")

これは機能しているように見えますが、複数のリクエストでこれを実行すると、カテゴリが重複して作成されていることがわかりました。これは、カテゴリがトランザクションの外部でクエリされ、複数のリクエストで複数のカテゴリが作成される可能性があるためです。

これらのカテゴリを適切に作成する方法を知っている人はいますか? カテゴリの作成をトランザクションに入れようとしましたが、祖先クエリに関するエラーが再び表示されました。

ありがとう!

サイモン

4

2 に答える 2

2

これがあなたの問題を解決するためのアプローチです。これは多くの点で理想的なアプローチではありません。他の AppEngineer が私よりも優れたソリューションを考え出すことを心から願っています。そうでない場合は、これを試してください。

私のアプローチでは、次の戦略を利用しています。Category エンティティのエイリアスとして機能するエンティティを作成します。カテゴリの名前は変更できますが、エイリアス エンティティはそのキーを保持し、エイリアスのキーの要素を使用してカテゴリ エンティティのキ​​ー名を作成できます。そのため、名前でカテゴリを検索できますが、そのストレージはその名前から分離されています。

エイリアスはすべて 1 つのエンティティ グループに格納されるため、トランザクションに適した祖先クエリを使用できるため、複数のコピーが作成されるリスクを負うことなく、CategoryAlias を検索または作成できます。

カテゴリとアイテムの組み合わせを検索または作成する場合、カテゴリのキー名を使用して、トランザクション内でプログラムによってキーを生成できます。また、トランザクション内でそのキーを介してエンティティを取得できます。

class CategoryAliasRoot(db.Model):
    count = db.IntegerProperty()
    # Not actually used in current code; just here to avoid having an empty
    # model definition.

    __singleton_keyname = "categoryaliasroot"

    @classmethod
    def get_instance(cls):
            # get_or_insert is inherently transactional; no chance of
            # getting two of these objects.
        return cls.get_or_insert(cls.__singleton_keyname, count=0)

class CategoryAlias(db.Model):
    alias = db.StringProperty()

    @classmethod
    def get_or_create(cls, category_alias):
        alias_root = CategoryAliasRoot.get_instance()
        def txn():
            existing_alias = cls.all().ancestor(alias_root).filter('alias = ', category_alias).get()
            if existing_alias is None:
                existing_alias = CategoryAlias(parent=alias_root, alias=category_alias)
                existing_alias.put()

            return existing_alias

        return db.run_in_transaction(txn)

    def keyname_for_category(self):
        return "category_" + self.key().id

    def rename(self, new_name):
        self.alias = new_name
        self.put()

class Category(db.Model):
    pass

class Item(db.Model):
    name = db.StringProperty()

def get_or_create_item(category_name, item_name):

    def txn(category_keyname):
        category_key = Key.from_path('Category', category_keyname)

        existing_category = db.get(category_key)
        if existing_category is None:
            existing_category = Category(key_name=category_keyname)
            existing_category.put()

        existing_item = Item.all().ancestor(existing_category).filter('name = ', item_name).get()
        if existing_item is None:
            existing_item = Item(parent=existing_category, name=item_name)
            existing_item.put()

        return existing_item

    cat_alias = CategoryAlias.get_or_create(category_name)
    return db.run_in_transaction(txn, cat_alias.keyname_for_category())

警告 emptor: 私はこのコードをテストしていません。明らかに、実際のモデルに合わせて変更する必要がありますが、使用する原則は適切だと思います。

更新:サイモン、あなたのコメントで、あなたはほとんど正しい考えを持っています。ただし、見逃してはならない重要な微妙な点があります。Category エンティティがダミー ルートの子ではないことがわかります。これらは親を共有せず、それ自体が独自のエンティティ グループのルート エンティティです。カテゴリ エンティティがすべて同じ親を持つ場合、1 つの巨大なエンティティ グループが作成され、各エンティティ グループは一度に 1 つのトランザクションしか実行できないため、パフォーマンスが低下します。

むしろ、CategoryAlias エンティティは偽のルート エンティティの子です。これにより、トランザクション内でクエリを実行できますが、各カテゴリに属する​​アイテムが CategoryAlias に関連付けられていないため、エンティティ グループが大きくなりすぎません。

また、CategoryAlias エンティティのデータは、エンティティのキ​​ーを変更せずに変更できます。実際のカテゴリ エンティティ自体の作成に使用できるキー名を生成するためのデータ ポイントとしてエイリアスのキーを使用しています。したがって、CategoryAlias に格納されている名前を変更しても、そのエンティティを同じカテゴリに一致させる能力を失うことはありません。

于 2010-08-20T16:04:31.177 に答える
0

注意すべき点がいくつかあります(おそらく単なるタイプミスだと思います)-

  1. トランザクション メソッドの最初の行は、キーに対して get() を呼び出します。これは文書化された関数ではありません。とにかく、関数に実際のカテゴリ オブジェクトを含める必要はありません。カテゴリ エンティティを使用している両方の場所でキーがあれば十分です。

  2. カテゴリまたはアイテムのいずれかで put() を呼び出しているようには見えません (ただし、データストアでデータを取得していると言うので、簡潔にするためにこれを省略したと思いますか?)

解決策に関する限り-妥当な有効期限でmemcacheに値を追加しようとすることができます-

if memcache.add("category.%s" % category_name, True, 60): create_category(...)

これにより、少なくとも複数の作成が停止します。クエリがカテゴリを返さない場合にどうすればよいかを知るのはまだ少し難しいですが、memcache からロックを取得することはできません。これは、カテゴリが作成中であることを意味します。

元のリクエストがタスク キューからのものである場合は、タスクが再実行されるように例外をスローします。

それ以外の場合は、少し待ってから再度クエリを実行できますが、これは少し危険です。

要求がユーザーからのものである場合は、競合が発生したことをユーザーに伝え、再試行することができます。

于 2010-08-20T14:59:49.920 に答える