Design, migrate, and run scalable, highly available databases on Google Cloud across relational and NoSQL services, and track your readiness for the Professional Cloud Database Engineer exam.
Prepare and test your skills
Prepare and test your skills
Identify and Implement Indexing Strategies is the practice of matching query patterns to schema design so reads use an index instead of scanning extra data. A missing or suboptimal index increases read input/output (I/O), slows queries, and can overload a primary instance and its replicas. A complete strategy balances read gains against write cost, because every additional indexed field must be updated on each write.
A missing index is found by comparing the fields used for filtering and sorting in slow queries with the indexes defined on the schema. In Firestore (opens in a new tab), running an updated query without its required composite index returns an error message with a link to create the required index in the Firebase Console. When slow reads contribute to replica lag in relational workloads, the same correlation applies before changing topology.
The common responses to replica lag from slow reads are:
Indexing the tables reduces the I/O overhead for the read pattern, while sending read traffic to the read replica and reducing load on the primary relieve contention while the fix is verified. If slow writes remain after reads are indexed, the write queries themselves must be isolated and fixed because indexes alone do not resolve write contention.
A Firestore composite index is a named definition that tells Firestore which documents to pre-sort for a specific query pattern. Each definition names a collectionGroup, such as instruments, a queryScope of COLLECTION, and an ordered list of fields where each field has a fieldPath and an order of ASCENDING or DESCENDING. The order of fields in the definition must match the order used by the query filter and sort, including nested paths such as price.currency.
For sharded time-series queries, three related patterns each need their own composite index that leads with the shard key and ends with the timestamp:
| Query pattern | Indexed fields in order |
|---|---|
| Recent instruments by exchange | shard DESCENDING, exchange ASCENDING, timestamp DESCENDING |
| Recent instruments by type | shard DESCENDING, instrumentType ASCENDING, timestamp DESCENDING |
| Recent instruments by currency | shard DESCENDING, price.currency ASCENDING, timestamp DESCENDING |
New indexes for sharded timestamps are added to the index definition file as entries in the indexes array and then deployed with firebase deploy --only firestore:indexes. After deployment, the updated queries can run without returning the missing-index error, and reads flow from the query to the pre-sorted index entries instead of scanning unindexed documents.
A write hot spot is a performance tradeoff created when sequential indexed values prevent Firestore from scaling index writes. For each index write, Firestore defines a key-value entry that concatenates the document name and the value of each indexed field. Firestore organizes these index entries into groups of data called tablets, and each Firestore server holds one or more tablets. When write load to a tablet becomes too high, Firestore scales horizontally by splitting the tablet into smaller tablets and spreading the new tablets across different Firestore servers.
Firestore places lexicographically close index entries on the same tablet. If index values in a tablet are too close together, such as for timestamp fields that always increase, Firestore cannot efficiently split the tablet into smaller tablets. This creates a hot spot where a single tablet receives too much traffic, and read and write operations to that hot spot become slower. By sharding a timestamp field, the concatenated shard value plus timestamp gives Firestore enough space between index entries to split the entries among multiple tablets, even though the timestamp values remain close together.
Exam tip: An index on a sequential timestamp alone concentrates writes on one tablet, while a sharded index that leads with shard spreads the same writes across multiple tablets.
Evaluate Query Performance and Execution Plans is the practice of finding why a query is slow by separating instance, client, and storage effects and then tracing the costly path through scans, locks, and resource use. A slow query can be caused by a blocked main thread, a full scan of the keyspace, document locks held by another transaction, or a slow disk underneath the database. The analysis flows from latency measurement to operation review to contention and capacity checks, so each layer either explains the delay or points to the next layer.
Isolating the latency source determines whether delay originates in the database instance, the client application, or the network between them. For Memorystore for Valkey (opens in a new tab), the test runs from a Compute Engine virtual machine (VM) in the same region and virtual private cloud (VPC) network as the instance, so cross-region routing is removed from the measurement. The valkey-cli tool continuously pings the server and reports minimum, maximum, and average latency, and an average of 1 millisecond or less means the instance is healthy and responding quickly. If that test is fast but the application is still slow, the delay sits outside the instance in network distance across regions or zones, high central processing unit (CPU) or memory use on the client, exhausted connection pools, or application logic bottlenecks.
Run the isolation test in order:
valkey-tools with apt-get on Debian or Ubuntu or yum on Red Hat Enterprise Linux (RHEL) or CentOS.redis-cli --latency -h ENDPOINT_ADDRESS -p PORT, adding --tls and certificate authorities (CAs) when in-transit encryption is used.Ctrl+C to stop and compare the average to the 1-millisecond baseline.The ordered test matters because each step removes one variable. The same-region VM removes distance, the correct install provides the measurement tool, and the timed sample provides minimum, maximum, and average values that can be compared to the baseline.
Resource-intensive operations are commands that block the single Valkey main thread and make many queries look slow at once. When the main thread is blocked, the instance can show high latency and client timeouts, memory pressure from commands that grow memory, data loss during node replication and synchronization, and starved health checks, observability, and replication. The fix is to replace full-space operations with incremental ones, offload reads, and limit data lifetime.
Safer alternatives for heavy operations include:
SCAN instead of KEYS to iterate the keyspace without blocking the main threadSlow sequential writes can also come from index hot spots. Firestore stores each index write as a key-value entry that concatenates the document name and indexed field values, and it groups lexicographically close entries into tablets held by Firestore servers. When values are too close together, such as timestamps, Firestore cannot split the tablet efficiently, so one tablet receives too much traffic and reads and writes slow down. Sharding the timestamp with a random shard prefix spreads entries across tablets and lets Firestore split and scale horizontally.
Exam tip: Use SCAN, not KEYS, for scanning the entire keyspace because KEYS blocks the main thread while SCAN iterates resource-efficiently.
Locking and data contention occurs in Firestore when two or more operations compete to control the same document and one operation requires the document to remain consistent while another tries to update it. Firestore resolves contention by delaying or failing one operation, automatically retrying transactions that fail, and returning ABORTED: Too much contention on these documents. Please try again. after a finite number of retries. The behavior depends on the database concurrency mode, which describes how the system resolves contention between concurrent operations. Firestore guarantees serializable isolation by commit time, which means transactions commit in order by commit time, are isolated from concurrent operations with a later commit time, and are unaffected by uncommitted changes even while the database executes transactions in parallel.
The two Firestore concurrency modes differ in how they protect documents read by a transaction:
| Mode | How it protects reads | Effect on other writers |
|---|---|---|
| PESSIMISTIC | Transaction places database locks on documents it reads and releases them at commit time, timeout, or failure | Other transactions, batched writes, and non-transactional writes must wait; locks are acquired in chronological order |
| OPTIMISTIC | Transaction tracks documents it read and commits writes only if none changed during execution, retrying on change | Other operations are not blocked, but the transaction retries and then fails if it cannot get a clean result |
Mode selection also depends on the client. Mobile and web software development kits (SDKs) always emulate optimistic concurrency with write preconditions on document versions and do not use built-in transactions, so they behave optimistically even when the database is set to PESSIMISTIC. Server client libraries use built-in transactions and follow the database setting, with Standard edition defaulting to PESSIMISTIC for low-latency reliable connections and Enterprise edition defaulting to OPTIMISTIC to support scans over entire collections without locking many documents. Within a transaction, reads must come before writes, and reads return the version at commit time without seeing the transaction's own prior writes.
Underlying storage and memory pressure makes query execution paths look resource-intensive even when the query shape has not changed. For etcd-backed control planes, slow reads show log entries such as etcdserver: read-only range request ... took too long and slow writes show wal: sync duration of 2.466870869s, expected less than 1s, and frequent entries indicate disk slowness. In Cloud Monitoring Metrics explorer, continuously high p99 for etcd_disk_wal_fsync_duration_seconds over 10 milliseconds and for etcd_disk_backend_commit_duration_seconds over 25 milliseconds confirms the same condition. That confirmation then leads to checking virtual disk read and write latency, high input/output operations per second (IOPS) from other VMs on the datastore, and throttled read and write bandwidth.
Memory stress is tracked with /instance/memory/maximum_utilization, and use approaching 80% with expected growth means scale up the instance to protect performance and make room for new data. Connection pressure is tracked with /instance/node/stats/rejected_connections_count for connection rejection when the maximum clients limit is reached and /instance/node/clients/connected_clients to see load per node. Responses to connection pressure include:
CLIENT KILL for leaked connectionsDiagnose Database Contention and Locking Mechanisms is the analysis of how concurrent Firestore transactions compete for the same documents and how concurrency settings shape the result. Firestore in Native mode manages concurrent operations with transactions that provide serializable isolation and configurable concurrency controls. Data contention occurs when two or more operations compete to control the same document, and Firestore resolves it by delaying or failing one operation.
Data contention in Firestore means a transaction requires documents it read to stay consistent while a concurrent operation tries to update those same documents. For a transaction to succeed, the documents retrieved by its read operations must remain unmodified by operations outside the transaction. Firestore resolves contention by delaying or failing one of the operations, and client libraries automatically retry transactions that fail for this reason. After a finite number of retries, the transaction fails with ABORTED: Too much contention on these documents. Please try again. Whether Firestore delays a writer or retries the whole transaction depends on the database concurrency mode.
Concurrency controls in Firestore are the configurable database rules that decide how the system resolves conflicts between concurrent operations. The concurrency mode is set to either PESSIMISTIC or OPTIMISTIC for the database, and server transactions follow that setting.
| Mode | Assumption about contention | Locking and resolution behavior |
|---|---|---|
| PESSIMISTIC | Contention is likely | Transactions place locks on documents they read; other writers must wait for the lock to release |
| OPTIMISTIC | Contention is unlikely or locking is inefficient | Transactions track documents read and commit writes only if none of those documents changed; otherwise the handler retries |
A pessimistic lock on a document blocks other transactions, batched writes, and non-transactional writes from changing that document. Transactions acquire their locks in chronological order, and other write operations must wait until the holding transaction reaches commit time. A transaction releases its document locks at commit time, and it also releases locks if it times out or fails for any reason.
Client type and database edition together determine which concurrency controls actually apply to Firestore transactions. Server client libraries for C#, Go, Java, Node.js, PHP, Python, and Ruby use built-in transactions that follow the database-level concurrency mode. The default mode is PESSIMISTIC for Standard edition, which assumes low latency and a reliable connection, and OPTIMISTIC for Enterprise edition, which supports operations that scan over entire collections without locking a large number of documents.
Mobile and web SDKs always emulate optimistic concurrency with write preconditions on document versions, regardless of the database concurrency mode setting. Mobile and web SDKs do not use the built-in transactions feature, so even a database configured for PESSIMISTIC still behaves optimistically for those clients. This design avoids holding locks over high-latency and unreliable network connections, where locking would cause too many contention failures. To reduce locking and contention for server workloads, run server client libraries from a Google Cloud compute product as close to the Firestore database location as possible.
Exam tip: Mobile and web SDKs always behave optimistically, even when the database concurrency mode is set to PESSIMISTIC.
The concurrency mode for Firestore is a database option that can be viewed and changed with the gcloud CLI. Running gcloud firestore databases describe with the project ID and database ID shows the current server-side concurrency mode. Running gcloud firestore databases update --concurrency-mode=PESSIMISTIC or --concurrency-mode=OPTIMISTIC with the same project and database identifiers changes the mode that server client libraries will use.
Serializable isolation in Firestore is the guarantee that transactions behave as if they executed in series, even while the database executes multiple transactions in parallel. An isolation level describes how well the system shields transactions from conflicts, and serializable isolation is the highest level because transactions are not affected by uncommitted changes in concurrent operations. This guarantee matters because transactions need accurate, consistent data while the database also executes operations concurrently for efficiency.
Firestore implements serializable isolation by assigning each transaction a commit time that represents a single point in time. All reads and writes within a transaction can be treated as taking place exactly at that commit time, even though actual execution spans time and overlaps with other operations. Firestore commits transactions in order by commit time and isolates each transaction from concurrent operations with a later commit time. When operations contend at overlapping times, Firestore uses pessimistic or optimistic concurrency controls to enforce that ordering.
Isolation within a Firestore transaction means queries and reads inside the transaction do not see the results of previous writes inside that same transaction. Even if the transaction modifies or deletes a document, document reads return the version of the document as of the commit time before the transaction write operations. Read operations return nothing if the document did not exist at that point. As a practical ordering rule, document reads must come before document writes in the transaction.
firebase deploy --only firestore:indexes.valkey-cli latency from a same-region, same-VPC Compute Engine VM averages 1 millisecond or less.SCAN, not KEYS, to scan the Valkey keyspace because KEYS blocks the single main thread.PESSIMISTIC.Last updated