Document 39 min read

Architecture Decision Record — Flipkart Marketplace Platform

Solution Architecture v1.0 · Open source on Microsoft Azure · Enterprise Architecture · 2026-09

Twenty-eight decisions that are the architecture. Everything else on these twenty-five views is convention, and convention does not need defending. Each record states the forcing question, the choice, the credible alternatives with the reason each was rejected, and what the choice costs — because a decision recorded without its price is a decision nobody can revisit. The bias throughout is open-source engines running on Azure managed infrastructure: Azure is used where the managed service is genuinely better value at this scale, and an open-source engine is run on AKS where control over sharding, partitioning, retention or cost outweighs the operational burden.

Technology by capability

What each capability is built from, and the one-line reason. The record that argues each row is linked from it. Three records — ADR-16, ADR-17 and ADR-28 — decide behaviour rather than technology and so have no row here.

Capability Built with Why this one Record
Global edge, WAF, CDN Azure Front Door Premium + DDoS Protection Anycast, TLS termination, WAF and caching in one managed hop; no origin has a public IP. ADR-01
Customer API entry Envoy Gateway on AKS Per-request cost at 10M+ RPS peak makes a per-call-priced gateway indefensible. ADR-03
Partner and seller API Azure API Management Subscription keys, quotas, versioning and a developer portal are the product, not the proxy. ADR-03
Compute substrate Azure Kubernetes Service One scheduler for 400 services, stateful engines and GPU inference; node-pool tenancy replaces cluster sprawl. ADR-02
Service-to-service security Istio ambient mesh mTLS and policy without a sidecar per pod, which at this pod count is a material cost line. ADR-04
Transactional core Azure Cosmos DB for PostgreSQL (Citus) Real SQL transactions that shard horizontally on order and seller keys. ADR-05
Catalog and listings Azure Cosmos DB for NoSQL Per-category document shapes, multi-region writes, and a read SLA the catalog path depends on. ADR-06
Inventory contention Redis (Azure Managed Redis) + Citus ledger An atomic Lua compare-and-set survives 100k concurrent buyers on one SKU; the ledger stays the truth. ADR-07
Search and vector retrieval OpenSearch on AKS 150M documents, hybrid BM25 and kNN in one cluster, with index lifecycle we control. ADR-08
Real-time analytics Apache Pinot Sub-second aggregation over upserted streams for seller dashboards and the sale-day war room. ADR-09
Lakehouse Apache Iceberg on ADLS Gen2, Spark, Trino, Airflow Open table format keeps the engine replaceable and the storage bill separable from the compute bill. ADR-10
Media Blob Storage + imgproxy behind the CDN Derivatives generated on demand and cached, so the origin holds one master per asset. ADR-11
Event backbone Apache Kafka on Strimzi Partition counts, compaction, tiered storage and quotas are all things this design must control. ADR-12
Stream processing Apache Flink Event-time windows, exactly-once sinks and real-time joins that Kafka Streams cannot carry alone. ADR-13
Long-running workflows Temporal Refunds, settlements and returns are month-long sagas needing durable state and visible retries. ADR-14
Change data capture Debezium into Kafka One log for the lake and the derived stores, with no dual writes to go out of step. ADR-15
Cart state Server-side, Redis-backed The basket has to survive the UPI handoff leaving the app entirely. ADR-18
Payments isolation Separate Azure subscription and AKS cluster + HashiCorp Vault token vault PCI scope stops at a subscription boundary; nothing else inherits the audit. ADR-19
Seller money Double-entry settlement ledger on Citus A payout dispute must be answerable from immutable entries, not from a mutable balance. ADR-20
Customer identity Keycloak on AKS Per-MAU identity pricing does not survive a 500M-user population. ADR-21
Workforce and seller-staff identity Microsoft Entra ID Conditional access, PIM and device compliance for every privileged human. ADR-21
Authorisation Open Policy Agent, policy as code Field-level masks reviewed as code rather than scattered through service handlers. ADR-22
Secrets and keys Azure Key Vault + Managed HSM, workload identity No secret in a manifest, and payment keys that never leave the module. ADR-19
Model serving KServe + MLflow + Feast on AKS Ranking, fraud and ETA models on the same fleet as the services that call them. ADR-23
Embeddings and moderation Azure OpenAI Elastic capacity for bursty catalog re-embedding without owning GPUs for the peak. ADR-24
Delivery GitHub Actions, Argo CD, Argo Rollouts, Kyverno, Terraform The cluster is reconciled to Git, so the audit trail and the rollback are the same mechanism. ADR-25
Observability OpenTelemetry, Prometheus + Thanos, Loki, Tempo, Grafana One vendor-neutral instrumentation layer, and retention priced as storage rather than as ingestion. ADR-26
Resilience Central India active, South India read-active Two in-country regions, honest RPO, and a failover that is rehearsed rather than assumed. ADR-27

The decisions, and the alternatives that lost

ADR-01 · Terminate every request at Azure Front Door Premium

Area: Edge · Status: Accepted · Shown on views: 01, 02, 08, 19, 23

Context. Traffic is India-heavy but not India-only, arrives from mobile networks with poor last-mile latency, and multiplies roughly tenfold in a sale hour. The edge has to absorb volumetric attack, serve cached product and media bytes, and keep every origin off the public internet.

Decision. All ingress terminates at Front Door Premium with the managed WAF and network-tier DDoS Protection, and reaches AKS over Private Link. Static assets and product media are cached at the edge with short, explicitly set TTLs.

Option Verdict Reasoning
Front Door Premium Chosen Anycast PoPs, WAF, caching and private origin in one product; the private-origin support is what removes the public IP.
Application Gateway per region Rejected Regional, so it needs Traffic Manager in front and gives no edge caching; two products to do one job.
Third-party CDN plus a self-run WAF Rejected Better cache controls, but adds a vendor on the critical path and an egress bill between two clouds.

What it buys

  • One place to shed load, block a botnet or serve a static maintenance page
  • Cache hit at the edge is the cheapest request the platform can serve
  • No workload has an internet-routable address

What it costs

  • Front Door is a single global control plane and therefore a systemic dependency
  • Cache purge is eventually consistent, so a price correction needs a versioned URL rather than a purge
  • WAF false positives on seller uploads need a tuned exclusion list

ADR-02 · One AKS fleet with node-pool tenancy, not many clusters

Area: Platform · Status: Accepted · Shown on views: 07, 08, 19, 20

Context. Roughly 400 services, several stateful engines (Kafka, Flink, OpenSearch, Pinot, Temporal) and GPU inference all need somewhere to run, with wildly different scaling behaviour and blast-radius requirements.

Decision. A single AKS fleet per region, partitioned by node pool and namespace: system, general application, spot-only batch, stateful, and GPU. The one exception is the cardholder data environment, which is a separate cluster in a separate subscription.

Option Verdict Reasoning
One fleet, node-pool tenancy Chosen One upgrade treadmill, one policy set, and bin-packing that a cluster-per-domain estate never achieves.
Cluster per domain Rejected Isolation is real but so is the cost: 20 control planes, 20 upgrade cycles and a mesh that has to span them.
Azure Container Apps Rejected Excellent for the stateless tier and useless for Kafka, Flink and OpenSearch, which would need a second platform anyway.

What it buys

  • Batch and stream work runs on spot capacity while checkout never does
  • One Kubernetes API to write policy, autoscaling and admission control against
  • GPU pools are shared across ranking, embedding and fraud rather than idling per team

What it costs

  • A cluster-wide failure is a regional failure; the control plane is the blast radius
  • Noisy-neighbour risk is managed by quota and priority classes, which needs continuous attention
  • Upgrades have to be choreographed across stateful workloads that dislike being drained

ADR-03 · Two front doors: Envoy for customers, API Management for partners

Area: Edge · Status: Accepted · Shown on views: 02, 08, 09, 24

Context. First-party apps generate the overwhelming majority of calls and need the cheapest possible per-request path. Sellers, brands, ONDC and affiliates need subscription keys, quotas, versioning, a developer portal and, eventually, monetisation — none of which the customer path needs.

Decision. Envoy Gateway on AKS is the customer BFF. Azure API Management fronts every partner and seller API. Both sit behind Front Door and both verify tokens locally.

Option Verdict Reasoning
Envoy for first party, APIM for partners Chosen Each surface pays for what it actually uses; consumption-priced gateways are confined to low-volume, high-governance traffic.
API Management for everything Rejected Per-call pricing at peak volume is the single largest avoidable line in the estate.
Envoy for everything Rejected Would mean building a developer portal, key management and quota billing that APIM already ships.

What it buys

  • Customer path latency and cost are controlled by us end to end
  • Partner governance, versioning and rate cards are configuration rather than code
  • A partner surge cannot exhaust customer gateway capacity

What it costs

  • Two policy surfaces to keep consistent on auth, CORS and rate limiting
  • Two sets of runbooks and dashboards
  • A shared concern such as a new auth scheme has to be implemented twice

ADR-04 · Istio in ambient mode for mesh security

Area: Platform · Status: Accepted · Shown on views: 08, 23

Context. Every service-to-service call must be mutually authenticated and encrypted, and traffic policy — retries, timeouts, circuit breaking, canary weights — should be uniform rather than reimplemented per language.

Decision. Istio ambient mesh across the fleet. mTLS and L4 policy come from the per-node ztunnel; L7 policy is added with a waypoint only for the services that need it.

Option Verdict Reasoning
Istio ambient Chosen No sidecar per pod, so the memory and CPU tax scales with nodes rather than with pods.
Istio sidecars Rejected Mature and well understood, but a sidecar on every pod at this pod count is a five-figure monthly line and a slower rollout.
Linkerd Rejected Simpler and lighter, but the L7 policy and multi-cluster story here is weaker than what the trust boundaries need.
No mesh, mTLS in libraries Rejected Works until the fourth language arrives, then certificate rotation becomes everyone's problem.

What it buys

  • Zero-trust between services without touching application code
  • Canary weights and circuit breakers are declarative and uniform
  • Certificate rotation is the platform's job

What it costs

  • Ambient mode is younger than sidecars; debugging tooling is still catching up
  • The ztunnel is a per-node dependency and a per-node failure domain
  • Waypoint proxies reintroduce a hop for services that need L7 policy

ADR-05 · Citus on Azure Cosmos DB for PostgreSQL for the transactional core

Area: Data · Status: Accepted · Shown on views: 08, 10, 13, 19

Context. Orders, payments, refunds, the inventory ledger and seller settlement need multi-row atomic transactions, foreign keys and the ability to answer a financial question exactly. They also need to hold 6M orders a day, and up to 1.5M in a peak hour, which no single PostgreSQL writer will do.

