Transactions

Google Cloud 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

A transaction is a set of Cloud Datastore operations on one or more
entities. 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. Transactions have a
maximum duration of 60 seconds with a 10 second idle expiration time after 30
seconds.

An operation might fail when:

Too many concurrent modifications are attempted on the same entity group.

The transaction exceeds a resource limit.

Cloud Datastore encounters an internal error.

In all these cases, the Cloud 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 Cloud Datastore
transactions idempotent so that if you repeat a transaction, the end result will
be the same.

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

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

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 Cloud 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 should be idempotent, which means they have the same result when
executed multiple times. Note that datastore.Get is not idempotent when
unmarshaling slice fields.

What can be done in a transaction

Google Cloud Datastore imposes restrictions on what can be done inside a single
transaction.

All Cloud 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 twenty-five entity groups if the
transaction is a cross-group 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 a
cross-group transaction.

When two or more transactions simultaneously attempt to modify entities in one
or more common entity groups, only the first transaction to commit its changes
can succeed; all the others will fail on commit. 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, Google Cloud Datastore 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, Google Cloud Datastore again
checks he last update time for the entity groups used in the transaction. If it
has hanged since the initial check,
an error is returned. For an explanation of entity
groups, see the
Cloud Datastore Overview page.

Isolation and consistency

Outside of transactions, Cloud Datastore's isolation level is closest
to read committed. Inside of transactions, serializable isolation is enforced.
This means that another transaction cannot concurrently modify the data that is
read or modified by this transaction.
Read the serializable isolation
wiki and the Transaction Isolation
article for more information on isolation levels.

In a transaction, all reads reflect the current, consistent state of
Cloud Datastore at the time the transaction started. Queries and gets
inside a transaction are guaranteed to see a single, consistent snapshot of
Cloud 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
Cloud 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.

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 might 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.
If the entity is updated during the
transaction, then the transaction is retried until all steps are completed
without interruption.

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

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.

Note: 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 Cloud 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
Cloud 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,
cross-group 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 Cloud 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-Cloud Datastore actions to a transaction that depend on the
transaction succeeding (such as sending an email to confirm a purchase). You
can also tie Cloud 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.