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

# Transactions

The App Engine Datastore supports transactions. A transaction is an operation or set of operations that is atomic—either all of the operations in the transaction occur, or none of them occur. An application can perform multiple operations and calculations in a single transaction.

- Using Transactions
- What Can Be Done In a Transaction
- Isolation and Consistency
- Uses for Transactions
- Transactional Task Enqueuing

## Using Transactions

A transaction is a set of Datastore operations on one or more entity. Each transaction is guaranteed to be atomic, which means that transactions are never partially applied. Either all of the operations in the transaction are applied, or none of them are applied.

An operation may fail when:

- Too many users try to modify an entity group simultaneously.
- The application reaches a resource limit.
- The Datastore encounters an internal error.

In all cases, the Datastore API returns an error.

Note: If your app receives an error when submitting a transaction, it does not always mean that the transaction failed. You can receive `ErrConcurrentTransaction` in cases where transactions have been committed and eventually will be applied successfully. Whenever possible, make your Datastore transactions idempotent so that if you repeat a transaction, the end result will be the same.

Transactions are an optional feature of the Datastore; you're not required to use transactions to perform Datastore operations.

The `datastore.RunInTransaction` function runs the provided function in a transaction.

    package counter

    import (
        "appengine"
        "appengine/datastore"
        "http"
        "os"
    )

    func init() {
        http.HandleFunc("/", handler)
    }

    type Counter struct {
        Count int
    }

    func handler(w http.ResponseWriter, r *http.Request) {
        c := appengine.NewContext(r)

        key := datastore.NewKey(c, "Counter", "mycounter", 0, nil)
        count := new(Counter)
        err := datastore.RunInTransaction(c, func(c appengine.Context) os.Error {
            // Note: this function's argument c shadows the variable c
            //       from the surrounding function.
            err := datastore.Get(c, key, count)
            if err != nil && err != datastore.ErrNoSuchEntity {
                return err
            }
            count.Count++
            return datastore.Put(c, key, count)
        }, nil)
        if err != nil {
            c.Errorf("Transaction failed: %v", err)
            http.Error(w, "Internal Server Error", 500)
            return
        }

        fmt.Fprintf(w, "Current count: %d", count.Count)
    }

If the function returns `nil`, `RunInTransaction` attempts to commit the transaction, returning `nil` if it succeeds. If the function returns a non-`nil` error value, any Datastore changes are not applied and `RunInTransaction` returns that same error.

If `RunInTransaction` cannot commit the transaction because of a conflict it tries again, giving up after three attempts. This means that the transaction function must be idempotent (it must have the same result when executed multiple times).

## What Can Be Done In a Transaction

The Datastore imposes several restrictions on what can be done inside a single transaction.

All Datastore operations in a transaction must operate on entities in the same entity group if the transaction is a single group transaction, or on entities in a maximum of five entity groups if the transaction is a cross-group (XG) transaction. This includes querying for entities by ancestor, retrieving entities by key, updating entities, and deleting entities. Notice that each root entity belongs to a separate entity group, so a single transaction cannot create or operate on more than one root entity unless it is an XG transaction.

When two or more transactions simultaneously attempt to modify entities in one or more common entity groups, only one of those transactions can succeed. While an app is applying changes to entities in one or more entity group, all other attempts to update any entity in the group or groups fail at commit time. Because of this design, using entity groups limits the number of concurrent writes you can do on any entity in the groups. When a transaction starts, App Engine uses optimistic concurrency control by checking the last update time for the entity groups used in the transaction. Upon commiting a transaction for the entity groups, App Engine again checks the last update time for the entity groups used in the transaction. If it has changed since our initial check, App Engine returns an error. For an explanation of entity groups, see Transactions and Entity Groups.

## Isolation and Consistency

The Datastore's isolation level outside of transactions is closest to READ_COMMITTED. Inside transactions, on the other hand, the isolation level is SERIALIZABLE, specifically a form of snapshot isolation. See the Transaction Isolation article for more information on isolation levels.

In a transaction, all reads reflect the current, consistent state of the Datastore at the time the transaction started. This does not include previous puts and deletes inside the transaction. Queries and gets inside a transaction are guaranteed to see a single, consistent snapshot of the Datastore as of the beginning of the transaction. Entities and index rows in the transaction's entity group are fully updated so that queries return the complete, correct set of result entities, without the false positives or false negatives described in Transaction Isolation that can occur in queries outside of transactions.

