Domain 3 of 5 · Chapter 1 of 4

Selecting Storage

Read the access pattern before you name a service

Take a single fact, "a user placed an order," and watch where it needs to live: the order itself must be written atomically and read back consistently a millisecond later (a transaction), the running total of today's revenue must be aggregated across millions of orders (analytics), the product image attached to it is a blob, and the "customers who bought this" panel reads a precomputed value in microseconds (a cache). One business fact, four different access patterns, four different stores. That is why the exam's storage questions are won by profiling the workload first and choosing the service second.

Profile along three axes. First, transactional (OLTP) versus analytical (OLAP): OLTP means many small reads and writes that must be individually consistent (insert an order, debit a balance); OLAP means a few large queries that scan and aggregate huge row counts (sum revenue by region for the year). A store tuned for one is wrong for the other. Second, the data model: relational tables with joins and a fixed schema; key-value or wide-column rows fetched by a single key; hierarchical documents; in-memory key-value; or opaque blobs and files. Third, the non-functional needs: throughput, read/write latency, total scale, consistency, and whether the workload is regional or global.

Google's own guidance frames the choice exactly this way, mapping a workload's characteristics to a managed database or storage service[1]. The diagram below is the spine of the whole subtopic: every later section is one branch of it. Work top-down through structured-versus-unstructured, then transactional-versus-analytical, then the data model, and you land on one primary service. When a scenario seems to fit two, the tie-breaker is almost always a non-functional need: global scale forces Spanner over Cloud SQL, query-by-key forces Bigtable over a relational store, full-table scans force BigQuery over either.

Structured data?No (blobs/files)YesCloud Storage (+ BigLake)Transactional (OLTP)?No (OLAP)BigQueryYesRelational with joins?YesNo (NoSQL)Global scale / strong global?NoYesCloud SQL(AlloyDB if high-perf)SpannerQuery by single key, huge scale?YesNo (docs)BigtableFirestoreMemorystore: cache in front of any store
Storage-selection flow modeled on Google Cloud's database and storage product guidance; in-memory cache layers over whichever store is chosen.

The relational and NoSQL stores, and where each one breaks

Group the operational stores by data model first, because their differences are deltas inside a model, not separate mental models.

Relational (SQL, joins, fixed schema)

Cloud SQL is the fully managed service for MySQL, PostgreSQL, and SQL Server, and it is the default for a regional transactional application. Google positions it for transactional workloads that fit within a single region's vertical and read-replica scaling, documented in the Cloud SQL overview[2]. AlloyDB for PostgreSQL is the same operational profile with much higher performance: it is a fully managed, PostgreSQL-compatible database, and its built-in columnar engine accelerates analytical queries by holding data in memory in columnar format, per the AlloyDB overview[3]. Pick AlloyDB when a single-region PostgreSQL app outgrows Cloud SQL's performance but does not need cross-region writes.

Spanner is the only relational option that scales writes horizontally and stays strongly consistent across regions. It combines the relational model with NoSQL-style horizontal scale and offers up to a 99.999% availability SLA, per the Spanner overview[4]. The rule that the exam tests: choose Spanner over Cloud SQL only when the workload genuinely needs global scale or high availability, because that guarantee carries a higher cost floor than a single-region Cloud SQL instance. A small regional app on Spanner is a classic over-engineering distractor.

NoSQL (no joins, scale by key)

Bigtable is a wide-column NoSQL store: a sparsely populated table that scales to billions of rows and thousands of columns, sized in terabytes to petabytes, built for high-throughput, low-latency workloads such as time-series, IoT, financial, and ad-tech data, per the Bigtable overview[5]. Its hard constraint is the query model: Bigtable retrieves data efficiently only by row key or a contiguous key range. There are no secondary indexes and no joins, so schema design centers on the row key. A question that needs ad-hoc filtering on many columns is pointing away from Bigtable.

Firestore is a serverless document database with automatic scaling, real-time synchronization, and offline support, aimed at mobile, web, and server apps, per the Firestore docs[6]. It stores hierarchical documents and is the answer when clients need live-updating data, not when you need warehouse-scale analytics.

In-memory (cache, not of record)

Memorystore is the managed in-memory store for Redis and Memcached, used as a caching and session layer for low-latency reads of hot data, per the Memorystore docs[7]. Debunk the common misread on sight: a cache is not durable storage. Memorystore sits in front of a database or Cloud Storage to accelerate hot reads; the authoritative copy always lives in a durable store. Treating it as a system of record is the trap.

The one shared model across all five: each store is tuned for one access pattern, and using it outside that pattern (analytics on Cloud SQL, multi-column filters on Bigtable, durability on Memorystore) is where it breaks.

Operational stores: one store per access patternRelational (joins, SQL)Cloud SQLAlloyDBSpannerglobal, horizontal scaleRegional default vs global scaleNoSQL (scale by key)Bigtablewide-column, by row keyFirestoredocuments, real-time syncIn-memoryMemory-storecache only
Operational stores grouped by data model; Memorystore is a cache layer, never the system of record.

Analytics and lake storage: BigQuery, Cloud Storage, and BigLake

When the access pattern is analytical, the database tier is the wrong tier. BigQuery is the fully managed, serverless data warehouse: you analyze data with SQL and built-in machine learning without provisioning infrastructure, per the BigQuery introduction[8]. It is columnar and built for OLAP, so it scans and aggregates billions of rows where Cloud SQL or Spanner would be far slower and far more expensive. The selecting-storage decision here is binary: analytics over large tables means BigQuery, not a transactional database.

Cloud Storage is object storage for unstructured and semi-structured data at any scale with no schema defined up front, which is why it is the standard landing zone and data-lake substrate on Google Cloud, per the Cloud Storage docs[9]. Images, logs, backups, and open-format files (Parquet, ORC, Avro) belong here. Debunk the predictable misread immediately: Cloud Storage holds files but does not run SQL. To query objects you do not move them into a database; you put a table interface over them.

That interface is BigLake. A BigLake table sits over open-format files (Parquet, ORC, Avro, Iceberg, Delta Lake, CSV, JSON) on Cloud Storage and decouples access to the table from access to the underlying bucket through a connection service account, so users query the table without direct Cloud Storage permissions, per the BigLake introduction[10]. BigLake enforces row-level and column-level security plus dynamic data masking, and those policies apply on all access, including through Spark, Hive, and Trino connectors via the BigQuery Storage API. This is how one open copy of data stays portable across engines while staying governed, the bridge that lets the lake and the warehouse share data without duplicating it. Deep warehouse modeling lives in the data-warehouse subtopic and lake operations in data-lake; here the only call is which tier the access pattern selects.

For multicloud, BigQuery Omni runs the BigQuery engine inside AWS and Azure regions so you can analyze data in Amazon S3 or Azure Blob Storage without copying it into Google Cloud, preserving residency, per the BigQuery Omni introduction[11]. It is the answer when analytics must reach data sitting in another cloud.

Cloud Storageopen files: Parquet, ORC, Avro, IcebergBigLake tablerow/column security, maskinggoverns in placeBigQuery engineSpark / Hive / Trinovia Storage APISQLconnectors
BigLake puts a governed table over open-format files on Cloud Storage so BigQuery and OSS engines query one copy without separate bucket permissions.

Planning cost, performance, and lifecycle

Each store bills on the dimension that matches its access pattern, and getting cost right means sizing to that dimension and expiring data the platform can expire for you.

BigQuery: storage separate from compute

BigQuery prices storage and compute independently. Storage is billed per GB, and a table or partition not edited for 90 consecutive days automatically moves to long-term storage at roughly half the active-storage price, with no change to performance or availability, per BigQuery storage pricing[12]. Compute is separate: on-demand pricing charges per TB scanned, while capacity (slot) pricing buys dedicated processing. The exam lever is that you cut scan cost by partitioning and clustering so queries read fewer bytes, and you cap storage growth with table and partition expiration, which deletes old data on a schedule, per managing partitioned tables[13].

Bigtable: pay per node

Bigtable cost is driven by the number of nodes you provision per cluster plus the storage consumed; throughput scales roughly linearly with nodes, so you size the cluster to the required queries-per-second and latency, per Bigtable instances and nodes[14]. Under-provisioning nodes raises latency; over-provisioning wastes money. This is the opposite of BigQuery's serverless model, and confusing the two billing shapes is a common distractor.

Cloud Storage: class plus lifecycle

Cloud Storage has four storage classes (Standard, Nearline, Coldline, Archive) that share the same low-latency access and eleven-nines (99.999999999%) annual durability and differ only in cost profile and minimum storage duration: Standard none, Nearline 30 days, Coldline 90 days, Archive 365 days, per storage classes[15]. Colder classes cost less to store but more to retrieve, and deleting or rewriting an object before its minimum duration still incurs the charge for the full minimum. Automate the cost curve with Object Lifecycle Management, which transitions objects to colder classes or deletes them based on conditions such as age, per the lifecycle docs[16]. A rule like "move to Nearline after 30 days, Coldline after 90, delete after 365" matches storage cost to declining access frequency without manual work.

The through-line: match the billing model to the access pattern (serverless scan cost for analytics, node count for key workloads, storage class for blobs), then let lifecycle and expiration policies retire stale data automatically rather than paying to keep it hot.

Standardhot, min 0 daysNearlinemin 30 daysColdlinemin 90 daysArchivemin 365 daysDelete30d90dage365dColder class: cheaper to store, pricier to retrieve, longer minimum-duration chargeObject Lifecycle Management applies these transitions and the final delete automatically by age
Object Lifecycle Management transitions objects through colder classes by age and deletes them on schedule; colder classes trade lower storage cost for higher retrieval cost.

Exam-pattern recognition

Storage-selection questions follow a small set of stems. Learn the signal word in the stem and the distractor it dangles.

"Globally distributed, strongly consistent, relational"

The stem names worldwide users, horizontal write scale, or a 99.999% SLA on a relational schema. The answer is Spanner. The distractor is Cloud SQL with read replicas; replicas scale reads, not global writes, and do not give cross-region strong consistency. The inverse distractor also appears: a small single-region app described with no scale need, where Spanner is the over-engineered wrong answer and Cloud SQL (or AlloyDB for performance) is right.

"High write throughput, low latency, queried by key"

The stem says time-series, IoT sensor data, financial ticks, or ad-tech at millions of events per second, accessed by a single identifier. The answer is Bigtable. The distractor is BigQuery (which is for analytical scans, not low-latency single-key serving) or Firestore (document model, not wide-column at that throughput). If the same stem then asks for ad-hoc multi-column analytics on that data, the answer shifts to BigQuery, often fed from Bigtable.

"Mobile/web app, real-time sync, offline"

The stem mentions clients that must see live updates and work offline. The answer is Firestore. The distractor is Cloud SQL (no real-time client sync) or Memorystore (not durable).

"Run SQL analytics over terabytes" or "dashboards / ad-hoc queries"

The answer is BigQuery. The distractor is running the analytics on the operational database (Cloud SQL or Spanner), which scans too slowly and competes with transactions; the correct pattern replicates to BigQuery, often with Datastream.

"Store raw files / images / logs / backups" or "data-lake landing zone"

The answer is Cloud Storage. If the stem then needs SQL over those files without copying them, the answer is BigLake (or BigQuery external tables); if the files sit in another cloud, it is BigQuery Omni.

"Cache to reduce database load / sub-millisecond reads"

The answer is Memorystore. The distractor treats it as the primary store; the durable copy stays in a database.

Cost and lifecycle stems

"Reduce storage cost for data accessed less over time" points to Cloud Storage storage classes driven by Object Lifecycle Management, not to deleting and re-uploading by hand. "Stop paying to keep old warehouse data" points to BigQuery table/partition expiration and the automatic long-term-storage discount after 90 days untouched. Sizing a key-value workload's cost is about Bigtable node count, while a BigQuery cost question is about bytes scanned (partitioning/clustering) and slot capacity, never node count.

Managed store by access pattern

