Skip to content
Community

EXPLORE TOPICS

Editorial approach

New to CoreTrailKit?

Observe your services. Keep the evidence close.

CoreTrailKit
Back to articles
GUIDE/ Google Cloud

Firestore: read before writing in a transaction

A Firestore transaction queues a document update and then tries to read another value. The Python client raises ReadAfterWriteError because transaction reads must precede writes.

By CoreTrailKit3 min read

01

Locate the first queued write

Inspect the transaction callback and any helpers it calls. A helper that queues a set or update can make a later read invalid even if those operations are separated by several layers of application code.

Our incorrect callback queued a value and then called DocumentReference.get with the same transaction. The SDK raised ReadAfterWriteError before a document-read request or commit reached the fixture. The rejection occurred in the transaction API contract, not in a database query result.

02

Gather the required reads first

List the documents and values needed for the decision and read them before adding any writes. Reads in a transaction should not be used to inspect the result of a write you just queued. Calculate the proposed value in memory instead.

For a real operation, validate existence, types, and domain limits on those snapshots. The fixed example below expects an existing counter with an integer value; it intentionally raises if that prerequisite is not satisfied.

03

Compute and queue the change in one callback

The callback returns the computed value while queuing the update through the supplied transaction. It does not issue an unrelated read after that update. The caller invokes it through the SDK’s transactional decorator.

Use an appropriately authorized client and document reference in your application. Running the final call below writes the selected counter if the transaction commits; it is not a read-only inspection command.

python · EXAMPLE
from google.cloud import firestore

@firestore.transactional
def increment_counter(transaction, reference):
    snapshot = reference.get(transaction=transaction)
    if not snapshot.exists:
        raise ValueError("Counter is missing")
    current = snapshot.get("value")
    if not isinstance(current, int) or isinstance(current, bool):
        raise ValueError("Counter value must be an integer")
    updated = current + 1
    transaction.update(reference, {"value": updated})
    return updated

# Authorized write when deliberately invoked:
# updated = increment_counter(db.transaction(), counter_reference)

04

Keep retryable callbacks free of external side effects

The service can cause a transaction callback to run again under contention. Sending an email or making a payment inside the callback can repeat an effect even though document changes are committed atomically.

Choose an application design that records the necessary intent atomically and handles external work with an appropriate idempotent process. Moving a side effect into a transaction callback does not make that external system part of the Firestore transaction.

05

Verify ordering and the final state

The corrected local callback read a fixture value of one and submitted an update to two in one commit. The checks observed the real SDK’s read and commit calls, while the stub supplied deterministic document and transaction responses.

For production verification, use an approved test document and confirm the final state after the transaction completes. Add separate contention and failure tests when those guarantees matter. A local stub cannot prove that a live service handles concurrent writers, rules, or retries as your application requires.

Know the limits

Reproduced with google-cloud-firestore 2.34.1 against a deterministic local gRPC contract fixture. The actual SDK rejected the read-after-write before a read or commit; the reordered callback read once and committed the computed value. No real Firestore isolation, contention, retry conflict, or IAM behavior was tested.

Sources & further reading

A technical publication by CoreTrailKit.

Browse all articles

CONTINUE EXPLORING