お気に入り | 日本語 | ログイン

トランザクション

App Engine データストアは「トランザクション」をサポートします。トランザクションとは、完全に成功または失敗した一連の操作のことです。アプリケーションは、複数の操作と演算を単一のトランザクションで実行できます。

トランザクションの使用

「トランザクション」とは、完全に成功または失敗した一連のデータストア操作のことです。トランザクションが成功すると、意図した効果がすべてデータストアに適用されます。トランザクションが失敗すると、意図した効果は適用されません。

データストアの書き込み操作はすべてアトミックです。エンティティの作成、更新、削除は「実行される」か「実行されない」かのどちらかです。操作が失敗する原因として、同時にエンティティを変更しようとするユーザーが多すぎて競合頻度が高くなっていることが考えられます。また、アプリケーションが割り当て制限に達したために操作が失敗する場合もあります。そのほか、データストアの内部エラーが原因の場合もあります。いずれの場合にも、操作の効果は適用されず、Datastore API は例外を発行します。

アプリケーションは、ステートメントとデータストア操作のセットを 1 つのトランザクションで実行できます。そのため、ステートメントと操作のどれかが例外を発行すると、そのセット内のデータストア操作はすべて適用されません。アプリケーションは Python 関数を使ってトランザクション内で実行する処理を定義し、この関数を引数として db.run_in_transaction() を呼び出します。

from google.appengine.ext import db

class Accumulator(db.Model):
  counter = db.IntegerProperty()

def increment_counter(key, amount):
  obj = db.get(key)
  obj.counter += amount
  obj.put()

q = db.GqlQuery("SELECT * FROM Accumulator")
acc = q.get()

db.run_in_transaction(increment_counter, acc.key(), 5)

db.run_in_transaction() は関数オブジェクトと関数に渡す位置引数およびキーワード引数を使用します。関数が値を返すと、db.run_in_transaction() がその値を返します。

関数が戻るとトランザクションが実行され、データストア操作の効果がすべて適用されます。関数が例外を発行すると、トランザクションは「ロールバック」され、その効果は適用されません。

関数が Rollback 例外を発行すると、db.run_in_transaction() が None を返します。その他の例外の場合、db.run_in_transaction() は例外を再発行します。

トランザクションで実行できること

データストアには 1 つのトランザクションで実行できることに制限があります。

1 つのトランザクションでのデータストア操作は、すべてが同じエンティティ グループ内のエンティティを対象とします。これには、キーによるエンティティの取得、エンティティの更新、エンティティの削除が含まれます。なお、各ルート エンティティは個別のエンティティ グループに属するので、1 つのトランザクションが複数のルート エンティティについて作成や操作を行うことはできません。 エンティティ グループの説明は、キーとエンティティ グループをご覧ください。

アプリケーションは、トランザクション実行中にクエリを実行できません。しかし、トランザクション中にキーを使用してデータストア エンティティを取得することは可能です。このとき、フェッチされたエンティティは他のトランザクションの内容と一致していることを保証します。トランザクション実行前にキーを準備するか、キー名または ID を使用してトランザクション内にキーを構築できます。

アプリケーションは、1 つのトランザクションで何度もエンティティの作成や更新を行うことはできません。

その他の Python コードはすべてトランザクション関数の中で使用できます。トランザクション関数でデータストア操作以外の副作用が発生しないようにしてください。他のユーザーが同じエンティティ グループ内のエンティティを更新しているためにデータストア操作が失敗した場合、トランザクション関数を複数回呼び出すことができます。この場合、Datastore API はトランザクションを一定の回数だけ再試行します。トランザクションがすべて失敗すると、db.run_in_transaction() が TransactionFailedError を発行します。db.run_in_transaction() の代わりに db.run_in_transaction_custom_retries() を使用し、トランザクションの再試行の回数を調整できます。

同様に、トランザクション関数を呼び出すコードがトランザクションの成功に伴う副作用を元に戻す方法を認識しない限り、トランザクション関数でそのような副作用が発生しないようにしてください。たとえば、トランザクションが新しいデータストア エンティティを保存し、作成されたエンティティの ID を後で使用するために保存する場合、エンティティの作成がロールバックされないことから、そのトランザクションは失敗し、保存された ID が目的のエンティティを参照しません。この場合、呼び出しコードが、保存された ID を使用しないように注意する必要があります。

トランザクションの用途

この例では、トランザクションの 1 つの用途として、相対的な新しいプロパティ値を使って現在のエンティティのプロパティ値を更新します。

def increment_counter(key, amount):
  obj = db.get(key)
  obj.counter += amount
  obj.put()

オブジェクトをフェッチするコードの実行と、変更したオブジェクトの保存の間に、別のユーザーによって値が更新されている可能性があります。そのため、処理をトランザクションとする必要があります。トランザクションを使用しないと、ユーザーのリクエストは、他のユーザーによる更新の前の counter の値を使用し、保存により上書きしてしまいます。トランザクションを使用すると、アプリケーションは他のユーザーによる更新を検知できます。 トランザクションの実行中にエンティティが更新された場合、そのトランザクションは、すべてのステップが中断なく完了するまで再試行されます。

その他に多いトランザクションの用途は、名前付きキーを使ったエンティティの更新や、エンティティがまだない場合のエンティティの作成です。

class SalesAccount(db.Model):
  address = db.PostalAddressProperty()
  phone_number = db.PhoneNumberProperty()

def create_or_update(parent_obj, account_id, address, phone_number):
  obj = db.get(Key.from_path("SalesAccount", account_id, parent=parent_obj))
  if not obj:
    obj = SalesAccount(key_name=account_id,
                       parent=parent_obj,
                       address=address,
                       phone_number=phone_number)
  else:
    obj.address = address
    obj.phone_number = phone_number

  obj.put()

前述のように、トランザクションが必要なのは、他のユーザーが同じ文字列 ID を使ってエンティティの作成や更新を行おうとしている場合です。トランザクションを使用していない場合、エンティティが存在せず、2 人のユーザーが作成しようとした場合、2 人目のユーザーが 1 人目のユーザーの更新内容を上書きしてしまいますが、上書きしたことに気づきません。 トランザクションを使用している場合、2 回目の作成は再試行され、その時点でエンティティの存在が認識され、そのエンティティは更新されます。

作成や更新は非常に便利なので、そのための組み込みメソッドが用意されています。キーの名前とパスのエンティティが存在しない場合は、Model.get_or_insert() がキー名、オプションの親、引数をモデル コンストラクタに渡します。取得の試みと作成が 1 つのトランザクションで実行されるので、(そのトランザクションが成功すれば)メソッドは常に、実際のエンティティを表すモデル インスタンスを返します。

ヒント: トランザクションで使用するエンティティが変更されて、トランザクションの再試行が必要になる可能性を減らすため、トランザクションができるだけ速く実行されるようにしてください。トランザクションの外部データをできるだけ多く準備してからトランザクションを実行し、一定の状態に基づいてデータストア操作が行われるようにします。アプリケーションは、トランザクション内部で使用するオブジェクトのキーを準備してから、トランザクション内部のエンティティをフェッチするようにしてください。