Decision. Azure Cosmos DB for PostgreSQL (managed Citus). Orders, order lines and payments are distributed on a key derived from order_id so an entire order is one shard and one local transaction; settlement is distributed by seller for the payout run.

Option Verdict Reasoning
Citus Chosen Horizontal sharding with real PostgreSQL semantics, and the shard key can be chosen so that cross-shard transactions are rare.
Single PostgreSQL Flexible Server Rejected Correct and simple; the write ceiling arrives long before 6M orders a day.
Cassandra or ScyllaDB Rejected Scales beautifully and cannot give the atomic multi-row guarantee that a payment and its ledger entry require.
Cosmos DB NoSQL for orders too Rejected Transactions are limited to a logical partition, which forces the transaction boundary into application code for the one place it must not be.

What it buys

  • Financial correctness is a database guarantee, not an application convention
  • Existing PostgreSQL skills, tooling, migrations and Debezium CDC all still apply
  • Read replicas and per-shard rebalancing are managed operations

What it costs

  • The shard key is close to irreversible; choosing it badly is the expensive mistake in this design
  • Cross-shard queries and joins are slow and must be designed out or served from the lakehouse
  • Cross-region replication is asynchronous, which is why the region-loss RPO is 30 seconds rather than zero

ADR-06 · Cosmos DB for NoSQL as the catalog and listing store

Area: Data · Status: Accepted · Shown on views: 08, 10, 17

Context. 150M products across categories whose attributes have nothing in common: a phone has storage and a colour, a shirt has a size and a fit, a book has an ISBN. Reads dominate by orders of magnitude and must be served in single-digit milliseconds from more than one region.

Decision. Cosmos DB for NoSQL holds products, listings and their attributes as documents, partitioned by category with a synthetic suffix on hot categories, and configured for multi-region writes with session consistency.

Option Verdict Reasoning
Cosmos DB NoSQL Chosen Schema-per-category without migrations, a read SLA the product page depends on, and multi-region writes out of the box.
MongoDB or Cassandra self-managed on AKS Rejected Cheaper per gigabyte, and the operational load of a multi-region 150M-document cluster is a team we would rather spend elsewhere.
PostgreSQL with JSONB Rejected Attractive for the smaller catalog it starts as, and the wrong shape by the time it is the real one.

What it buys

  • A new category is a new document shape, not a schema migration
  • Multi-region writes make the catalog genuinely RPO 0
  • Throughput is provisioned per container, so a bulk feed cannot starve the product page

What it costs

  • Request-unit pricing rewards careful query design and punishes cross-partition scans
  • Partition-key choice is as consequential here as the shard key on Citus
  • Search and aggregation are somebody else's job — hence OpenSearch and Pinot

ADR-07 · Redis as the inventory contention point, Citus as the truth

Area: Commerce · Status: Accepted · Shown on views: 04, 08, 14, 15

Context. A sale item can have 100,000 people trying to buy the last few units in the same second. Overselling is unacceptable because it converts into a cancellation, a refund and a lost customer. Under-selling is also unacceptable because it is lost revenue on the item that drove the traffic.

Decision. Available stock per listing per node is a Redis integer. Reservation is a Lua script that checks and decrements atomically and writes a reservation with a 900-second TTL. Confirmation writes an immutable row to the inventory ledger in Citus. An hourly reconciliation corrects the counter from the ledger, never the other way round.

Option Verdict Reasoning
Redis atomic counter with a durable ledger Chosen The contention point is an in-memory single-threaded operation; durability is provided by a store that is not on the hot path.
Optimistic concurrency in PostgreSQL Rejected Correct and simple, and a single hot row with 100k contenders is a queue with a database attached.
Distributed locks Rejected The lock would have to be held across a payment redirect measured in minutes, which is not a lock, it is an outage.
Reserve nothing, oversell and cancel Rejected Operationally cheapest and it makes the marketplace look broken exactly when the most people are watching.

What it buys

  • Reservation p99 under 20 ms, with no lock and no database contention
  • Abandoned checkouts return stock automatically when the TTL expires
  • The ledger gives an auditable answer to where every unit went

What it costs

  • A Redis failover loses in-flight reservations; they are rebuilt from the reservation log with a brief oversell window
  • Reconciliation drift is a real, monitored business metric rather than a theoretical one
  • Seller stock also sold off-platform can never be perfectly accurate — SLA breach, not oversell, is the observable

ADR-08 · Self-managed OpenSearch for lexical and vector retrieval

Area: Discovery · Status: Accepted · Shown on views: 10, 12, 17

Context. Search must cover 150M listings, answer in under 300 ms at p95, support faceting and typo tolerance, and increasingly handle semantic queries. Index freshness matters: a price change or a stock-out that is ten minutes stale produces a listing that cannot be bought.

Decision. OpenSearch on dedicated AKS node pools, holding both the lexical index and a kNN vector index over the same documents. Hybrid retrieval combines BM25 and vector scores; availability is applied as a query-time filter from Redis rather than being indexed.

Option Verdict Reasoning
OpenSearch self-managed Chosen Apache-2.0, one cluster for both retrieval modes, and full control over sharding, refresh interval and index lifecycle.
Azure AI Search Rejected Genuinely good and the per-partition pricing and index-size limits do not fit a 150M-document catalog with this churn.
Elasticsearch Rejected The stronger product in places; the SSPL licence is an unnecessary argument to have at this scale.
A separate vector database Rejected Two systems to keep in sync over the same corpus, for a recall improvement that hybrid search in one cluster largely already provides.

What it buys

  • One store, one query, one consistency story for both retrieval modes
  • Alias-based zero-downtime rebuilds make a bad index a pointer change to undo
  • Index freshness p95 of 30 seconds is achievable because we own the refresh policy

What it costs

  • A large stateful cluster to run, upgrade and capacity-plan — the biggest single operational burden in the estate
  • Vector indexes are memory-hungry, and embedding dimensionality is now a cost decision
  • Rebuild from Kafka takes about an hour, which is the real recovery time for a corrupted index

ADR-09 · Apache Pinot for seller-facing and operational analytics

Area: Data · Status: Accepted · Shown on views: 10, 11, 12, 21

Context. 1.4M sellers each want their own near-real-time sales, funnel and SLA numbers, and the sale-day war room wants GMV, conversion and failure rates aggregated across the whole platform within seconds. Neither may touch the transactional shards.

Decision. Apache Pinot ingests from Kafka with upsert, serving both the seller dashboard API and the internal war-room boards.

Option Verdict Reasoning
Apache Pinot Chosen Built for exactly this: high-QPS, low-latency aggregation over a real-time stream with upsert semantics.
ClickHouse Rejected Faster on heavy analytical scans, weaker on the thousands-of-concurrent-tenants query pattern a seller dashboard actually is.
Trino over the lakehouse Rejected Already in the estate for ad-hoc work; seconds-to-minutes latency is wrong for a dashboard a seller refreshes.
Read replicas of the transactional store Rejected Puts analytical load next to the money path, which is the failure mode this whole separation exists to prevent.

What it buys

  • Seller analytics stays under a second at high concurrency without touching Citus
  • Upsert handles order-state corrections without a rebuild
  • The same store serves the internal war room, so sellers and operations see one number

What it costs

  • Another stateful cluster with its own tuning discipline
  • Table and index design is per query pattern; a new dashboard is a schema conversation
  • Pinot has no mark in the icon library, so it carries a neutral store glyph on the views

ADR-10 · Iceberg on ADLS Gen2 with Spark and Trino for the lakehouse

Area: Data · Status: Accepted · Shown on views: 10, 11

Context. The analytical estate must hold years of clickstream, order and supply-chain history, feed model training with point-in-time correctness, answer finance and regulatory questions, and not lock the organisation into whichever engine is fashionable this year.

Decision. Apache Iceberg tables on ADLS Gen2 in a bronze/silver/gold layout. Spark on AKS for batch, Trino for interactive SQL, Airflow for orchestration, Superset for dashboards.

Option Verdict Reasoning
Iceberg with Spark and Trino Chosen Open table format with hidden partitioning, schema evolution and time travel; storage and compute are separately negotiable.
Azure Databricks with Delta Rejected The best developer experience of the three and a per-DBU premium on a very large batch estate, plus a platform dependency.
Microsoft Fabric or Synapse Rejected Attractive integration; less control over file layout, compaction and cost at petabyte scale.

What it buys

  • Time travel makes a wrong mart recoverable and a training set reproducible
  • Compute engines can be swapped without moving a byte of data
  • Bronze retention of 30 days keeps the largest tier cheap

What it costs

  • Compaction and small-file management are real jobs that must be scheduled and watched
  • A metastore is now a critical dependency of the analytical estate
  • Self-managed Spark is more operational work than a managed platform, and that is the price of the choice

ADR-11 · One master per asset in Blob, derivatives generated on demand

Area: Media · Status: Accepted · Shown on views: 02, 08, 10

Context. Every listing carries several images, each needed in a dozen sizes and three formats across app, web and email. Pre-generating the full matrix for 150M listings is storage nobody should buy.

Decision. Sellers upload one master to Blob Storage. imgproxy on AKS generates the requested derivative on demand, signed by URL, and Front Door caches the result at the edge.

Option Verdict Reasoning
Blob plus imgproxy behind the CDN Chosen One stored master, derivatives cached where they are consumed, and the transform is open-source and self-hosted.
Pre-generate every variant Rejected Simple and fast, and multiplies the storage bill by the size of the variant matrix, most of which is never requested.
A third-party image CDN Rejected Excellent product, another vendor on the product-page critical path, and egress charged twice.

What it buys

  • Storage grows with the catalog, not with the design system
  • A new breakpoint or format is a URL parameter, not a re-processing campaign
  • Signed URLs stop the transform service being used as free image processing

What it costs

  • A cold derivative costs a transform, so cache hit ratio is a business metric
  • The transform fleet has to be sized for the sale-hour cold-cache spike
  • Malicious or malformed uploads must be validated before they reach the transformer

ADR-12 · Self-managed Kafka on Strimzi rather than Event Hubs

Area: Eventing · Status: Accepted · Shown on views: 02, 08, 11, 12, 19

Context. The event backbone carries around 4B clickstream events a day plus 38 domain topics, is the replay source for every derived store, and needs log compaction, tiered storage, per-tenant quotas and partition counts chosen per topic.

Decision. Apache Kafka on the Strimzi operator on AKS, roughly 300 brokers per region, tiered storage to Blob beyond seven days of hot retention, with MirrorMaker 2 replicating to the secondary region.

Option Verdict Reasoning
Kafka on Strimzi Chosen Full control of partitions, compaction, retention, quotas and tiering — every one of which this design depends on.
Azure Event Hubs (Kafka surface) Rejected Removes the operational load and constrains partition counts, compaction and long retention in ways this backbone cannot accept.
Apache Pulsar Rejected Better tiered storage and multi-tenancy on paper; a much smaller operational talent pool to hire from.
Azure Service Bus Rejected A queue, not a log. Replay is the whole point here.

What it buys

  • Every derived store is rebuildable by replaying the log, which is what makes the no-backup policy defensible
  • Compacted topics give current state and append-only topics give history from the same infrastructure
  • Consumer autoscaling on lag rather than CPU, via KEDA

What it costs

  • A 300-broker fleet is a dedicated team with a permanent upgrade and rebalance workload
  • Partition count is easy to raise and impossible to lower without a topic migration
  • Cross-region mirroring lag is a leading indicator of a bad failover and needs its own alerting

Area: Eventing · Status: Accepted · Shown on views: 11, 12, 17, 25

Context. Availability rollups, fraud features, search index updates and settlement accruals must be computed within seconds of the event. The same numbers must also be correct after late-arriving and corrected events, which a streaming job alone cannot guarantee.

Decision. Apache Flink via the Kubernetes operator for all streaming computation, with event-time windows, checkpointing and transactional sinks. Spark restates the same gold tables nightly from the lakehouse.

Option Verdict Reasoning
Flink for streaming, Spark for batch restatement Chosen Each engine used where it is strongest; the nightly restatement is the correctness backstop for the fast path.
Kafka Streams only Rejected Excellent for simple per-service transforms and weaker on large stateful joins and event-time semantics across domains.
Spark Structured Streaming only Rejected One engine to operate, and micro-batch latency does not meet a 40 ms fraud-scoring budget.

What it buys

  • Exactly-once end to end where it matters, at seconds of latency
  • One place to express event-time correctness rather than in each consumer
  • Late and corrected events cannot leave the marts permanently wrong

What it costs

  • Flink state backends and checkpointing need tuning, and a badly sized job fails at peak rather than in test
  • The same logic exists twice, in Flink and in Spark, and the two can drift
  • Savepoint-based upgrades are their own operational discipline

ADR-14 · Temporal for sagas that span services and days

Area: Commerce · Status: Accepted · Shown on views: 06, 08, 14, 16, 18

Context. A refund touches the payment rail, the order, the inventory ledger, the seller claim and the settlement cycle, and can take weeks. A settlement run spans millions of entries and must be resumable. Pure choreography leaves this state implicit in a scattering of retry queues that nobody can inspect.

Decision. Temporal, self-hosted on AKS, orchestrates refunds, returns, settlement runs, dispute workflows and seller onboarding. Routine domain propagation stays choreographed over Kafka.

Option Verdict Reasoning
Temporal Chosen Durable execution with visible history, so an operator can see exactly where a stuck refund stopped and why.
Choreography only Rejected Fine for propagation and poor for compensation; the saga state ends up implicit and unqueryable.
Azure Durable Functions Rejected Good managed option, and it pulls the money path onto a second compute platform with its own scaling behaviour.
Logic Apps Rejected Integration-shaped rather than transaction-shaped; wrong tool for compensating financial workflows.

What it buys

  • Compensation logic is code with tests, not a diagram in a wiki
  • Retries, timeouts and heartbeats come from the platform, uniformly
  • A stuck workflow is inspectable and resumable rather than lost

What it costs

  • Another stateful cluster with its own database and upgrade path
  • Workflow versioning is a genuine discipline — a long-running saga outlives several deployments
  • It is easy to over-apply; anything that fits an event is cheaper as an event

ADR-15 · Debezium change data capture instead of dual writes

Area: Data · Status: Accepted · Shown on views: 10, 11

Context. The lakehouse, the search index, the analytics store and the derived caches all need to know when a transactional row changes. Having each service publish its own event alongside its own write is the classic way to get the two permanently out of step.

Decision. Debezium reads the PostgreSQL write-ahead log and publishes change events to Kafka. Services publish business events for meaning; CDC publishes state for derivation. A transactional outbox is used where an event must be atomic with the write that caused it.

Option Verdict Reasoning
Debezium CDC plus an outbox where atomicity matters Chosen The log is the single source; nothing can commit a row without the change being seen.
Dual writes from the application Rejected Two systems, no shared transaction, and eventual divergence with no way to detect it.
Periodic batch extraction Rejected Simple and it puts an hour of staleness in front of search and analytics, which is the whole problem.

What it buys

  • No derived store can silently miss a change
  • Backfill is a connector snapshot rather than a bespoke migration
  • Business events stay about meaning rather than being overloaded with state

What it costs

  • Schema changes in the source now have downstream consumers, so migrations need an expand-and-contract discipline
  • Replication slots retain WAL, so a stalled connector becomes a database disk incident
  • Change events leak table shape, so a mapping layer is needed to keep it out of public contracts

ADR-16 · Reserve stock before requesting money

Area: Commerce · Status: Accepted · Shown on views: 04, 14, 15

Context. UPI and card authorisation take the customer out of the app and can take a minute. Requesting money first and discovering afterwards that the unit is gone produces the most expensive failure the platform has: money taken, no order, and a refund to explain.

Decision. Checkout reserves inventory with a TTL, then requests payment, then confirms the reservation on authorisation. Any abandoned or failed path releases explicitly; TTL expiry is the backstop.

Option Verdict Reasoning
Reserve, then pay, then confirm Chosen Failure lands on stock, which is recoverable, rather than on money, which is not.
Pay, then reserve Rejected Maximises conversion on paper and converts every stock-out into a refund and a support contact.
No reservation at all Rejected Only defensible for genuinely unlimited stock, which a marketplace does not have.

