Part 10 · Cloud / AI systems
Cloud-Native Storage as a Subsystem
Every mechanism so far — the write-ahead log, the LSM-tree's sequential writes, the leader shipping its replication log to followers — quietly assumed a disk: a block device attached to the machine, where a write lands in microseconds and you can overwrite a byte in place. In the cloud that assumption is gone. The disk is now a network service: storage lives on the far side of a network, compute is rented by the hour and may not survive the night, durability is a contract someone else signs, and moving a byte across a region boundary has a price tag. This lesson treats cloud storage not as "the same thing but managed", but as a subsystem with its own latency, failure, and cost model — and shows how that model reshapes choices you made in earlier lessons.
New capability: Reason about cloud storage as a subsystem with its own latency profile, durability contract, consistency model, failure boundaries (control-plane vs data-plane), and cost model (egress, per-op). Decide between an object store, a cloud warehouse, and a managed database for a given workload; explain how a lakehouse table format (Iceberg/Delta/Hudi) builds ACID tables, snapshots, and time travel on a store with only single-object atomicity; and place data across regions and availability zones for latency and blast-radius isolation.
1 · The disk is now a network service: object stores
What this is: S3 / GCS / Azure Blob as the durable substrate, and the four ways they differ from a local disk.
An object store (Amazon S3, Google Cloud Storage, Azure Blob Storage) is a service that stores named objects — opaque blobs of bytes, from a few KB to terabytes — addressed by a key (bucket/path/to/object), reached over HTTP. It is not a filesystem and not a block device. You PUT a whole object and GET a whole object (or a byte range of one); you do not seek to offset 4096 and overwrite 64 bytes. That single restriction — objects are immutable; there is no in-place update — is the hinge the rest of this section turns on.
The first and biggest consequence is the separation of storage and compute. On a classic database server, the disk is bolted to the CPU: if you want more storage you buy a bigger machine, and that same machine also does the computing. With an object store, the bytes live in a service that any number of compute nodes can read from over the network. Storage scales by writing more objects; compute scales by renting more (stateless) machines that read those objects. The two grow on independent axes. This is the architectural fact underneath cloud warehouses (§2) and the lakehouse pattern, and it is why a query engine can spin up 100 workers for one minute, read a dataset none of them owns, and spin back down.
The second consequence is the durability contract. The object store stores many replicas of each object across multiple availability zones (AZs — physically separate data centers within a region, with independent power and networking; introduced in §4). Providers advertise figures like "eleven nines" of durability — 99.999999999% — meaning the modeled annual probability of losing a given object is around 1 in 100 billion. Treat that number as a marketing summary of "we keep enough geographically separated replicas and continuously repair them that you should not plan to lose objects", not as a guarantee you can verify. Durability here is delegated: you no longer run the replication and anti-entropy you built in lesson 08 — the provider does, behind the API. What you give up is the ability to inspect or prove it; what you gain is not having to.
The third consequence is consistency. For years S3 was eventually consistent: PUT an object, immediately GET it, and you might get the old version or a 404, because the read hit a replica that hadn't caught up — exactly the replication-lag anomaly from lesson 08, now inside the storage service. As of 2020, S3 provides strong read-after-write consistency: after a successful PUT of a new object (or an overwrite), any subsequent GET or LIST sees that write. This removed a whole class of bugs (and the read-after-write workarounds people built), but note the boundary: it is read-after-write on a single object; it is not a multi-object transaction. You still cannot atomically swap two objects, which is why table formats (§ below) need their own commit protocols.
The fourth consequence — the one that reshapes storage-engine design — is the latency and throughput profile. A local NVMe SSD answers a small read in roughly 100 microseconds (0.1 ms). An object-store GET is an HTTP request over the network: first-byte latency is tens of milliseconds — call it 20–100 ms, often ~50 ms. That is 100× to 1000× slower per operation. But the throughput story is the opposite: you can run thousands of parallel GETs and pull many GB/s aggregate, far more bandwidth than one SSD. So the object store is a high-latency, high-bandwidth, immutable, whole-object device. That profile dictates how you store data on it:
| Local SSD assumes… | Object store forces… |
|---|---|
| Cheap small random reads (~0.1 ms) | Few, large, sequential reads — amortize the ~50 ms per-op cost over many MB; read big byte ranges, not 4 KB pages |
| In-place overwrite of a page | Write new immutable objects; never overwrite — exactly the LSM-tree / append-only instinct from lesson 04 |
| One process owns the disk | Many readers, no single owner; metadata/commit must live elsewhere |
This is why the modern data lake stores Parquet (the columnar format from lesson 04) as immutable objects, and why an LSM-tree on an object store flushes large immutable SSTables (which map perfectly onto immutable objects) rather than churning small pages. The append-only, "never overwrite, compact in the background" design you learned for write-heavy local engines turns out to be exactly what an object store wants — the immutability that was a clever optimization on a disk is a hard requirement in the cloud. And because no single process owns the bucket, the "which objects make up the current table" question can't live in a local index; it needs an external commit log. That is what table formats like Iceberg, Delta Lake, and Hudi provide: a small, atomically-updated manifest that points at the current set of immutable data objects — giving you snapshots and atomic table updates on top of a store that only guarantees single-object writes. This pattern — the lakehouse — is one of the biggest things the cloud era added to the field, and it is worth seeing the mechanism, because it is built entirely from primitives you already have.
The bind: a table is thousands of immutable Parquet objects, but appending a day's data means adding (and a compaction means replacing) many objects at once — and the store guarantees atomicity for one object only (§1's consistency point). A reader running a LIST mid-write would see a half-committed table. The fix is the same move the B-tree made with its WAL (lesson 03) and the same MVCC snapshot the database made for isolation (lesson 13), now lifted onto object storage:
Step 3 is the whole trick: because flipping the current-pointer is a single atomic operation — a single-object compare-and-swap, or an ordered append to a tiny transaction log (Delta's _delta_log/ of numbered JSON commits), or a catalog row update (Iceberg) — a reader sees either snapshot 7 or snapshot 8, never a half-written set. You bootstrapped multi-file atomicity from the one-object atomicity the store gave you, exactly as the WAL bootstrapped a recoverable B-tree from torn page writes. Three capabilities then fall out for free:
- Snapshot isolation / MVCC (lesson 13), on a lake. A reader pins a snapshot id and reads a stable table for the whole query while writers keep adding new snapshots — readers never block writers and never see torn state. Concurrent writers race on step 3's compare-and-swap; the loser detects the pointer moved and retries (optimistic concurrency).
- Time travel. Old snapshots' manifests still point at still-existing data files, so "query the table as of last Tuesday" is just reading snapshot N's manifest. Audit, reproducible training sets, and "undo a bad write by repointing to the previous snapshot" all come from this.
- Schema evolution (lesson 05) without rewriting data. The schema lives in the metadata, not the files, so adding or renaming a column is a metadata change; old Parquet files are read through the new schema by field-id mapping — the rolling-compatibility discipline of lesson 05, applied to a petabyte-scale table.
The bill is predictable from the spine: thousands of tiny commits breed metadata bloat and a small-file problem (each commit may add a few small objects, and ~50 ms-per-GET latency punishes many small reads), so table formats need exactly the lesson 03/04 cures — background compaction to merge small files into large ones, plus snapshot expiry to delete the manifests and now-unreferenced data files of ancient versions (which also bounds how far back time travel reaches). The three formats differ mainly in commit-log shape and how they handle row-level updates — Delta's ordered JSON log, Iceberg's manifest/snapshot tree + catalog, Hudi's upsert-oriented copy-on-write vs merge-on-read — but the core move, an atomic pointer swap over immutable files, is identical.
2 · Cloud warehouses: compute scales apart from data
What this is: Snowflake / BigQuery built on storage-compute separation.
A cloud data warehouse (Snowflake, Google BigQuery, Amazon Redshift in its modern form) takes §1's separation and makes it the product. Your tables live as columnar objects in the object store. To run a query you provision a compute cluster (Snowflake calls it a "virtual warehouse"; BigQuery hides it as on-demand "slots") that reads those objects, executes the analytical query (the scan/join/aggregate machinery from lesson 21), and returns a result. The cluster is ephemeral and sized on demand.
The payoff is that compute and stored data scale independently. You can store 500 TB and query it with a tiny cluster overnight and a huge one at 9am, paying only for the compute you spin up — the stored bytes cost the same regardless. Two teams can run heavy queries against the same data on two separate clusters without contending for each other's CPU, because they are different compute reading shared storage. This is the cloud-native answer to the OLTP/OLAP split from lesson 04 and the query-execution concerns of lesson 21: the warehouse is a query engine you rent by the second, sitting on top of the object store of §1.
3 · Managed databases: operational delegation
What this is: RDS / Aurora / Spanner / DynamoDB, and the trade you make by not running the database yourself.
A managed database is a database the cloud provider operates for you: Amazon RDS (managed Postgres/MySQL), Aurora (a cloud-rearchitected MySQL/Postgres that puts the storage layer on a distributed, multi-AZ service), Google Spanner (a globally-distributed, strongly-consistent relational database), DynamoDB (a managed key-value/document store). You still design the schema and write the queries; you do not run the machine. The deal is operational delegation — and like any delegation, it is a trade, not a free win.
| You hand off (provider does it) | You give up |
|---|---|
| Backups & point-in-time recovery — automatic snapshots + log retention so you can restore to any second in a window | Control — you can't touch the OS, pick arbitrary extensions/versions, or tune the storage layer below the provider's knobs |
| Patching & upgrades — security patches and minor-version upgrades applied in maintenance windows | Portability — Aurora, Spanner, DynamoDB have proprietary surfaces; migrating off is a real project (a form of lock-in) |
| Failover — multi-AZ standby promotion (the lesson-08 failover, run for you, often in under a minute) | Cost — you pay a managed premium per hour, and at very large scale self-hosting can be markedly cheaper |
| Replication & scaling — read replicas, storage auto-grow, sharding (DynamoDB) behind the API | Transparency — when it's slow or fails, you debug through the provider's metrics and a support ticket, not the box |
The decision is rarely "managed vs not" globally; it is per system. A small team running a product database almost always wins by delegating — failover and backups done right are hard, and that is exactly the lesson-08 machinery you don't want to operate at 3am. A team at scale with a hot, predictable, cost-sensitive workload may self-host to recover the managed premium and the control. Name the trade explicitly: what operational burden are you offloading, and what control / portability / cost are you paying for it?
4 · Constraints unique to the cloud: egress, IAM, regions and AZs
What this is: three cloud realities that become hard architecture constraints — money, identity, and placement.
Egress cost. Reading and writing within a region is cheap or free; moving data out is not. Egress — data leaving a region or leaving the cloud entirely (to the internet, to another cloud, sometimes to another region) — is billed per GB, and it is one of the largest and most surprising lines on a cloud bill. This is not an accounting footnote; it is an architecture constraint. It is why you put compute in the same region as its data (so the bytes never cross a priced boundary), why cross-region replication has a real recurring cost, and why "just back up everything to another cloud" is expensive at scale. Egress quietly pushes architectures toward keeping data and the things that read it co-located. We put a number on this in §6.
IAM (identity and access management). In the cloud, identity is first-class infrastructure. Every API call — a service reading an object, a function writing a row — is made by a principal (a user, a role, a service account) and is allowed or denied by an IAM policy. The discipline is least privilege: each principal gets exactly the permissions it needs and no more, so a compromised service can touch only its own bucket, not the whole account. This matters for data systems specifically because access control is no longer just rows-in-a-table permissions inside one database — it is who can read this bucket, who can decrypt this key, who can assume this role, enforced by the cloud before your code even runs. Misconfigured IAM (a public bucket, an over-broad role) is among the most common causes of real data breaches.
Regions and availability zones. A cloud is organized as regions (geographic areas — us-east-1, europe-west1) each containing several availability zones (AZs — physically separate data centers within the region, with independent power, cooling, and networking, connected by fast low-latency links). This is the cloud's placement vocabulary, and it serves the two goals from lesson 08:
- Latency. Put data and compute in a region near the user. A reader in Frankfurt hits europe-central, not a trans-Atlantic round-trip — the same local-read win as a regional read replica in lesson 08.
- Blast-radius isolation. Spread replicas across AZs so that one data center losing power doesn't take the data down — and spread critical systems across regions so that a whole-region outage isn't fatal. AZ-level replication is cheap (same region, low latency, no cross-region egress); region-level replication costs latency and egress but survives bigger failures. You are choosing how large a failure you must survive, and paying accordingly.
5 · The two failure boundaries: control plane vs data plane, and serverless
What this is: the most important cloud failure distinction, plus scale-to-zero compute.
Cloud services are split into two layers, and they fail independently:
- The data plane is the path that serves your actual requests: the object
GET/PUT, the database read/write, the running query. This is what your users touch. - The control plane is the management path: provisioning new resources, scaling a cluster up, the web console, creating IAM roles, launching new instances.
The crucial property: the data plane can keep serving while the control plane is down. In several real large-scale cloud outages, already-running instances and existing objects kept serving reads and writes fine, but you could not launch new instances, scale up, or even reliably load the console. The architectural lesson is twofold. First, do not put control-plane operations on your critical request path — if serving a user request requires provisioning a resource or calling the management API, a control-plane outage takes you down even though the data plane is healthy. Second, your recovery plans often lean on the control plane (auto-scaling, launching replacement nodes, failing over to another region), so a control-plane outage can disable exactly the mechanism you were counting on to ride out a problem. Drill this failure explicitly (lesson 28): assume the console and provisioning are gone, and check that your already-running fleet still serves.
Serverless and scale-to-zero. The cloud's extreme of compute/storage separation is serverless compute (AWS Lambda, Cloud Functions, Cloud Run): you supply a stateless function, the platform runs it on demand, and when no requests arrive it scales to zero — you run (and pay for) nothing. Paired with serverless data (DynamoDB on-demand, Aurora Serverless, BigQuery), an entire system can idle at near-zero cost and scale up instantly under load. The costs are real and specific: (1) cold starts — the first request after idle pays the time to spin up a fresh execution environment (tens to hundreds of ms, more for heavy runtimes), so latency is bursty; (2) statelessness — a function keeps nothing between invocations, so all state must live in the external data plane (object store, managed DB), which makes the "where is truth?" question below even sharper; and (3) the per-request and per-op pricing rewards exactly the few-large-operations discipline §1 already forced on you.
6 · Worked number: the egress bill of a cross-region copy
What this is: turning the §4 egress constraint into dollars and milliseconds.
Suppose you keep a 40 TB training-data lake in us-east-1 and a stakeholder asks you to also stand up a copy in eu-west-1 for a European team — and to refresh 5 TB of new data into it every month. Cross-region egress is billed at roughly 0.02 dollars per GB (rates vary by provider and region pair; use this as an order-of-magnitude figure). Note 40 TB = 40,000 GB.
Two things to read off this. First, the movement is a recurring cost separate from storage: you pay to store the second copy and ~1,200 dollars/year just to keep it fresh — and if instead of replicating you let the EU team query us-east-1 directly, every query result also crosses the priced boundary, so a chatty analytics workload can quietly out-cost the replica. Second, latency: a cross-region round-trip is tens of milliseconds versus sub-millisecond within a region, so even ignoring money, serving EU reads from us-east-1 means every read pays a trans-continental hop. Egress cost and cross-region latency together are why you co-locate compute with data and treat a second-region copy as a deliberate, budgeted disaster-recovery / latency decision — not a default. Scale this to petabytes and a careless cross-region or cross-cloud data flow becomes a six-figure line item.
Failure modes
- Control-plane outage. Console and provisioning are down; you can't launch instances, scale, or fail over — even though the data plane still serves. If recovery depends on the control plane, you're stuck. Drill it (lesson 28).
- Treating the object store like a local disk. Small random
GETs (4 KB at a time) at ~50 ms each murder throughput and rack up per-op charges; the fix is large sequential reads and immutable, append-only layout (lesson 04). - Assuming multi-object atomicity. S3 is read-after-write per object, not transactional across objects; a naive "write two objects" table update can be seen half-done. Use a table format's commit manifest.
- Surprise egress bill. A cross-region or cross-cloud data flow (replication, chatty queries, a backup to another provider) silently bills per GB; it can dwarf storage cost.
- Over-broad IAM. A public bucket or an over-permissioned role turns one compromised service into a full-account breach; least privilege is not optional.
- Cold-start latency. Serverless idles to zero, so the first request after quiet pays the spin-up; latency-sensitive paths need warm capacity.
- Silent lock-in. Proprietary managed surfaces (Aurora, Spanner, DynamoDB) make migration a project; you discover the portability cost only when you try to leave.
Decision checklist
- Is the workload write-heavy ingestion, wide analytical scans, or transactional point reads? → object store + table format, cloud warehouse, or managed OLTP DB respectively.
- Are storage and compute on independent scaling axes here, and have you co-located compute with its data to avoid egress?
- What is the durability/consistency contract you actually need, and does the chosen service meet it (single-object read-after-write vs multi-object atomicity)?
- What did you delegate (backups, patching, failover) and what did you give up (control, portability, cost)? Is that trade right for this system's scale?
- How large a failure must this survive — one AZ (cheap, same-region) or a whole region (egress + latency cost)? Place replicas accordingly.
- Does any critical request path depend on the control plane? If so, remove that dependency.
- Is IAM least-privilege, with no public buckets and scoped roles per service?
- Have you estimated egress for every cross-region / cross-cloud data flow before committing to it?
Checkpoint exercise
Where this points next
This lesson handed durability, replication, and even failover to a provider — and the moment you do that, where your data physically lives and who is allowed to touch it stop being purely technical choices. A region is also a legal jurisdiction; an object store's deletion semantics are also a compliance obligation; an IAM policy is also an audit boundary. Lesson 25 takes that thread up directly: law, regulation, and ethics as an architecture input — data residency, the right to be deleted, retention requirements, and consent — treating these not as paperwork bolted on at the end but as constraints that shape the system the same way latency budgets and egress costs do.
Interview prompts
- What actually changes when "the disk" becomes an object store? (§1 — storage/compute separation, delegated eleven-nines durability, single-object read-after-write consistency, and a high-latency/high-bandwidth/immutable/whole-object profile; no in-place update and no single owner.)
- How does the object-store latency profile change storage-engine design? (§1 — ~50 ms per op vs ~0.1 ms on SSD forces few large sequential reads and append-only immutable writes; this is exactly the LSM-tree/Parquet design from lesson 04, with immutability now a hard requirement and the table's current state tracked by an external commit manifest, e.g. Iceberg/Delta.)
- S3 consistency — what guarantee, and what is it not? (§1 — strong read-after-write on a single object since 2020 (a GET after a successful PUT sees the write); it is NOT multi-object atomicity, so cross-object table updates need a table-format commit protocol.)
- How does a table format (Iceberg/Delta/Hudi) give a table atomic, consistent updates when the object store only guarantees single-object atomicity? (§1 — data files are immutable Parquet objects; a manifest lists the files of one snapshot (a table version); committing means PUT the new files and manifest (invisible), then atomically swap a single current-pointer (CAS / ordered log entry / catalog row) — one atomic op flips the whole table, so readers see only the old or new snapshot. This is the WAL pointer-swap and MVCC snapshot from lessons 03/13, and it yields snapshot isolation, time travel, and metadata-only schema evolution; the cost is the small-file problem, paid down by compaction and snapshot expiry.)
- Why do cloud warehouses scale compute and storage independently, and why does that matter? (§2 — tables are columnar objects in the store; you rent ephemeral query clusters that read them, so 500 TB can be queried by a tiny cluster overnight and a huge one at 9am, and two teams query the same data without CPU contention.)
- Managed database — what do you gain and give up? (§3 — gain: delegated backups/PITR, patching, failover, scaling; give up: control, portability (lock-in), cost premium, transparency. The right call is per-system and scale-dependent.)
- Why is egress an architecture constraint, with a number? (§4, §6 — out-of-region/out-of-cloud data is billed per GB (~0.02 $/GB); copying 40 TB cross-region is ~800 $ once plus ~1,200 $/yr to refresh, on top of storing the copy, and adds tens of ms latency — so you co-locate compute with data and budget any cross-region copy deliberately.)
- Control plane vs data plane — why care? (§5 — the data plane (serving GETs/reads/queries) can stay up while the control plane (provisioning, console, scaling) is down; so keep control-plane calls off the critical request path and don't let recovery depend on the layer that's failing — and drill it, lesson 28.)
- What does serverless buy and cost? (§5 — scale-to-zero (pay nothing idle, scale instantly) at the cost of cold-start latency on the first request and forced statelessness (all state in the external data plane).)