Access patternPrimary serviceData modelQuery / read profileScale ceiling
Regional OLTP relationalCloud SQL (AlloyDB for high-perf)Relational SQLTransactional reads/writes, joinsSingle regional primary
Global OLTP relationalSpannerRelational SQLStrongly consistent transactions worldwideHorizontal, multi-region
Key-value / wide-columnBigtableWide-column NoSQLLow-latency lookups by row keyPetabytes, millions ops/sec
Document / real-time appFirestoreDocument NoSQLDocument reads, live sync, offlineAutomatic, serverless
In-memory cacheMemorystoreKey-value (Redis/Memcached)Sub-millisecond cache readsBounded by instance memory
OLAP analyticsBigQueryColumnar warehouseLarge scans, aggregations, SQL MLPetabytes, serverless
Blob / data-lake filesCloud Storage (BigLake to query)Object / open filesObject get/put; SQL via BigLakeEffectively unlimited

Decision tree

Structured data?No (blobs/files)YesCloud StorageBigLake to query in placeTransactional (OLTP)?No (OLAP)BigQueryYesRelational with joins?YesNo (NoSQL)Global scale / strong global?NoYesCloud SQLAlloyDB if high-perfSpannerQuery by single key, huge scale?YesNo (docs)BigtableFirestoreAlways optional: Memorystorein-memory cache in front of any store

Sharp facts the exam loves — give these one last read before exam day.

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Name the access pattern before you name the store

The storage choice follows the workload's access pattern, not the data's surface shape. Classify it on three axes first: transactional (OLTP, many small consistent reads and writes) versus analytical (OLAP, large scans that aggregate); the data model (relational, key-value or wide-column, document, in-memory, or blob); and the non-functional needs (throughput, latency, total scale, regional versus global). Only then does one managed service fall out. When two services seem to fit, a non-functional need is almost always the tie-breaker.

Cloud SQL is the default for a regional transactional relational app

For an OLTP relational workload (MySQL, PostgreSQL, or SQL Server) that fits in one region, Cloud SQL is the default managed choice; it scales vertically and with read replicas inside a region. Reach past it only when a hard non-functional need appears: global write scale points to Spanner, and much higher single-region PostgreSQL performance points to AlloyDB.

Trap Reaching for Spanner for a small single-region app; its global strong consistency carries a higher cost floor than a single Cloud SQL instance and is wasted when there is no global-scale need.

9 questions test this
AlloyDB is the high-performance PostgreSQL pick before going global

AlloyDB for PostgreSQL is a fully managed, PostgreSQL-compatible database with a built-in columnar engine that accelerates analytical queries by holding data in memory in columnar format. Choose it when a single-region PostgreSQL app needs significantly higher transactional and analytical performance than Cloud SQL but does not need cross-region writes, so you gain throughput without taking on Spanner's global cost floor.

Trap Jumping to Spanner for a single-region performance problem; AlloyDB raises PostgreSQL performance without the multi-region price, and Spanner is only warranted when global scale or a 99.999% SLA is genuinely required.

Firestore is the store for real-time, offline-capable app data

Firestore is a serverless NoSQL document database with automatic scaling, real-time synchronization, and offline support, built for mobile, web, and server apps. Pick it when clients must see live-updating hierarchical documents and keep working offline. It is not a warehouse, so analytical scans over its data belong in BigQuery, not Firestore.

Trap Choosing Cloud SQL for a mobile app that needs live client sync and offline mode; a relational database has no built-in real-time document synchronization, which is Firestore's signature.

Memorystore is a cache layer, never the system of record

Memorystore is the managed in-memory store for Redis and Memcached, used as a caching and session layer for low-latency reads of hot data such as sessions and frequently-read keys. It sits in front of a durable store to cut database load and latency; because in-memory data is not durable, the authoritative copy always stays in a database or Cloud Storage.

Trap Treating Memorystore as durable primary storage; an in-memory cache can lose data, so using it as the system of record risks losing the authoritative copy.

Route OLAP to BigQuery, not the operational database

Analytical questions (dashboards, ad-hoc SQL over billions of rows, in-warehouse ML) select BigQuery, the serverless columnar warehouse, because it is built to scan and aggregate at scale. Running that analytics on Cloud SQL or Spanner scans too slowly and competes with live transactions; the correct pattern keeps the operational store for OLTP and replicates to BigQuery for OLAP, often via Datastream.

Trap Answering an analytics-over-huge-tables stem with the transactional database it already lives in; OLTP stores are tuned for small consistent operations, not full-table aggregation.

Cloud Storage is the blob and data-lake store; query it with BigLake, not SQL directly

