Cloud Architecture Design Principles and Trade-offs Questions
The cross-pillar reasoning skill for architecting cloud systems: weighing reliability, scalability, security, performance, and cost against each other to justify ONE architectural choice over another under real constraints (budget, team size, timeline, existing systems). Covers well-architected-style design reviews, resilience and failure-mode reasoning (blast radius, graceful degradation, idempotency), consistency-versus-availability trade-offs (CAP/PACELC), and scenario-based decisions such as choosing a managed versus self-hosted component or an architectural style (monolithic, microservices, or serverless) for one system. Provider-agnostic: no specific cloud vendor's service catalog. This topic is the JUSTIFICATION layer, not a subsystem deep dive: a full design of observability, disaster recovery, identity and access management, networking, caching, or Kubernetes orchestration belongs to that subsystem's own topic. Comparing compute abstractions (VM versus container versus serverless versus GPU/TPU) belongs to compute options and trade-offs. Choosing an architectural style is covered here, but the internal implementation patterns of that style (service mesh, sagas, two-phase commit, event sourcing) belong to microservices architecture and service design. Multi-year roadmaps, vendor evaluation, and governance belong to infrastructure strategy and technology selection. Spanning multiple cloud providers or bridging on-premises and cloud belongs to multi-cloud and hybrid cloud architecture. The IaaS/PaaS/SaaS delivery-model taxonomy belongs to cloud service and deployment models. Region-crossing replication and failover design belongs to multi-region and geo-distributed systems.
A payments service must keep operating correctly through a partial regional network partition, and it must never double-charge a customer or lose a transaction. Design the resilience patterns you would put across the request path (client retries, the write path, and cross-service coordination) to guarantee correctness under partition and retries, and explain how you would audit for and reconcile any anomalies afterward.
Sample Answer
Direct answer
Preventing a double-charge or lost transaction under a partial regional network partition comes down to three things working together: the client must retry safely, an idempotency key attached to every payment attempt, the write path must make the idempotency key the actual source of truth, reject or return the original result for a duplicate key, never process it twice, and cross-service coordination must use a pattern, a saga with compensating actions, or a durable outbox, that survives a partition without leaving money in a half-applied state. On top of that, an independent reconciliation process has to audit for anomalies afterward, because no design is provably perfect against every partition timing.
Structured elaboration
Client side: idempotency keys
Every payment request carries a client-generated idempotency key, typically a version-4 universally unique identifier, generated once per user action, not once per network attempt. If the client's request times out and it retries, the retry carries the same key. The client never generates a new key for a retry of the same logical action; that single discipline is what makes retries safe instead of dangerous.
Write path: the idempotency key as source of truth
The payment service persists the key and its result as an atomic part of the same transaction that processes the payment, not as a separate step that could itself fail out of sync. On receiving a request, it first checks whether this key has been seen before. If yes, and the prior attempt completed, it returns the original result, not a new charge. If the prior attempt is still in progress, it returns a "still processing, retry shortly" response rather than starting a second concurrent attempt. If the key is new, it processes it and records the result atomically with the charge itself. This makes "processed twice" structurally impossible for any request carrying the same key, regardless of how many times the client retries.
Cross-service coordination under partition
A payment usually spans at least two operations, debit the payer, credit the payee, maybe notify a ledger service, which cannot be a single database transaction across services. Use a saga: each step is a local transaction, and each step has a compensating action defined up front, if the credit fails after the debit succeeds, run an automatic compensating credit back to the payer. Pair this with a durable outbox pattern: the debit step and the "please credit the payee" event are written atomically in the same local transaction, so a partition or crash between "debit succeeded" and "credit event published" cannot happen, either both happened or neither did, and a background process reliably delivers the outbox event once connectivity is restored, retrying safely because the credit step is also idempotent by its own key.
Handling the partition specifically
If the network partition isolates the credit step's target service, the debit's local transaction already committed and the outbox event is durably stored; it simply cannot be delivered yet. This is safe: money is not lost, the debit is real and recorded, and no double-charge occurs, since the outbox event, when eventually delivered, carries the same idempotency key, so even if it is redelivered multiple times after the partition heals, the credit step processes it exactly once. The customer sees "payment processing" rather than a false success or a false failure, both worse than an honest "still working on it."
Auditing and reconciliation after the fact
Run a periodic reconciliation job that compares the payment ledger against actual money-movement records, bank or card-network settlement data, and flags any mismatch: a debit with no matching credit past a time threshold, a credit with no matching debit, or two records sharing what should have been a unique idempotency key. Every flagged anomaly should be investigated and, once understood, either resolved automatically, replaying a stuck outbox event, or escalated to a human for manual correction, with the resolution logged for audit purposes. This step exists precisely because "we designed it to be correct" is not the same claim as "we verified it stayed correct," and partitions are exactly the condition most likely to expose a subtle bug in the design.
sequenceDiagram
participant C as Client
participant P as Payment service
participant O as Outbox / event log
participant L as Ledger service (payee credit)
C->>P: Charge request (idempotency key K)
P->>P: Check key K, not seen before
P->>P: Debit payer and write outbox event (K) atomically
P-->>C: "Processing" (or success once confirmed)
Note over P,L: Network partition isolates L
O--xL: Credit event (K) cannot be delivered yet
Note over O,L: Partition heals
O->>L: Redeliver credit event (K)
L->>L: Check key K, apply credit exactly once
L-->>O: Acknowledge
C->>P: Client retries with same key K (thinks it failed)
P->>P: Key K already recorded, return original result
P-->>C: Original result, no second debit
Worked example
A customer's payment times out on their phone during a regional partition and they tap "pay" again. The second tap generates a request with the same idempotency key, because the client library correctly treats it as a retry of the same action, not a new one. The payment service sees the key already recorded from the first attempt, which had already debited the payer and written the outbox event, and returns that original result instead of debiting again. Meanwhile the outbox event to credit the payee, blocked by the partition, is redelivered once connectivity returns and applied exactly once because the ledger service checks the same key. The customer is charged once, the payee is credited once, and the reconciliation job later confirms the debit and credit match, closing the loop with evidence rather than assumption.
Trade-offs and pitfalls
- The single most common bug in real systems is a client that generates a new idempotency key on every retry, say from an auto-generated request ID, instead of reusing the key for the same logical action. That silently defeats the entire idempotency mechanism and reintroduces the double-charge risk it was built to prevent.
- Storing the idempotency-key check in a different transaction than the charge itself, a common shortcut, reintroduces a race: two near-simultaneous requests with the same key can both pass the "not seen before" check before either has recorded the key, causing a double charge. The check and the charge must be atomic together.
- Skipping the reconciliation step because "the design is provably correct" is a mistake; partitions have edge-case timings, a crash at the exact moment between two writes, a bug in the compensating-action logic, that a design review will not catch but a reconciliation audit will.
Explain eventual consistency, read-your-writes consistency, monotonic reads, and causal consistency. For each, give a concrete requirement where it would be the right guarantee to offer, and describe how you would support it in a cloud service through your choice of caching and replication strategy.
Sample Answer
Direct answer
These four guarantees are points on a spectrum of how fresh and ordered the data a reader sees has to be. Eventual consistency only promises convergence once writes stop; read-your-writes guarantees a client sees its own writes; monotonic reads guarantees a client's view never goes backward in time; causal consistency guarantees that if one write causally depends on another (it read it, or the same actor made both), everyone sees them in that order, though unrelated concurrent writes can appear in any order. Pick the weakest guarantee that still satisfies the real requirement, because every guarantee above eventual consistency costs latency, availability, or both.
Structured elaboration
The four guarantees
| Guarantee | What it promises | What it does not promise |
|---|---|---|
| Eventual consistency | If writes stop, all replicas converge to the same value eventually | No bound on staleness while writes continue, no ordering guarantee between reads |
| Read-your-writes | A client always sees writes it made itself, on its next read | Nothing about seeing other clients' writes promptly |
| Monotonic reads | A client's successive reads never go backward, never see an older value after a newer one | Nothing about seeing the very latest write quickly |
| Causal consistency | Writes that are causally related (B read A, or the same client wrote both) are seen by everyone in that order | Concurrent, unrelated writes can be seen in different orders by different clients |
Where each is right, and how to implement it
- Eventual consistency fits a "like count" or view counter on a social post: nobody notices if it is off by a few seconds. Implement with asynchronous replication and a cache with a short time-to-live (TTL), reads hitting the nearest replica or cache with no coordination.
- Read-your-writes fits a shopping cart: a customer adds an item and must see it on the very next page load, but does not need to see someone else's cart changes instantly. Implement by routing a client's reads to the same replica or region that handled their last write (sticky routing, or a read-after-write token the client passes back), or by writing through a cache synchronously for that client's session.
- Monotonic reads fits an account balance shown across several pages in one session: a user should never see a balance regress from $100 to $80 back to $100 as they navigate, even before any new transaction happens, because replica lag could otherwise expose an older value after a newer one. Implement with a session-bound read version (a logical timestamp or vector clock, a small per-write counter set that lets a node detect which operations causally precede which), routing reads only to a replica that has caught up to at least that version.
- Causal consistency fits a comment thread or a bank transfer's downstream notifications: if a bank debits account A and credits account B, and a fraud check reads B's new balance to decide whether to hold funds, that check must see the credit; two unrelated customers' unrelated transfers can be seen in any order relative to each other. Implement by propagating a causal token (a vector clock or dependency list) with each write and having replicas withhold delivery of a write until its dependencies have already been applied locally.
Applied to a machine learning feature store and model metadata
A feature store serving a model at inference time typically only needs eventual consistency for feature values themselves: a slightly stale feature rarely changes a prediction meaningfully, and the alternative, blocking inference on cross-region replication, would blow the latency budget. Model metadata, such as which model version is currently active or what its rollback target is, needs a stronger guarantee than the features: a rollback decision made by one operator must be immediately visible to every serving node, which is read-your-writes at minimum and often causal consistency if the rollback event has dependencies, such as rolling back the model and the feature schema together. This is a case where two data classes inside the same system legitimately sit at different points on the spectrum.
Worked example
Account A has $500. A transfer service debits A by $200 and credits B by $200. If a support dashboard reads B's balance right after the transfer and the read lands on a stale replica, it might show B's pre-credit balance: an eventual-consistency gap that would be embarrassing but not incorrect, because the transfer log stays the source of truth. If instead a downstream automated fraud check reads B's balance to decide whether $200 looks anomalous for that account, and it must evaluate the post-credit state to be correct, causal consistency is required: the fraud check's read is causally dependent on the credit write, so the system must guarantee it sees that write, by routing the check to a replica the write has already reached, verified via a causal token, or by having the check read from the same node that processed the write.
Trade-offs and pitfalls
- Reaching for the strongest guarantee "to be safe" is the most common mistake: causal or read-your-writes consistency usually requires sticky routing or coordination that adds latency and reduces how freely the system can load-balance or fail over.
- Monotonic reads and read-your-writes are easy to conflate: read-your-writes is about your own writes, monotonic reads is about time never going backward regardless of who wrote it. A system can offer one without the other.
- A subtle pitfall is implementing causal consistency with a coarse dependency tracker, such as "same user ID" as a proxy for causality: it over-serializes unrelated writes from the same user and under-serializes genuinely causal writes across users, like the transfer-then-fraud-check example above, so dependency tracking has to be based on actual read-then-write chains, not identity.
Explain the CAP theorem in the context of cloud services. Provide one practical example of a managed cloud service that favors availability over consistency and one example of a service that favors consistency over availability. Discuss the operational consequences of each design choice.
Sample Answer
Direct answer
The CAP theorem says a distributed data system can only guarantee two of three properties during a network partition: consistency, every read sees the most recent write; availability, every request gets a response even if it might not be the latest data; and partition tolerance, the system keeps working despite a network split between nodes. Because partition tolerance is not really optional for any real distributed system spanning more than one machine or data center, CAP in practice is a choice between consistency and availability specifically during a partition; everyday, non-partition behavior is a separate trade-off that CAP does not actually describe.
Structured elaboration
The three properties, precisely
- Consistency: every node that gets a read returns the most recent successfully written value, or an error, never a stale value silently.
- Availability: every request that reaches a working node gets a non-error response, even if that response might be based on stale data.
- Partition tolerance: the system continues operating even when network messages between some nodes are lost or delayed.
Since a real distributed system can and eventually will experience network partitions, partition tolerance is treated as a given, and the actual design decision is what the system does during a partition: does it refuse to answer some requests to guarantee correctness, choosing consistency (CP), or does it keep answering with whatever data it has locally, accepting that answer might be stale, choosing availability (AP)?
One example favoring availability over consistency
A geo-distributed shopping cart or session store, the same shape as Amazon DynamoDB: DynamoDB's reads are eventually consistent by default (a strongly consistent read is available only as an explicit, pricier option, and is not offered at all on global secondary indexes or streams), and its multi-Region global tables replicate asynchronously by default, so each region keeps accepting reads and writes locally even if that region is cut off from the others during a partition. This favors availability: a customer can keep adding items to their cart even if their region cannot currently talk to other regions, at the cost that if they somehow interact with two partitioned regions during the outage, their view of the cart could briefly diverge until the partition heals and the system reconciles.
Operational consequence: the system needs a defined conflict-resolution strategy for when the partition heals, which value wins if both sides accepted writes, and application logic has to tolerate showing a customer a view that might be a few seconds or minutes stale during a partition, usually an acceptable trade for a shopping cart.
One example favoring consistency over availability
A configuration or coordination service used by other infrastructure to elect a leader or store critical cluster configuration, such as etcd, the Raft-based key-value store that backs the control plane of most managed Kubernetes offerings, or Google Cloud Spanner, which by default provides external consistency, its strictest guarantee, using a Paxos-based quorum for every read and write. This favors consistency: during a partition, the minority side of the split deliberately refuses to serve writes, and often refuses certain reads too, rather than risk a split-brain cluster, two nodes both believing they are the authoritative leader, which is a far worse outcome than a temporary unavailability.
Operational consequence: during a partition, some clients on the minority side will see errors or timeouts instead of a stale-but-working response, and operators need monitoring and alerting specifically for whether the system is currently in a minority partition and refusing traffic, since that is a real, if hopefully rare, production event that needs a runbook, not a surprise.
Worked example
A three-node cluster running a CP-style coordination service such as etcd splits so that one node is isolated from the other two. The two-node majority side continues accepting writes normally, since it can still form a quorum; the isolated single node refuses writes, and depending on configuration refuses reads too, to avoid serving stale data with false confidence, until the partition heals and it can rejoin and catch up. Any client that happens to be talking only to the isolated node experiences an outage during this window, that is availability being deliberately sacrificed to guarantee that no two nodes can independently believe they are both the current leader.
Trade-offs and pitfalls
- A very common mistake is treating CAP as describing everyday behavior: CAP only says something about what happens during a partition. A system's normal-operation latency-versus-consistency trade-off, the far more common case since a full network partition is relatively rare, is a different trade-off, often better described by PACELC (if there is a Partition, choose between Availability and Consistency, as CAP already describes; Else, during normal operation, choose between Latency and Consistency), which extends CAP to cover the non-partition case explicitly.
- Assuming a chosen availability-favoring design means consistency does not need to be thought about at all is a mistake; availability-favoring systems still need a defined conflict-resolution or reconciliation strategy for when the partition heals, skipping that design step leaves the system with an undefined, effectively random, outcome for conflicting writes.
- Treating partition tolerance as something you can simply opt out of is a misunderstanding: any system with more than one node connected by an unreliable network will eventually face a partition, so choosing consistency and availability while skipping partition tolerance is not a real long-term option for a genuinely distributed system, it just means the system has not been tested against a partition yet.
Explain the difference between strong consistency, causal consistency, and eventual consistency. A game leaderboard needs to serve reads with sub-millisecond latency: which of these three consistency models would you choose for it, and why? Walk through the user-facing trade-offs of that choice.
Sample Answer
Strong consistency guarantees that every read reflects the latest completed write, as if there were only ever a single copy of the data. Causal consistency guarantees only that operations one client's actions causally depend on are seen in that order, while operations with no causal relationship to each other can appear in a different order to different readers. Eventual consistency guarantees only that, once writes stop arriving, every replica eventually converges on the same value, with no bound on how stale any individual read can be until then.
For a leaderboard that has to serve reads in under a millisecond, eventual consistency is the only one of the three that can meet the requirement. Strong and causal consistency both need the reader's request to check or coordinate with something beyond its own local copy of the data, and that coordination alone costs more time than a sub-millisecond budget allows, before any application logic runs at all. The design that follows is to keep the write path (the score submission itself) strongly consistent against one source of truth, so a score is never lost or double-counted, while serving every leaderboard read from a separate, eventually consistent, purely local cache.
Strong, causal, and eventual consistency compared
| Model | What every reader is guaranteed | What it costs to provide | Where it fits |
|---|---|---|---|
| Strong (linearizable) | Every read sees the single most recent committed write; all clients agree on one global order of operations | A quorum (a majority, more than half of replicas) must coordinate on the write, usually via a consensus protocol (an algorithm such as Raft or Paxos that gets multiple replicas to agree on one ordered log), and a read from a follower must confirm it is not stale | Bank balances, inventory counts, anything where a stale or out-of-order read is a correctness bug, not just an inconvenience |
| Causal | A reader who is causally connected to a write (made it, or read something derived from it) always sees it, and in order; unrelated concurrent writes can appear in different orders to different readers | Needs a way to track "happened before," such as vector clocks (a small per-write counter set that lets a node detect which operations causally precede which) or a session token, but not global agreement | Comment threads or activity feeds, where a reply must appear after the comment it replies to, but the exact order of two unrelated users' actions does not matter |
| Eventual | Every replica converges to the same value once writes stop, with no guaranteed order or freshness bound while writes are still happening | The cheapest of the three: a read is served from whichever local copy is nearest, with zero cross-node coordination at read time | Anything where a bounded staleness window is acceptable and raw read speed matters more than a strict ordering guarantee, which is exactly the leaderboard case here |
Why a sub-millisecond budget decides this before anything else does
It is worth putting an actual number on what "coordination costs time" means. Light in optical fiber travels at roughly two-thirds its vacuum speed, because of the fiber's refractive index (about 1.5):
vfiber=nc≈1.53×108 m/s=2×108 m/sA round trip has to cover the distance out and back, so for a full latency budget of one millisecond (0.001 s), the farthest a single round trip can physically reach is:
d=2vfiber×tbudget=22×108 m/s×0.001 s=1×105 m=100 kmThat is the physical floor, before adding any serialization, queueing, or protocol overhead, and before accounting for the fact that both strong and causal consistency typically need more than one such round trip (a consensus protocol commits a write only once a quorum acknowledges it, and a linearizable read from a follower has to separately confirm it is not stale). Availability zones, the isolated data-center groups a cloud region is built from, are frequently already tens of kilometers apart, and a cross-region hop is hundreds to thousands of kilometers. Any read that must coordinate beyond its own local node is competing for a budget that a single physical hop can already exhaust on its own. A read that never leaves a local, in-memory cache pays none of this cost: its latency is bounded only by local compute and local network, which is routinely and comfortably under a millisecond.
The leaderboard architecture: split the write path from the read path
flowchart LR
subgraph WRITE["Write path (authoritative, strongly consistent)"]
P[Player submits score] --> W[Game service]
W --> DB[(Primary score store: atomic max-update)]
end
subgraph READ["Read path (eventual, sub-millisecond)"]
C[(In-memory sorted leaderboard cache)]
R[Leaderboard read request] --> C
C --> UI[Rendered rank list]
end
DB -. async fan-out on bounded refresh interval .-> C
The write path is where correctness lives, so it stays strongly consistent: a score submission is an atomic "set my score to X only if X beats what is stored" update against a single primary store, which stops two near-simultaneous submissions (including a cheating client racing its own requests) from corrupting the record. The read path is where the latency requirement lives, so it is eventually consistent: an asynchronous process fans the primary store's changes out to a precomputed, sorted, in-memory structure on a bounded refresh interval, and every leaderboard read is served from that local structure with no coordination at read time. This is the same idea as CQRS (Command Query Responsibility Segregation: separating the path that accepts writes from the path that serves reads so each can be optimized on its own terms), applied specifically along the consistency dimension.
User-facing trade-offs of choosing eventual consistency for reads
- A player who just posted a new high score may not see their own updated rank until the next refresh cycle runs, because their read is served from the same eventually consistent cache as everyone else's. This is the specific cost of applying eventual consistency uniformly: it breaks the one guarantee a user notices immediately, seeing the result of their own action. It is usually patched at the interface layer rather than the consistency layer: show the player's own new score optimistically the instant the write is acknowledged, while the shared leaderboard view around them stays eventually consistent.
- Two players can briefly see different top-ten lists, or different ranks for the same third player, depending on which cache replica or edge location served each of them. For a leaderboard this is usually an acceptable cost, since a rank is inherently a snapshot in time, but it has to be a deliberate call. If the actual requirement were "every player sees an identical ranking at every instant," that pushes back toward strong consistency and a completely different latency conversation.
- Anti-cheat or audit tooling must never read from the eventually consistent cache to decide something consequential, such as banning a player or voiding a score, because that data is stale or momentarily wrong by design. It should read from the strongly consistent primary store, which is exactly why the two paths were split apart in the first place instead of trying to make one path serve both needs.
- The refresh interval feeding the cache needs an explicit staleness bound, a service-level objective (SLO) such as "the cache is never more than a few hundred milliseconds behind the primary store," plus monitoring against that bound, rather than just running "as fast as the background job happens to go." An eventually consistent system with no stated limit on how eventual is not a design decision, it is an outage waiting to be noticed.
- The most common wrong turn here is reaching for strong consistency "to be safe" on the read path, which is precisely where the stated requirement (latency) lives, while the operation that genuinely needs a strong guarantee, the write, gains nothing extra from that choice. Matching the consistency model to the specific operation, instead of applying one model uniformly across the whole feature, is the actual skill this question is testing.
For a global e-commerce platform, choose appropriate data stores for these components: (a) transactional orders, (b) product catalog, (c) user sessions, and (d) product images. For each choice, justify your pick based on consistency needs, query patterns, expected scale, latency, and cost.
Sample Answer
Direct answer
Each of these four components has a distinct consistency, query, scale, latency, and cost profile, so a single "one database for everything" choice is wrong for at least two of them: transactional orders need strong consistency and transactional guarantees, the product catalog needs to be read-heavy and flexible for search and browse, user sessions need to be fast and disposable, and product images need to be stored as large blobs, not database rows.
Structured elaboration
| Component | Recommended store type | Why |
|---|---|---|
| (a) Transactional orders | A relational database with strong (ACID: atomicity, consistency, isolation, durability) transactional guarantees | Orders involve money and inventory decrement together; a lost or double-applied order write is a real business incident, so the strong consistency and multi-row transaction support of a relational engine is worth its lower write-throughput ceiling |
| (b) Product catalog | A document or search-optimized store, a document database, or a dedicated search index alongside a simpler backing store | Catalog reads vastly outnumber writes, query patterns are flexible, filter by category, attribute, free text, and schema varies by product type; eventual consistency, a new product taking a few seconds to become searchable, is a non-issue |
| (c) User sessions | An in-memory key-value store with a time-to-live (TTL) | Sessions are read and written on nearly every request, so raw speed matters most, and they are inherently disposable, a lost session just forces a re-login, which makes an in-memory store's weaker durability guarantee an acceptable trade for its latency |
| (d) Product images | Object storage, referenced by a URL or key from the catalog record | Images are large, immutable-once-uploaded blobs; storing them as database rows wastes an expensive, latency-optimized engine on cheap, bulk-optimized data, and object storage pairs naturally with a content delivery network (CDN) for fast delivery |
Justification detail per component
- Orders: consistency need is high, a double-applied order or a lost inventory decrement is a real financial and operational problem; query pattern is transactional, read-then-write within one logical operation; scale is moderate relative to catalog reads; latency tolerance is moderate, users expect an order confirmation in seconds, not milliseconds; and cost is acceptable to spend on the pricier, higher-guarantee engine precisely because order volume is the smallest of the four, so the more expensive per-operation cost is applied to the lowest-volume, highest-stakes workload.
- Catalog: consistency need is low, eventual consistency on a new listing appearing in search is invisible to users; query pattern is read-heavy and highly variable, filters, free text, sorting; scale is the largest of the four in read volume; latency needs to be low for a good browsing experience; and cost per read on a search-optimized store is low, which matters most here because this is the highest-volume read path of the four, running it through a pricier transactional engine would be the expensive mistake.
- Sessions: consistency need is minimal, losing a session is an inconvenience, not a correctness bug; query pattern is a simple key lookup; scale is very high in request volume but small in data size per session; latency needs to be the lowest of the four; and cost per gigabyte for in-memory storage is higher than disk-based storage, but session data is tiny per user and time-to-live-bounded, so total cost stays small despite the pricier storage tier.
- Images: consistency need is essentially none, an image is written once and read many times; query pattern is a simple key or URL fetch; scale is large in total bytes but simple in access pattern; latency is best served by caching close to the user; and cost per gigabyte for object storage is the cheapest of the four storage tiers, which matters because images are by far the largest total byte volume of the four components.
Worked example
A customer places an order for a product. The order write, component a, goes through a transaction that decrements inventory and creates the order record atomically, using the relational store's transactional guarantee to ensure both happen together or not at all. The product page they ordered from was served from the catalog store, component b, which returned in a few milliseconds from a search-optimized index without touching the transactional database at all, keeping catalog browsing traffic, the highest-volume read path, off the system protecting transactional correctness. Their session, component c, was checked on every single page request via a sub-millisecond in-memory lookup, cheap enough to do on every request without adding meaningful latency. The product images on that page, component d, were served from object storage through a content delivery network edge cache, never touching any database, and a regional content-delivery outage would degrade image loading without touching order processing, catalog search, or session handling at all, demonstrating the isolation benefit of separating these four concerns into four purpose-fit stores.
Trade-offs and pitfalls
- Putting everything in one relational database, a common early-stage shortcut, works fine at small scale but couples catalog read load and session read and write load to the same engine that needs to protect transactional order correctness; a catalog traffic spike from a viral product can then degrade order processing, the worst possible failure to have coupled together.
- Putting session data in a database "for durability" trades away the latency benefit sessions actually need, for a durability guarantee sessions do not actually require, a common overcorrection once teams learn to distrust in-memory stores.
- Storing images as database blobs instead of in object storage bloats the database's storage and backup size and slows every backup and restore operation, for data that gets no benefit from living in a transactional engine.
Unlock Full Question Bank
Get access to all 19 Cloud Architecture Design Principles and Trade-offs interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.