What it buys

  • A stock-out is discovered before the customer is charged
  • Reservation TTL puts a bound on how long abandoned baskets can hold inventory
  • The reservation identifier gives every downstream step something idempotent to key on

What it costs

  • Slow checkouts hold stock away from buyers who would have completed
  • TTL length is a tuned trade between conversion and availability, and it differs by category
  • A reservation service outage stops checkout even when stock exists

ADR-17 · Buy Box as a scored, cached decision rather than cheapest-wins

Area: Marketplace · Status: Accepted · Shown on views: 04, 13, 17

Context. Many sellers offer the same product. Which offer a shopper sees decides the shopper's experience, the seller's revenue and the platform's fulfilment cost. Computing it per request over every listing is not affordable at product-page latency.

Decision. A Buy Box score per listing combines landed price, delivery promise, seller SLA history, return rate and stock position. It is recomputed on change events, cached in Redis, and read at page render.

Option Verdict Reasoning
Precomputed multi-factor score Chosen Fast to read, explainable to a seller, and it lets fulfilment reliability compete with price.
Lowest price wins Rejected Trivial to compute and it rewards sellers who cannot deliver, which the platform pays for later in cancellations.
Score at query time Rejected Freshest possible answer and it does not fit inside a 300 ms product page across millions of listings.

What it buys

  • Delivery reliability is a commercial incentive rather than a policy document
  • A seller can be shown why they lost the Buy Box, which is what makes the mechanism defensible
  • Product-page latency is unaffected by the number of competing offers

What it costs

  • The score is a ranking system, so it needs bias review and an appeal path
  • Score staleness during rapid price wars can show an offer that is no longer best
  • Weight changes move real revenue between sellers and must be governed, not tuned casually

ADR-18 · Cart state lives on the server

Area: Commerce · Status: Accepted · Shown on views: 04, 08, 22

Context. The journey map is unambiguous: the deepest trough is payment, and the specific failure is a basket lost when the customer is sent to a UPI or bank application and comes back to a cold app. A client-held basket cannot survive that, nor a device switch.

Decision. Cart is a server-side entity keyed on customer identity, held in Redis with a durable write-behind, and returned intact on resume regardless of device or app restart.

Option Verdict Reasoning
Server-side cart Chosen Survives the payment handoff, the device switch and the app crash — the three ways carts are actually lost.
Client-held basket Rejected Zero infrastructure and it loses exactly the sessions worth the most money.
Cookie or local storage with server merge Rejected A merge strategy is a source of duplicate lines and support contacts, for a saving that does not matter.

What it buys

  • Resume after a payment redirect is the default rather than a feature
  • Cart contents feed abandonment recovery and personalisation without client instrumentation
  • One cart across app, web and care console

What it costs

  • Cart is now a high-write service on the critical path with its own scaling profile
  • Anonymous-to-identified merge on login still has to be got right
  • Retention and privacy rules now apply to a store nobody thinks of as personal data

ADR-19 · Isolate the cardholder data environment in its own subscription

Area: Payments · Status: Accepted · Shown on views: 08, 14, 23

Context. Card data brings PCI DSS scope, and scope is contagious: anything that can reach cardholder data is in the audit. With 400 services and hundreds of engineers, uncontrolled scope is both an audit cost and a permanent drag on delivery.

Decision. Payment orchestration, the token vault, PSP connectors and the webhook receiver run in a separate Azure subscription and a separate AKS cluster. The commerce plane holds only tokens and amounts, and reaches the boundary over mTLS. Payment keys live in a Managed HSM.

Option Verdict Reasoning
Separate subscription and cluster Chosen Scope stops at a subscription boundary that is also an RBAC, network and billing boundary.
Namespace isolation in the main cluster Rejected Technically arguable, and it invites an auditor to ask about every workload sharing the control plane.
Outsource entirely to a PSP-hosted flow Rejected Smallest possible scope and it surrenders routing across three acquirers, which is a real availability and cost lever.

What it buys

  • The audit boundary is small, explicit and demonstrable
  • A compromise of the commerce plane yields tokens, not card numbers
  • Payment can be given its own change-control regime without slowing everything else

What it costs

  • A second cluster, a second pipeline and a cross-plane hop on the checkout path
  • Debugging spans a boundary that is deliberately hard to see across
  • Some duplication of platform capability inside the boundary

ADR-20 · Double-entry settlement ledger, no mutable balances

Area: Payments · Status: Accepted · Shown on views: 05, 10, 13, 18

Context. The platform holds other businesses' money between a customer payment and a seller payout, net of commission, shipping, penalties and refunds. A seller who disputes a deduction must be given a traceable answer, and finance must reconcile to the paisa against PSP statements.

Decision. Settlement is an append-only double-entry ledger in Citus, one entry pair per commercial fact, keyed to the order line that caused it. Balances are projections. PSP statements are reconciled against the ledger daily, and financial records are exported write-once for eight years.

Option Verdict Reasoning
Append-only double-entry ledger Chosen Every rupee has two sides and a cause; a dispute is answered by replaying entries.
Mutable balance column per seller Rejected Simple, fast and unanswerable the first time a seller asks why a number moved.
Reconstruct from order events on demand Rejected Correct in principle, and it makes finance depend on the event retention policy of another team.

What it buys

  • Any payout can be explained line by line back to an order line
  • Reconciliation breaks are detected daily instead of at quarter end
  • The audit and regulatory export is a read of an immutable table

