Firestore Transactions — Atomic Read-Then-Write Operations for Data Consistency
In this tutorial, you will learn about Firestore Transactions. We cover key concepts, practical examples, and best practices to help you master this topic.
Firestore transactions enable atomic read-then-write operations where the read result is used to determine the write, with automatic retry on concurrent modification and full ACID-like guarantees within a single Transaction.
What You'll Learn
- Writing transaction functions with read and write operations
- How Firestore handles transaction retries and conflicts
- Best practices for transaction design
Why It Matters
Transactions prevent race conditions in scenarios like updating counters, transferring balances, or reserving inventory. Without transactions, concurrent requests cause data corruption. DodaTech uses transactions for license key activation and usage tracking.
sequenceDiagram
Client->>Firestore: Begin transaction
Client->>Firestore: Read document balance
Firestore-->>Client: balance: 100
Note over Client: Calculate new balance: 90
Client->>Firestore: Write new balance: 90
Firestore-->>Client: Commit (may retry if conflict)
Note over Firestore: If another client wrote balance
between read and write, retry
Code Examples
// Transaction: Transfer money between accounts
import { runTransaction, doc } from 'firebase/firestore';
async function transferMoney(fromId, toId, amount) {
try {
await runTransaction(db, async (transaction) => {
// Read documents inside the transaction
const fromDoc = await transaction.get(doc(db, 'accounts', fromId));
const toDoc = await transaction.get(doc(db, 'accounts', toId));
if (!fromDoc.exists()) {
throw new Error('Source account does not exist');
}
const fromBalance = fromDoc.data().balance;
if (fromBalance < amount) {
throw new Error('Insufficient funds');
}
// Write updates
transaction.update(doc(db, 'accounts', fromId), {
balance: fromBalance - amount
});
transaction.update(doc(db, 'accounts', toId), {
balance: toDoc.data().balance + amount
});
});
console.log('Transfer successful');
} catch (error) {
console.error('Transfer failed:', error);
}
}
// Transaction: Counter increment with retry handling
async function incrementCounter(counterId) {
const counterRef = doc(db, 'counters', counterId);
return runTransaction(db, async (transaction) => {
const counterDoc = await transaction.get(counterRef);
if (!counterDoc.exists()) {
transaction.set(counterRef, { count: 1 });
return 1;
}
const newCount = counterDoc.data().count + 1;
transaction.update(counterRef, { count: newCount });
return newCount;
});
}
# Python Firestore transaction
from google.cloud import firestore
db = firestore.Client()
def transfer_funds(from_id, to_id, amount):
from_ref = db.collection('accounts').document(from_id)
to_ref = db.collection('accounts').document(to_id)
@firestore.transactional
def execute(transaction):
from_snapshot = transaction.get(from_ref)
to_snapshot = transaction.get(to_ref)
if not from_snapshot.exists:
raise ValueError('Source account does not exist')
from_balance = from_snapshot.get('balance')
if from_balance < amount:
raise ValueError('Insufficient funds')
transaction.update(from_ref, {'balance': from_balance - amount})
transaction.update(to_ref, {
'balance': to_snapshot.get('balance') + amount
})
transaction = db.transaction()
execute(transaction)
Common Mistakes
1. Reading Documents After Writing in the Same Transaction
All reads must occur before writes. Firestore transactions do not support read-after-write.
2. Using Transactions for Single Write Operations
Use simple set/update/delete for operations that do not depend on read results.
3. Not Handling Transaction Failures
Transactions can fail due to contention. Implement retry logic with exponential backoff.
4. Writing Too Many Documents in a Transaction
Transactions are limited to 500 affected documents. Exceeding this causes failure.
5. Performing Non-Firestore Operations Inside Transaction Functions
Transaction functions should only contain Firestore reads and writes. External API calls cause issues.
Practice Questions
- What is the difference between a batch write and a transaction?
- How does Firestore handle concurrent transaction conflicts?
- Can you read a document after writing to it in the same transaction?
- What is the maximum number of documents a transaction can affect?
- When should you use a transaction vs a batch write?
Answers:
- Transactions allow reading before writing; batch writes are write-only.
- Firestore retries the transaction function when it detects concurrent modification.
- No. All reads must precede all writes in a transaction.
- 500 documents.
- Use transactions when the write depends on a read value; use batches for independent writes.
Challenge: Build a ticket reservation system with Firestore transactions. Users can reserve tickets, the transaction checks availability and decrements the count atomically. Handle concurrent reservations and timeouts.
FAQ
Mini Project
Build a simple banking application with Firestore transactions for deposits, withdrawals, and transfers. Include concurrent access testing to demonstrate transaction safety, error handling for insufficient funds, and a transaction log for auditing.
What's Next
Learn about Firestore security rules for access control, then explore security rules functions for advanced validation.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro