This page describes transactional data contention, serializability, and isolation. For transaction code samples, see transactions and batched writes instead.
Transactions and data contention
For a transaction to succeed, the documents retrieved by its read operations must remain unmodified by operations outside the transaction. If another operation attempts to change one of those documents, that operations enters a state of data contention with the transaction.
- Data contention
- When two or more operations compete to control the same document. For example, one transaction might require a document to remain consistent while a concurrent operation tries to update that document's field values.
Cloud Firestore resolves data contention by delaying or failing one of the operations. The Cloud Firestore client libraries automatically retry transactions that fail due to data contention. After a finite number of retries, the transaction operation fails and returns an error message:
ABORTED: Too much contention on these documents. Please try again.
When deciding which operation to fail or delay, behavior depends the type of client library.
The mobile/web SDKs use optimistic concurrency controls.
The server client libraries use pessimistic concurrency controls.
Data contention in the mobile/web SDKs
The mobile/web SDKs (iOS, Android, Web, C++) use optimistic concurrency controls to resolve data contention.
- Optimistic concurrency controls
- Based on the assumption that data contention is not likely or that it's not efficient to hold database locks. Optimistic transactions do not use database locks to block other other operations from changing data.
Mobile/web SDKs use optimistic concurrency controls, because they can operate in environments with high latency and an unreliable network connection. Locking documents in a high latency environment would cause too many data contention failures.
In the Mobile/Web SDKs, a transaction keeps track of all the documents you read inside the transaction. The transaction completes its write operations only if none of those documents changed during the transaction's execution. If any document did change, the transaction handler retries the transaction. If the transaction can't get a clean result after a few retries, the transaction fails due to data contention.
Data contention in the server client libraries
The server client libraries (C#, Go, Java, Node.js, PHP, Python, Ruby) use pessimistic concurrency controls resolve data contention.
- Pessimistic concurrency controls
- Based on the assumption that data contention is likely. Pessimistic transactions use database locks to prevent other operations from modifying data.
Server client libraries use pessimistic concurrency controls, because they assume low latency and a reliable connection to the database.
In the server client libraries, transactions place locks on the documents they read. A transaction's lock on a document blocks other transactions, batched writes, and non-transactional writes from changing that document. A transaction releases its document locks at commit time. It also releases its locks if it times out or fails for any reason.
When a transaction locks a document, other write operations must wait for the transaction to release its lock. Transactions acquire their locks in chronological order.
Data contention between transactions is closely related to database isolation levels. A database's isolation level describes how well the system handles conflicts between concurrent operations. Conflict comes from the following database requirements:
- Transactions require accurate, consistent data.
- To efficiently use resources, databases execute operations concurrently.
In systems with a low isolation level, a read operation within a transaction might read inaccurate data from uncommitted changes in a concurrent operation.
Serializable isolation defines the highest isolation level. Serializable isolation means that:
- You can assume that the database executes transactions in series.
- Transactions are not affected by uncommitted changes in concurrent operations.
This guarantee must hold even while the database executes multiple transactions in parallel. The database must implement concurrency controls to resolve conflicts that would break this guarantee.
Cloud Firestore guarantees serializable isolation of transactions. Transactions in Cloud Firestore are serialized and isolated by commit time.
Serializable isolation by commit time
Cloud Firestore assigns each transaction a commit time which represents a single point in time. When Cloud Firestore commits a transaction's changes to the database, you can assume all reads and writes within the transaction take place exactly at the commit time.
Actual execution of a transaction requires some span of time. The execution of a transaction begins before the commit time, and the execution of multiple operations may overlap. Cloud Firestore upholds serializable isolation and guarantees that:
- Cloud Firestore commits transactions in order by commit time.
- Cloud Firestore isolates transactions from concurrent operations with a later commit time.
In the case of data contention between concurrent operations, Cloud Firestore uses optimistic and pessimistic concurrency controls to resolve contention.
Isolation within a transaction
Transaction isolation also applies to write operations within a transaction. Queries and reads inside a transaction do not see the results of previous writes inside that transaction. Even if you modify or delete a document within a transaction, all document reads in that transaction return the version of the document at commit time, before the transaction's write operations. Read operations return nothing if the document did not exist then.