What it costs

  • The ledger is the fastest-growing transactional table in the estate
  • Corrections are reversing entries, never edits, which people find unintuitive
  • Projection lag means a seller-facing balance is a near-real-time number, not an instantaneous one

ADR-21 · Keycloak for 500M customers, Entra ID for every privileged human

Area: Identity · Status: Accepted · Shown on views: 08, 23, 24

Context. Customer identity is a very large, low-value-per-identity population authenticating mostly by phone and OTP. Staff and seller-staff identity is a small, high-value population that needs conditional access, privileged access management and device compliance.

Decision. Keycloak, self-hosted on AKS, is the customer realm. Microsoft Entra ID is the workforce and seller-staff realm. Three realms, three token audiences, no shared tokens.

Option Verdict Reasoning
Keycloak for customers, Entra ID for workforce Chosen Each population served by the system whose pricing and feature model fits it.
Entra External ID for customers too Rejected One vendor and one operating model; per-monthly-active-user pricing at 180M actives is not defensible.
Build it in house Rejected Nobody should implement OIDC, token rotation and MFA from scratch in 2026.

What it buys

  • Identity cost scales with infrastructure rather than with population
  • Privileged access keeps enterprise-grade conditional access and PIM
  • A stolen seller token cannot be replayed against a customer API

What it costs

  • A highly available Keycloak cluster with an upgrade treadmill and an owning team
  • Two identity operating models, two sets of runbooks
  • Federation between the realms has to be designed rather than assumed

ADR-22 · Authorisation as policy code with field-level masks

Area: Security · Status: Accepted · Shown on views: 23, 24

Context. The same order endpoint is called by the customer who placed it, a care agent, a seller who fulfils one line of it and an internal analytics job. Each is entitled to a different subset of the same record, and encoding that in handler code guarantees it drifts.

Decision. Open Policy Agent evaluates authorisation next to each service. The decision returns both an allow or deny and a field mask, so one endpoint serves every caller at the right level of detail.

Option Verdict Reasoning
Policy as code with field masks Chosen Access rules are reviewable, testable and diffable in one place.
Checks in application code Rejected Works for three roles and becomes untraceable at thirty.
Separate endpoints per audience Rejected Explicit and it multiplies the API surface and the ways it can drift apart.

What it buys

  • One place to answer who can see a customer's phone number
  • Policy changes ship without changing service code
  • The decision is loggable, so an access review has data to work from

What it costs

  • Policy evaluation is on the request path and has a latency budget
  • Policy is a language engineers must learn, and a bad policy fails closed at peak
  • Masks must be tested, or a field quietly disappears from a response

ADR-23 · KServe, MLflow and Feast on the same fleet as the callers

Area: ML · Status: Accepted · Shown on views: 11, 17, 25

Context. Ranking, recommendation, fraud scoring, delivery-time prediction and abuse detection are all on request paths with millisecond budgets. Training is bursty and offline. Training and serving must see the same features or the models silently decay.

Decision. MLflow as the registry, Feast as the feature store with Redis online and Iceberg offline, KServe for serving on the same AKS fleet, with canary rollout and automatic rollback on latency or quality regression.

Option Verdict Reasoning
KServe, MLflow and Feast on AKS Chosen Inference is a pod next to the caller, so there is no cross-boundary hop inside a 40 ms budget.
Azure Machine Learning managed endpoints Rejected Less to operate, and a network hop and a scaling model outside our control on the request path.
Models embedded in each service Rejected Lowest latency of all and no shared versioning, rollback or monitoring — every team reinvents deployment.

What it buys

  • One feature definition serves training and inference, which removes the commonest cause of silent decay
  • Model rollout uses the same canary and rollback machinery as service code
  • GPU pools are shared across ranking, embedding and fraud

What it costs

  • Three more open-source components to run and upgrade
  • Feature-store correctness at point in time is subtle and easy to get wrong
  • Canary catches latency regressions but not slow relevance decay, so a human review stays in the loop

ADR-24 · Azure OpenAI for embeddings and content moderation

Area: ML · Status: Accepted · Shown on views: 12, 17, 25

Context. Semantic search and counterfeit detection need text and image embeddings over a catalog that churns in bursts — a brand re-uploading 200,000 listings is a single event. Owning GPU capacity for that peak means owning idle GPUs the rest of the time.

Decision. Azure OpenAI provides embeddings for catalog text and images and first-pass moderation classification, called from Flink enrichment jobs rather than from any request path. Embeddings are cached and only recomputed when the source text changes.

Option Verdict Reasoning
Azure OpenAI Chosen Elastic capacity for a bursty workload, inside the same tenancy and data-residency boundary.
Self-hosted open-weight embedding models Rejected Cheaper per token at steady state and it requires provisioning for the burst, which is the whole difficulty.
No semantic layer at all Rejected Lexical search alone loses long-tail and vernacular queries, which is a large share of Indian e-commerce search.

What it buys

  • No GPU fleet sized for a re-upload spike
  • Model upgrades are a version change, not a migration
  • Cost tracks catalog churn, which is measurable and controllable

What it costs

  • A managed dependency in the indexing path, so quota and throttling are capacity-planning inputs
  • Changing embedding model means reindexing the whole corpus
  • Nothing on a customer request path may call it — that is a rule, not a guideline

ADR-25 · GitOps with Argo CD, and progressive delivery with automatic rollback

