Skip to content
SeeItFlow
System Design · Trade-offs & Decisions · ~20 min read

Trade-offs & Decisions

The key architectural decisions in an e-commerce platform, and the trade-offs that make each one non-obvious -- from search infrastructure and cache boundaries through checkout timing, reservation-vs-intent, consistency scoping, reserve/pay ordering, order/payment sequencing, saga coordination style, expiry mechanisms, inventory concurrency, event delivery, partitioning, flash-sale admission and monolith-vs-services.

Mangesh MandlikLast updated:
Decision 01

Catalog Database vs. Search Index

Should product search run against the catalog database or a dedicated index? Both are viable at different scales.

Catalog databaseDedicated search index
CapabilityBasic full-text matching is possible and adequate at small catalog scaleRelevance ranking, faceted filtering, typo tolerance, autocomplete
ScalingSearch load competes with transactional write load on the same storeScales independently from the transactional catalog store
ConsistencyAlways as fresh as the catalog itselfEventually consistent -- an indexing pipeline introduces lag
Operational costNone beyond the existing databaseA second system to run, monitor and keep in sync
Decision 02

Cache vs. Authoritative Read

When is a cached value trustworthy, and when does the system need to read the authoritative source?

Safe to cacheNeeds an authoritative read
Product name, description, images, categoryInventory availability used to grant a reservation
Search results (discovery, not a contract)The checkout price actually charged
A read-only order-status display viewAn order-state transition being written

The distinction is not 'reads can be cached, writes can't' -- it's about which decisions the read feeds. A read-only order-status view can be cached with explicit staleness semantics; a correctness-changing decision (commit a reservation, confirm an order) needs sufficiently fresh, authoritative state regardless of whether the surrounding display layer is cached.

Decision 03

Synchronous vs. Asynchronous Checkout

It is not accurate that inventory reservation, payment, and order creation must all complete synchronously before responding to the customer -- some payment methods are inherently asynchronous.

A valid checkout response can legitimately be PENDING rather than CONFIRMED, when the chosen payment method resolves asynchronously (a bank redirect, a challenge flow). The real rule does not depend on synchronous vs. asynchronous plumbing at all:

What is a genuine design choice: downstream business effects (fulfillment kickoff, notification, analytics) should generally be event-driven off durable confirmed state, not tied to whether the original checkout call happened to be synchronous.

Decision 04

Cart vs. Inventory Reservation

Should adding an item to cart reserve inventory against it?

Reserve on add-to-cartReserve at checkout (baseline)
BehaviorPrevents another shopper from buying the item while it sits in someone's cartCart is intent only; reservation happens when the shopper commits to purchase
DownsideAbandoned carts can block inventory indefinitely; popular items can appear falsely sold outA shopper can reach checkout and find the item just sold out -- needs a clear, informative error
When it's usedExtremely scarce, time-sensitive inventory (limited drops, ticketing) usually paired with a waiting room/queueThe default for ordinary retail with normal-velocity inventory
Decision 05

Consistency by Workload

Different parts of the platform warrant different consistency treatment -- but the useful statement is a list of concrete invariants, not a label like 'strong consistency.'

Eventual consistency is acceptableA concrete guarded invariant is required
Catalog display, search results, recommendation updates, analytics aggregates, order history displayNo duplicate checkout/order for the same idempotency identity
No inventory allocation beyond the policy the SKU actually permits
Order state transitions only follow the guarded transition table
A payment identity is never reused across two independent charges
Decision 06

Reserve-Before-Pay vs. Pay-Before-Reserve

Should inventory be reserved before or after the payment attempt?

Reserve, then payPay, then reserve
BenefitNever charges a customer for an item that turns out to be unavailableAvoids holding stock during a payment attempt that might fail or take a while
RiskHolds stock during the payment window, even if payment ultimately failsA successful charge can be followed by unavailable inventory, forcing a refund
Decision 07

Order-Before-Payment vs. Order-After-Payment

Should a durable pending order exist before the payment attempt, or only after payment succeeds?

Create pending order firstCreate order only after payment succeeds
RecoverabilityA durable workflow identity exists from the start -- crash recovery has something to resumeA payment that succeeds with no matching order is a harder gap to recover from
BookkeepingAbandoned/failed checkouts leave PENDING or CANCELLED order records behindFewer abandoned order records, at the cost of a harder recovery path
Decision 08

Orchestration vs. Choreography

Should the checkout saga be coordinated by one explicit owner, or emerge from services reacting to each other's events?

OrchestrationChoreography
CoordinationOne workflow owner explicitly drives each stepServices react to events published by other services
VisibilityEasier to see and query the current state of any given checkoutState is implicit, spread across independent consumers
CouplingMore central coupling to the orchestratorLooser coupling between individual services
Failure reasoningEasier as the workflow grows -- one place to lookHarder to reason about end-to-end as more steps/services are added
Decision 09

