Careeroza
System Design and Scenario Questions
Design the checkout flow for ShopEase across services.
client ─POST /api/orders (Idempotency-Key)─► gateway ─► order-service
1. order-service: validate cart, price snapshot from product-service (sync)
2. save Order(PENDING) + outbox "OrderCreated" (one local tx) ──► 202 Accepted {orderId}
3. inventory-service on OrderCreated → reserve stock (TTL 15 min) → "StockReserved"
or "StockRejected" ──► order CANCELLED
4. payment-service on StockReserved → charge with key=orderId → "PaymentCompleted"
or "PaymentFailed" ──► release stock, order CANCELLED
5. order-service on PaymentCompleted → order CONFIRMED → "OrderConfirmed"
6. notification-service on OrderConfirmed → email/SMS- Choreography saga, outbox in every service, idempotent consumers keyed by event id,
orderIdas the Kafka key so events of one order stay ordered. - Client sees PENDING and gets the final status via polling or WebSocket. A timeout job cancels orders stuck in PENDING.
The payment service has become slow (p99 from 300 ms to 8 s). What do you do?
- Contain: timeouts on the caller (e.g. 2 s), circuit breaker opens on slow-call rate, bulkhead so other features keep working; move payment to async (order PENDING, pay later) if possible.
- Find: Grafana for latency/error rate per endpoint; Zipkin trace of a slow request to see which span is slow (DB query? the bank API? GC pauses?); check thread pool, HikariCP pool wait time, CPU, GC logs.
- Fix the cause: add an index, increase pool size, scale out pods, add caching for read calls, or rate-limit/queue calls to the external provider.
- Prevent: SLO alerts on p99, load tests before sales (Big Billion Days style traffic).
How do you avoid charging a customer twice?
- Client sends an
Idempotency-Keyper checkout attempt; the Pay button is disabled after the first click. - payment-service stores the key with a unique constraint before calling the provider; a duplicate request returns the stored result.
- Pass the same key (or
orderId) to the payment provider (Razorpay/Stripe support idempotency keys or a unique receipt id), so retries after a timeout do not create a second charge. - On timeout, do not blindly retry: query the provider for the status of that order id first. Reconcile daily with the provider's settlement report.
- Kafka consumers dedupe by event id; one payment per
orderIdenforced by a unique index.
A Kafka consumer is lagging far behind during a sale. How do you handle it?
- Check consumer lag per partition (Kafka UI, Burrow,
kafka_consumergroup_lagmetric). - Scale consumers up to the number of partitions; if already equal, increase partitions (note: changes key → partition mapping for new messages).
- Make processing faster: batch listener, bulk DB writes, avoid a synchronous HTTP call per message, raise
concurrencyon the listener container. - If one partition is hot (one key gets most traffic), choose a better key.
One instance of order-service throws OutOfMemoryError every few hours. How do you debug it?
- Run with
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps, analyse with Eclipse MAT: look at the dominator tree. - Common causes: unbounded caches/maps, loading whole tables into memory (no pagination), large Kafka batches, thread leaks, container memory limit lower than
-Xmx+ metaspace + thread stacks. - Use
-XX:MaxRAMPercentage=75in containers instead of a fixed-Xmx; watch heap metrics in Grafana to see the growth pattern.
How do you secure communication between microservices?
- External: the gateway validates the user's JWT (OAuth2 resource server) and relays the token (
TokenRelayfilter) so downstream services can authorise by user and roles. - Service-to-service: OAuth2 client credentials tokens with scopes, or mTLS via a service mesh (Istio, Linkerd).
- Every service still validates tokens (zero trust); do not trust "internal network" alone. See the Spring Security material for details.
How would you design a rate limiter for the public API at the gateway?
Use a token bucket per key (user id, API key or IP): capacity = burst, refill rate = allowed RPS. Store buckets in Redis so all gateway instances share them, and update atomically with a Lua script (Spring Cloud Gateway's RedisRateLimiter does exactly this). Return 429 Too Many Requests with X-RateLimit-Remaining and Retry-After headers. Choose fail-open (allow if Redis is down) for normal APIs and fail-closed for sensitive ones like OTP/login. Runnable token bucket in the Coding Round.