Area: Delivery · Status: Accepted · Shown on views: 20

Context. Hundreds of engineers deploying to a fleet running the country's shopping traffic need a change mechanism that is auditable, reversible and unable to be bypassed under pressure.

Decision. Git is the only way in. Argo CD reconciles the cluster to the repository, Argo Rollouts runs canaries at 1, 5 and 25 percent gated on SLO burn rate, and Kyverno rejects any image that is unsigned or lacks an SBOM. Terraform owns the infrastructure.

Option Verdict Reasoning
GitOps with progressive delivery Chosen The audit trail and the rollback are the same mechanism, which is what makes it hold up under pressure.
Pipeline-push deployment Rejected Familiar, and cluster state drifts from any repository as soon as someone is in a hurry.
Manual approval gates only Rejected A human approving a change they cannot evaluate is a ritual, not a control.

What it buys

  • Rollback is a revert, and it works at three in the morning
  • Every production change has an author, a reviewer and a commit
  • Drift is detected and corrected continuously

What it costs

  • Database migrations are the one class GitOps does not make safe; expand-and-contract is mandatory
  • Feature flags accumulate into untested branches unless they are actively retired
  • The freeze window during sale events shifts risk to the release immediately after it

ADR-26 · OpenTelemetry with an open-source backend per signal

Area: Operations · Status: Accepted · Shown on views: 21, 22

Context. Four hundred services in several languages must be observable at a volume where per-gigabyte ingestion pricing becomes a governing constraint on what can be measured. And the earliest signal that checkout is broken is not a machine metric — it is conversion.

Decision. OpenTelemetry instrumentation everywhere, collected by the OTel Collector, stored in Prometheus with Thanos for metrics, Loki for logs and Tempo for traces, and presented in Grafana. Business metrics from Flink into Pinot are a first-class alerting source. Azure Monitor covers the platform plane only.

Option Verdict Reasoning
OpenTelemetry with the open-source stack Chosen Vendor-neutral instrumentation, retention priced as object storage, and thirteen months of history for year-on-year sale comparison.
Azure Monitor and Application Insights throughout Rejected Excellent integration and an ingestion bill at this event volume that would force sampling decisions for the wrong reason.
A commercial observability platform Rejected Best product experience of the three, and per-host and per-gigabyte pricing across this fleet is not affordable.

What it buys

  • Instrumentation is portable, so the backend stays replaceable
  • Long metric retention is affordable, which is what makes sale-on-sale comparison possible
  • Conversion drops page before CPU does

What it costs

  • Four storage systems to operate, scale and upgrade
  • Tail-based sampling needs tuning, and a bad rule loses the traces that mattered
  • Correlation across the four backends is Grafana's job and needs deliberate dashboard design

ADR-27 · Two Indian regions, active and read-active, with an honest RPO

Area: Resilience · Status: Accepted · Shown on views: 19, 22

Context. Data residency keeps everything in India, which leaves two Azure regions. The customer-facing availability target is 99.99%. Genuine active-active writes on a sharded relational core across regions would mean either synchronous cross-region commits or conflict resolution on financial data.

Decision. Central India is the active write region across three availability zones with synchronous in-region replication. South India is read-active at about 30% capacity with asynchronous replication, a full search index replica and a mirrored Kafka. Regional failover is a rehearsed runbook with a human decision point.

Option Verdict Reasoning
Active primary, read-active secondary Chosen Zone loss is invisible; region loss is a 20-minute rehearsed procedure with a stated, honest 30-second data loss.
Active-active writes across both regions Rejected Either synchronous commits across regions, which breaks the checkout latency budget, or conflict resolution on money, which nobody should build.
Single region, three zones Rejected Cheapest and simplest, and it has no answer at all to losing a region.

What it buys

  • Zone failure is handled by the platform with RPO 0
  • Read traffic can spill to the secondary during a peak, so the standby capacity is not idle
  • Catalog and media are genuinely multi-region by construction

What it costs

  • Up to 30 seconds of transactional writes lost in a region failover, reconciled by a manual playbook
  • Standby capacity is a permanent cost against an event that may never happen
  • Failover must be rehearsed quarterly or the runbook is fiction

ADR-28 · A written degradation contract enforced by feature flags

Area: Resilience · Status: Accepted · Shown on views: 03, 21, 22

Context. In a sale hour traffic multiplies tenfold and something has to give. Deciding what gives while it is happening produces inconsistent, unrehearsed and occasionally revenue-destroying choices.

Decision. Every customer-facing capability has a written behaviour at normal, 3x, 10x and brownout load. Cart, checkout and payment are protected and never shed. Each cell is a feature flag with a named owner, exercised in a game day before every sale event.

Option Verdict Reasoning
A pre-agreed contract behind flags Chosen The decision is made calmly, in advance, by the people who own the revenue it affects.
Autoscale and hope Rejected Autoscaling has a lead time and a quota ceiling; both are discovered at exactly the wrong moment.
Shed at the load balancer Rejected Sheds randomly, which means it sheds paying customers as readily as browsers.

What it buys

  • Nobody improvises during the highest-revenue hour of the year
  • Product owners have agreed in advance what their feature does under stress
  • Game days prove the flags work rather than assuming they do

What it costs

  • Degradations interact: a cached price plus a stale availability filter can show an offer that cannot be bought
  • The waiting room is the most visible mechanism in the set — if it misbehaves, it becomes the story
  • Above roughly 5x the binding constraint is physical fulfilment capacity, which no flag can relieve