English ▼
Skip to page content Skip to main navigation
e.g. "templates" or "datastore"

# NDB Transactions

#### Experimental!

NDB is an experimental, innovative, and rapidly changing new feature for App Engine. Unfortunately, being on the bleeding edge means that we may make backwards-incompatible changes to NDB. We will inform the community when this feature is no longer experimental.

A transaction is an operation or set of operations that either succeeds completely or fails completely. An application can perform multiple operations and calculations in a single transaction. Using the NDB asynchronous API, an application can manage multiple transactions simultaneously if they are independent. The synchronous API offers a simplified API using the `ndb.transaction()` function.

The `transaction()` function takes a callback function that is executed in the context of the transaction.

This simple example acts as a transactional get-or-insert operation:

    key = ndb.Key(Greeting, 'joe')

    def callback():
      # 'key' here uses the key variable in the outer scope;
      # the callback function is a closure.
      ent = key.get()
      if ent is None:
        ent = Greeting(key=key, message='Hey Joe')
        ent.put()
      return ent

    ent = ndb.transaction(callback)

If the transaction "collides" with another, it fails; NDB automatically retries such failed transactions a few times. Thus, the callback function may be called multiple times if the transaction is retried. There is a limit (default 3) to the number of retries attempted; if the transaction still does not succeed, the transaction() call raises `TransactionFailedError`. You can change the retry count by passing `retries=N` to the `transaction()` call. A retry count of 0 means the transaction is attempted once but not retried if it fails; a retry count of N means that the transaction may be attempted a total of N+1 times. Example:

    ent = ndb.transaction(callback, retries=1) # Total of 2 tries

By default, a transaction can only work with entities in the same entity group (entities whose keys have the same "ancestor").

In transactions, only ancestor queries are allowed.

You can specify cross-group ("XG") transactions (which allow up to five entity groups), by passing `xg=True`:

    ent = ndb.transaction(callback, xg=True)

If the transaction's callback function raises an exception, the transaction is immediately aborted and the `transaction()` call re-raises the exception. You can force a transaction to fail silently by raising the `ndb.Rollback` exception (the `transaction()` call returns `None` in this case). There is no mechanism to force a retry.

Often you have a function that must be run inside a transaction. The `@transactional` decorator automatically runs the decorated function inside a transaction, with the added twist that if a transaction is already in effect, it doesn't start a new transaction but just calls the function. For example:

    @ndb.transactional
    def get_or_insert(keyname):
      key = ndb.Key(Greeting, keyname)
      ent = key.get()
      if ent is None:
        ent = Greeting(key=key, message='Hey Rodrigo')
        ent.put()
      return ent

    moraes = get_or_insert('rodrigo')

The `@transactional` decorator does not support passing a retry count or other options.

To test whether some code is running inside a transaction, use the `in_transaction()` function:

    def maybe_run_in_transaction(func):
      if ndb.in_transaction():
        return func()
      else:
        return transaction(func)