NDB トランザクション

トランザクションとは、アトミック、つまりトランザクションが部分的に適用されないことが保証されている単一または一連のオペレーションです。トランザクション内のオペレーションは、すべて適用されるか 1 つも適用されないかのどちらかとなります。トランザクションの最大期間は 60 秒で、30 秒後に 10 秒間のアイドル期限があります。

NDB 非同期 API を使用すると、アプリケーションは、独立した複数のトランザクションを同時に管理できます。同期 API は、@ndb.transactional() デコレータを使用したシンプルな API を提供します。トランザクションのコンテキストにおいて、デコレート関数が実行されます。

@ndb.transactional
def insert_if_absent(note_key, note):
    fetch = note_key.get()
    if fetch is None:
        note.put()
        return True
    return False
note_key = ndb.Key(Note, note_title, parent=parent)
note = Note(key=note_key, content=note_text)
inserted = insert_if_absent(note_key, note)

トランザクションが互いに「衝突」すると、失敗します。NDB は、失敗したトランザクションを数回、自動的に再試行します。トランザクションが再試行されると、関数が複数回呼び出される場合があります。再試行する回数には上限があります(デフォルトは「3」)。再試行をしてもトランザクションが成功しない場合、NDB は、TransactionFailedError を生成します。再試行する回数を変更するには、retries=Ntransactional() デコレータに渡します。再試行する回数が「0」の場合は、トランザクションは、1 回の試行の後、失敗であっても再試行は行いません。再試行する回数が「N」の場合、トランザクションは総計で「N+1」回の試行を行うことになります。例:

@ndb.transactional(retries=1)
def insert_if_absent_2_retries(note_key, note):
    # do insert

トランザクションでは、祖先クエリのみが許可されます。デフォルトでは、トランザクションは、同じエンティティ グループ(同じ祖先エンティティのキーを持つエンティティ)のエンティティでなければ機能しません。

クロスグループ(「XG」)トランザクション(最大 25 個のエンティティ グループを許可)を指定するには、xg=True を渡します。

@ndb.transactional(xg=True)
def insert_if_absent_xg(note_key, note):
    # do insert

クロスグループ トランザクションは、複数のエンティティ グループをまたいで動作します。クロスグループ トランザクションでは、1 つのグループに対するトランザクションとは異なり、複数のエンティティ グループのエンティティを更新してもトランザクションが失敗することはありません。

この関数で例外が発生するとトランザクションは直ちに中止され、呼び出しコードで認識されるよう、NDB で同じ例外が再度生成されます。ndb.Rollback 例外を生成することによって、トランザクションを強制的に通知なく失敗させることができます(この場合、関数を呼び出すと、None が返されます)。強制的に再試行させるメカニズムは存在しません。

関数の中には、必ずしも常にトランザクションで実行するわけではないものがあります。そのような関数は、@ndb.transactional でデコレートするのではなく、コールバック関数として ndb.transaction() に渡します。

def insert_if_absent_sometimes(note_key, note):
    # do insert
inserted = ndb.transaction(lambda:
                           insert_if_absent_sometimes(note_key, note))

コードがトランザクション内で実行されるかどうかをテストする場合は、in_transaction() 関数を使用します。

「トランザクション向け」の関数をすでにトランザクション内にあるコードによって呼び出した場合に、その関数がどのように動作するかを指定できます。@ndb.non_transactional デコレータは、関数をトランザクション内で実行しないように指定します。トランザクション内で呼び出された場合は、トランザクション外で実行します。@ndb.transactional デコレータと ndb.transaction 関数では、propagation キーワード引数を使用します。たとえば、関数によって新しい独立したトランザクションが開始されるようにする場合は、そのようにデコレートします。

@ndb.transactional(propagation=ndb.TransactionOptions.INDEPENDENT)
def insert_if_absent_indep(note_key, note):
    # do insert

プロパゲート タイプは、他のコンテキスト オプションおよびトランザクション オプションとともにリストされます。

処理内容を正確に理解しないと、トランザクションの動作と NDB のキャッシュの動作の組み合わせを把握することが困難になる場合があります。トランザクション内でエンティティを修正したが、トランザクションをコミットしていない場合、NDB のコンテキスト キャッシュの値は修正されたものになり、基盤となるデータストアの値は修正されていないものになります。

トランザクション タスクをキューに入れる

Datastore トランザクションの一部として、トランザクションが正常にコミットされた場合にのみタスクをキューに入れることができます。トランザクションがコミットされない場合、タスクはキューに入りません。トランザクションがコミットされると、タスクがキューに入ります。キューに追加されたタスクはすぐに実行されません。そのため、タスクはトランザクションにおいてアトミックではありません。ただし、キューに入った後は、成功するまで再試行されます。これは、デコレート関数によってキューに入るすべてのタスクに適用されます。

トランザクション タスクは、トランザクションの成功(購入を確認するメールの送信など)に依存する Datastore 以外の処理を、トランザクションに組み合わせることができるので便利です。また、データストアの処理をトランザクションに組み合わせることもできます。たとえば、トランザクションが成功した場合にのみ、エンティティ グループに対する変更をトランザクションの外部で commit できます。

アプリケーションの 1 つのトランザクションでタスクキューに挿入できるトランザクション タスクは最大で 5 つです。トランザクション タスクの名前をユーザーが指定することはできません。

from google.appengine.api import taskqueue
from google.appengine.ext import ndb
@ndb.transactional
def insert_if_absent_taskq(note_key, note):
    taskqueue.add(url=flask.url_for('taskq_worker'), transactional=True)
    # do insert