Reservation Expiry Mechanism

Several mechanisms can implement durable, recoverable reservation expiry -- they differ in operational cost and precision.

MechanismTrade-off
Scheduled DB scanSimple to build; scan cost grows with reservation volume unless well indexed
Delayed queue messagePrecise timing; adds a dependency on a delay-capable queue/scheduler
Expiry indexMakes the scan option scale; still a polling-based mechanism, not instantaneous
Workflow engine timerPrecise and integrated with the saga's own state; only available if a workflow engine is already in use

See Architecture Deep Dive -> Reservation Expiry for the full mechanics of each option.

Decision 10

Inventory Concurrency Strategy

Conditional atomic update, optimistic versioning, row locks, and serialized per-SKU processing all implement the same invariant -- the choice is about contention level, not correctness.

Swipe to pan
Inventory Concurrency StrategiesSame invariant (no oversell), different contention handling at the storage layer.Conditional updateUPDATE ... WHERE qty>0default -- ordinary contentionOptimistic versioningUPDATE ... WHERE version=vmoderate contention, cheap retryRow lockSELECT ... FOR UPDATEmulti-step txn needs one lockSerialized per-SKUsingle-writer queueflash-sale hot-SKU contentionAll four enforce: reserved units never exceed available units.Moving right increases safety under extreme contention but trades away concurrency/throughput --serialized per-SKU processing is the Flash-Sale Hot-SKU Contention response, not the default path.
Four storage-layer implementations of the same no-oversell invariant: conditional update is the default, optimistic versioning suits cheap retries, row locks cover multi-step transactions, and serialized per-SKU processing is reserved for flash-sale hot-SKU contention.
StrategyBest when
Conditional atomic updateOrdinary contention -- the default for most SKUs
Optimistic versioningModerate contention, retries are cheap
Row lockMultiple related checks/writes need to happen inside one transaction
Serialized per-SKU processingExtreme contention on a single hot SKU -- see Flash-Sale Admission Control below

See Architecture Deep Dive -> Inventory Concurrency Control for the mechanics of each.

Decision 11

Direct Publish vs. Transactional Outbox

Publishing order.confirmed directly after the DB commit is simpler to build, but leaves a real dual-write gap that the outbox pattern exists to close.

Direct publishTransactional outbox
Implementation costLower -- one call to the broker after commitHigher -- an outbox table, a relay process, relay-lag monitoring
Failure windowA crash between DB commit and the publish call silently drops the eventThe event's existence is guaranteed once the DB transaction commits
Consumer requirement either wayConsumers must still be idempotentConsumers must still be idempotent -- at-least-once, not exactly-once
Decision 12

Inventory Partitioning

Should inventory be partitioned by SKU, by location, or by a broader database-wide key shared with other domains?

SKU/location partitioningShared platform-wide key (e.g. customer_id)
Contention isolationIsolates contention to the specific SKU under loadA hot customer or shard key can bundle unrelated SKUs together
Query fitMatches how reservation and stock queries are actually madeConvenient for cross-domain joins, poor fit for inventory's own access pattern

SKU/location partitioning is the natural fit for inventory's access pattern. See Architecture Deep Dive -> Partitioning for how this compares across catalog, cart, inventory and orders, and Deep Dive -> Flash-Sale Hot-SKU Contention for why partitioning alone doesn't solve one indivisible hot SKU.

Decision 13

Flash-Sale Admission Control

Should every user be allowed to contend for a scarce SKU simultaneously, or should admission be smoothed?

Let all users contendWaiting room / token admission
SimplicityNo additional componentAdds a queuing/admission layer
ContentionEvery request piles onto the same hot reservation path at onceAdmission is smoothed; the hot path sees bounded concurrency
FairnessFirst-to-arrive-at-the-database wins, not necessarily first-to-clickCan implement an explicit, product-defined fairness policy
Decision 14

Price at Cart vs. Checkout

Should the cart's stored display price be trusted, or should checkout always resolve a fresh quote?

Trust the cart's stored priceResolve a fresh checkout quote
RiskCharges a stale price the customer no longer owes or is no longer offeredNone -- the customer is always quoted current commercial terms
UX costNone until the stale price causes a disputeMay need to surface a price-changed notice if the quote differs from what was displayed
Decision 15

Modular Monolith vs. Independently Deployed Services

The domain boundaries this module teaches -- catalog, pricing, cart, inventory, checkout, order, payment, fulfillment -- matter even before a single service is independently deployed.

A modular monolith that keeps these boundaries as clean internal modules (separate schemas/tables, separate transaction scopes, explicit interfaces between them) can satisfy every invariant in this module without operating a dozen independently deployed services. Splitting into separate services is justified by independent scaling needs, independent deployment cadence, or team ownership boundaries -- not assumed as a requirement of correctness.