This consistent snapshot view also extends to reads after writes inside transactions. Unlike with most databases, queries and gets inside a Datastore transaction do not see the results of previous writes inside that transaction. Specifically, if an entity is modified or deleted within a transaction, a query or get returns the original version of the entity as of the beginning of the transaction, or nothing if the entity did not exist then.

## Uses for Transactions

This example demonstrates one use of transactions: updating an entity with a new property value relative to its current value.

    func increment(c appengine.Context, key *datastore.Key) os.Error {
        datastore.RunInTransaction(c, func(c appengine.Context) os.Error {
            count := new(Count)
            if err := datastore.Get(c, key, count); err != nil {
                return err
            }
            count.Count++
            return datastore.Put(c, key, count)
        }, nil)
    }

Warning! The above sample depicts transactionally incrementing a counter only for the sake of simplicity. If your app has counters that are updated frequently, you should not increment them transactionally, or even within a single entity. A best practice for working with counters is to use a technique known as counter-sharding.

This requires a transaction because the value may be updated by another user after this code fetches the object, but before it saves the modified object. Without a transaction, the user's request uses the value of `count` prior to the other user's update, and the save overwrites the new value. With a transaction, the application is told about the other user's update.

Another common use for transactions is to update an entity with a named key, or create it if it doesn't yet exist:

    type Account struct {
        Address string
        Phone   string
    }

    func CreateOrUpdate(c appengine.Context, id, addr, phone string) os.Error {
        key := datastore.NewKey(c, "Account", id, 0, nil)
        return datastore.RunInTransaction(c, func(c appengine.Context) os.Error {
            acct := new(Account)
            err := datastore.Get(c, key, acct)
            if err != nil && err != datastore.ErrNoSuchEntity {
                return err
            }
            acct.Address = addr
            acct.Phone = phone
            return datastore.Put(c, key, acct)
        }, nil)
    }

As before, a transaction is necessary to handle the case where another user is attempting to create or update an entity with the same string ID. Without a transaction, if the entity does not exist and two users attempt to create it, the second overwrites the first without knowing that it happened.

When a transaction fails, you can have your app retry the transaction until it succeeds, or you can let your users deal with the error by propagating it to your app's user interface level. You do not have to create a retry loop around every transaction.

Tip: A transaction should happen as quickly as possible to reduce the likelihood that the entities used by the transaction will change, causing the transaction to fail. As much as possible, prepare data outside of the transaction, then execute the transaction to perform Datastore operations that depend on a consistent state. The application should prepare keys for objects used outside the transaction, then fetch the entities inside the transaction.

Finally, you can use a transaction to read a consistent snapshot of the Datastore. This can be useful when multiple reads are needed to render a page or export data that must be consistent. These kinds of transactions are often called read-only transactions, since they perform no writes. Read-only single-group transactions never fail due to concurrent modifications, so you don't have to implement retries upon failure. However, XG transactions can fail due to concurrent modifications, so these should have retries. Committing and rolling back a read-only transaction are both no-ops.

## Transactional Task Enqueuing

You can enqueue a task as part of a Datastore transaction, such that the task is only enqueued—and guaranteed to be enqueued—if the transaction is committed successfully. Once enqueued, the task is not guaranteed to execute immediately, so the task is not atomic with the transaction. Still, once enqueued, the task will retry until it succeeds. This applies to any task enqueued during a `RunInTransaction` function.

Transactional tasks are useful because they allow you to combine non-Datastore actions to a transaction that depend on the transaction succeeding (such as sending an email to confirm a purchase). You can also tie Datastore actions to the transaction, such as to commit changes to entity groups outside of the transaction if and only if the transaction succeeds.

An application cannot insert more than five transactional tasks into task queues duing a single transaction. Transactional tasks must not have user-specified names.

    datastore.RunInTransaction(c, func(c appengine.Context) os.Error {
        t := &taskqueue.Task{Path: "/path/to/worker"}
        if err := taskqueue.Add(c, t, ""); err != nil {
            return err
        }
        // ...
    }, nil)