Unstructured and semi-structured blobs (images, logs, backups, Parquet/Avro files, a data-lake landing zone) belong in Cloud Storage, which stores objects at any scale with no up-front schema. Cloud Storage itself does not run SQL: to query those files in place you put a table interface over them with BigLake or a BigQuery external table, rather than copying the data into a database.

Trap Expecting to run SQL against Cloud Storage objects directly; object storage serves get and put, and querying files in place needs a BigLake or external-table layer over them.

Cloud Storage classes share durability and latency; they differ on cost and minimum duration

Cloud Storage has four classes (Standard, Nearline, Coldline, Archive) with identical low-latency access and eleven-nines (99.999999999%) annual durability; they differ only in cost profile and minimum storage duration: Standard none, Nearline 30 days, Coldline 90 days, Archive 365 days. Colder classes cost less to store but more to retrieve, and deleting or rewriting an object before its minimum duration still incurs the full minimum charge, so match the class to how often the data is actually read.

Trap Assuming colder classes are slower to read or less durable; access latency and durability are the same across classes, so the only real penalty is higher retrieval cost and the minimum-duration charge.

Automate storage cost with Object Lifecycle Management, not manual moves

Object Lifecycle Management transitions Cloud Storage objects to colder classes (SetStorageClass) or deletes them (Delete) automatically based on conditions such as object age. A rule like move to Nearline after 30 days, Coldline after 90, delete after 365 tracks declining access frequency with no manual work, which is how you keep storage cost in line with the access pattern over time.

Trap Scripting periodic deletes and re-uploads to control cost; lifecycle rules apply class transitions and deletions automatically by age, and a delete-then-reupload can even re-trigger minimum-duration charges.

1 question tests this
BigQuery bills storage separately from compute, with a long-term-storage discount

BigQuery prices storage per GB and compute separately, and a table or partition not modified for 90 consecutive days automatically drops to long-term storage at about half (roughly 50% less) the active-storage rate, with no change to performance, durability, or availability. Compute is either on-demand (per TB scanned) or capacity slots; you cut scan cost by partitioning and clustering so queries read fewer bytes.

Trap Sizing BigQuery cost by node count the way you would Bigtable; BigQuery is serverless, so its cost is bytes scanned plus stored, not provisioned nodes.

Cap BigQuery storage growth with table and partition expiration

BigQuery table expiration and partition expiration delete data on a schedule, so old rows are removed automatically instead of accumulating storage cost; when a partition expires, BigQuery deletes the data in that partition. Set partition expiration on a time-partitioned table to retire stale partitions, and pair it with the automatic long-term-storage discount on data left untouched for 90 days to keep a warehouse's storage bill matched to what is still queried.

3 questions test this
Bigtable cost scales with provisioned node count per cluster

Bigtable bills on the number of nodes provisioned per cluster plus the storage used, and throughput scales roughly linearly with nodes, so you size the cluster to the required queries per second and latency. Under-provisioning nodes raises latency under load; over-provisioning wastes money. This provisioned-capacity model is the opposite of BigQuery's serverless, scan-priced billing.

Trap Treating Bigtable like a serverless store with no capacity to plan; its latency and throughput depend on the node count you provision, so an undersized cluster degrades under load.

Spanner dual-region keeps data in one country at 99.999%

When a single country has only two Google Cloud regions and you need both data residency and the highest availability, pick a Cloud Spanner dual-region configuration: it replicates across the two in-country regions, stays within national borders, and delivers the 99.999% availability SLA (Enterprise Plus edition). Reach for it on prompts like Germany or Japan residency for a banking or healthcare app.

Trap Choosing a Spanner multi-region configuration when the prompt mandates data stay inside one country, since multi-region spans countries and breaks residency.

6 questions test this
Put the Spanner leader region where the writes originate

In a Spanner multi-region config (for example nam3) every write is committed by the default leader region first, so cross-region writes are slow when the leader is far from the writing app. Move the default leader to the read-write region closest to where write traffic originates (and use leader-aware routing) to cut write latency; after an app failover, re-point the leader to the new active region.

Trap Adding nodes or read replicas to fix slow writes, when the latency comes from the leader region being remote from the writers, not from a capacity shortfall.

5 questions test this
Interleave Spanner child tables to co-locate them with their parent

When a Spanner child table (order items, transactions) is almost always read together with its parent (orders, customers), declare it with INTERLEAVE IN PARENT and prefix the child primary key with the parent key. Spanner then physically stores child rows next to the parent row in the same split, turning the join into a local primary-key lookup and removing cross-network traffic.

Trap Leaving the related tables independent and relying on a regular JOIN, which forces Spanner to fetch rows from separate splits for the hot parent-child access pattern.

5 questions test this
Lead a Bigtable time-series row key with the entity id, end with the timestamp

For Bigtable time-series data keyed per device/sensor/vehicle, build the row key as id#timestamp so the high-cardinality id prefix spreads writes across the key space, and append a reversed timestamp when recent-first reads matter. A timestamp-first key forces every new write to the end of the key range, hotspotting one node.

Trap Putting the timestamp at the front of the row key, which makes monotonically increasing writes pile onto a single tablet server.

9 questions test this
Bigtable app profiles route by single-cluster for isolation, multi-cluster for latency

Add clusters to a Bigtable instance (replication) and steer traffic with app profiles: single-cluster routing pins a workload to one cluster, isolating a batch-scan job from latency-sensitive serving without duplicating data; multi-cluster routing sends each request to the nearest available cluster and auto-fails-over, giving low latency and high availability across regions.

Trap Reaching for multi-cluster routing to isolate a noisy batch job, which still lets both workloads land on the same cluster instead of separating them.

5 questions test this
Set Bigtable retention per column family with garbage-collection policies

Bigtable garbage collection is configured per column family, not per column, so split data with different retention into separate column families and give each its own age (TTL) or version policy. Combine rules with an intersection policy (delete only when ALL conditions hold, e.g. keep at least N versions AND 30 days) or a union policy (delete when ANY condition holds).

Trap Trying to set a different TTL on individual columns inside one family, since the garbage-collection policy applies to the whole column family.

5 questions test this
Cloud SQL REGIONAL availability survives a zone failure with no data loss

Set a Cloud SQL instance's availability type to REGIONAL (Multiple zones) for high availability: it synchronously replicates writes to a standby in a second zone via regional persistent disks, so a zone outage triggers automatic failover to the same IP with zero data loss. A promoted read replica is not automatically HA, so enable HA on it separately.

Trap Treating a read replica as the zone-failure safeguard, when only REGIONAL HA provides synchronous replication and automatic failover within the region.

11 questions test this
Cloud SQL cross-region read replica is the regional-outage DR answer

Survive a full regional outage by adding a cross-region read replica and promoting it (replica failover) when the primary region fails; asynchronous replication keeps RPO and RTO in minutes rather than hours, which is the threshold that warrants a cross-region failover configuration. On Enterprise Plus, designate it a DR replica and use the write-endpoint DNS name so failover redirects connections without changing connection strings.

Trap Counting on same-region HA for a regional disaster, when zonal HA does not survive the loss of the whole region and a cross-region replica is required.

4 questions test this
Cover steady BigQuery load with baseline slots and spikes with autoscaling

For predictable BigQuery workloads, buy a (commitment-discounted) baseline slot reservation for the always-on demand and add autoscaling slots that bill only while scaled up to absorb periodic spikes. Idle baseline slots are shared automatically across reservations in the same admin project and edition when ignore_idle_slots stays false; assign a project to 'None' to send it to on-demand pricing instead.

Trap Setting ignore_idle_slots to true to let one reservation borrow another's idle slots, which actually disables idle-slot sharing rather than enabling it.

5 questions test this

Also tested in

References

  1. Google Cloud databases and storage products
  2. Cloud SQL overview
  3. AlloyDB for PostgreSQL overview
  4. Spanner
  5. Bigtable overview
  6. Firestore documentation
  7. Memorystore documentation
  8. BigQuery introduction
  9. Cloud Storage introduction
  10. BigLake introduction
  11. BigQuery Omni introduction
  12. BigQuery pricing
  13. Managing partitioned tables (BigQuery)
  14. Bigtable instances, clusters, and nodes
  15. Cloud Storage classes
  16. Object Lifecycle Management (Cloud Storage)