Problem RestatementProblem
Design a highly scalable system that can generate and distribute unique, trackable coupons to millions of users. The solution must support personalization, real-time redemption validation, lifecycle tracking, and seamless integration with a CRM for targeted campaigns and analytics.
RequirementsRequirements
1.1 Functional
- Generate coupons: Unique codes per user, tied to specific campaigns.
- Track metadata: Expiry, discount type/value, usage limits.
- Support types: One-time and multi-use coupons.
- Real-time validation: Validate and apply coupons at checkout in under 100ms.
- CRM Integration: Targeted audience segmentation and campaign triggers.
- Event tracking: Emit events for issued, delivered, redeemed, and expired coupons.
1.2 Non-Functional
- High Scalability: Support millions of users and thousands of redemptions per second.
- High Availability: ≥ 99.9% uptime.
- Security: Prevent tampering, brute-force, and replay attacks.
- Auditability: Complete audit trails for ROI and fraud detection.
1.3 Scale Estimates
Users
100 million
Campaigns
10,000 active campaigns
Redemptions
5,000 requests/sec peak
- Storage: Highly compressed coupon records for historical tracking (billions of rows).
1.4 API Design
The core APIs required for the service:
/v1/couponsGet CouponsFetch active coupons for a user./v1/campaigns/:id/issueGenerate CouponIssue a new coupon for a user./v1/coupons/redeemRedeem CouponValidate and apply a coupon./v1/eventsTrack EventLog coupon usage events.High-Level ArchitectureArchitecture
2.1 Overview
- Campaign Engine: Orchestrates user selection and issuance rules.
- Coupon Generator: Creates unique codes and persists metadata.
- Redemption API: Validates and tracks coupon state in real-time.
- Analytics: Aggregates usage events for ROI and fraud detection.
2.2 Architecture Diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
U["User - Web/Mobile"] -->|"GET /coupons?userId"| AG["API Gateway"]
CD -->|"validate & update status"| DB
AG -->|"fetchCoupons(userId)"| CD["Coupon Distribution Service"]
AG -->|"redeemCoupon(userId)"| CD["Coupon Distribution Service"]
CD -->|"queryAssignedCoupons(userId)"| DB[(Coupon DB)]
CD -->|"if none → request new"| CG["Coupon Generation Service"]
CG -->|"generateUniqueCode() + persist"| DB
CG -->|"emitCouponCreatedEvent"| MQ[(Message Queue / Kafka)]
CRM["CRM / Campaign Engine"] -->|"eligibility rules, personalization"| CD
MQ -->|"analytics + targeting feedback"| CRM
U -->|"POST /redeemCoupon {couponId}"| AG
MQ --> NS["Notification Service"]
NS -->|"send coupon assigned"| U
classDef userFlow fill:#f0f8ff,stroke:#333,stroke-width:1px;
classDef coreService fill:#e0ffe0,stroke:#333,stroke-width:1px;
classDef asyncFlow fill:#fff0f0,stroke:#333,stroke-width:1px;
classDef crmFlow fill:#fef7e0,stroke:#333,stroke-width:1px;
class U,AG userFlow;
class CD,CG,DB coreService;
class MQ,NS asyncFlow;
class CRM crmFlow;Data ModelData model
coupons Table
CREATE TABLE coupons (
id UUID PRIMARY KEY,
code TEXT UNIQUE,
user_id TEXT,
campaign_id TEXT,
discount_type TEXT,
discount_value DECIMAL,
expiry_date TIMESTAMP,
usage_limit INT,
usage_count INT DEFAULT 0,
status ENUM('active','used','expired'),
created_at TIMESTAMP
);campaigns Table
CREATE TABLE campaigns (
id UUID PRIMARY KEY,
name TEXT,
start_date TIMESTAMP,
end_date TIMESTAMP,
target_segment TEXT,
delivery_channel TEXT,
created_by TEXT
);FlowsFlows
- Coupon Issuance
- CRM signals a new campaign
- Campaign Engine selects target users
- Coupon Generator issues unique codes and writes to the store
- Notification Service delivers coupons
- Redemption
- User submits code at checkout
- Redemption API checks:
- Existence and active status
- Ownership (for personalized coupons)
- Expiry and usage limit
- On success: apply discount, update usage count
- CRM Feedback Loop
- Redemption events streamed back to CRM
- CRM updates user lifecycle and may trigger follow-up campaigns
Scale ConsiderationsScale
- Coupon Generation
- Use UUIDv4 or HMAC(userID + campaignID + timestamp) for uniqueness
- Batch issuance via async workers (Kafka/RabbitMQ)
- Data Storage
- Shard by campaign or geographic region
- Cache active coupons in Redis for < 100 ms lookups
- Redemption API
- Stateless, horizontally scalable microservice
- JWT-based tokens to reduce DB hits
- Delivery Pipeline
- Asynchronous messaging with retries and dead-letter queues
- Analytics
- Stream all lifecycle events to a data lake (BigQuery / Snowflake)
- Build real-time dashboards for ROI and fraud monitoring
Deep Dive Topics: Approaches, Trade-OffsDeep dive
As we drill into the heart of the system, we’ll explore two key modules—Coupon Generation & Security, and Redemption API Design—each with multiple implementation strategies, along with their pros and cons.
1. Coupon Generation & Security
Approach A: UUIDv4 Codes
Generate a random UUIDv4 for every coupon (e.g. 550e8400-e29b-41d4-a716-446655440000).
- Pros
- Statistically unique without external coordination
- Easy to generate in parallel across worker pools
- No predictable pattern, hard to brute-force
- Cons
- Long code length can be cumbersome for users to type
- No embedded metadata (campaign, user) for quick lookups
- Requires DB lookup on redemption
Approach B: Structured Codes with Embedded Metadata
Compose codes as <CAMPAIGN>–<USERHASH>–<RAND> (e.g. BDAY2023–U1234–X9Y8Z).
- Pros
- Self-descriptive: campaign and user segment encoded
- Can validate prefix before hitting DB
- Shorter, more human-friendly than full UUID
- Cons
- Slightly more complex generator logic
- Metadata patterns may leak campaign details
- Collisions risk if random segment is too short
Approach C: Signed JWT Tokens
Issue each coupon as a signed JSON Web Token containing payload fields (userId, campaignId, expiry).
- Pros
- Stateless validation: signature check avoids DB hit
- Payload carries all metadata needed at redemption time
- Expiry is built into the token (the
expclaim) - Cons
- Token lengths can be 200+ characters, unfriendly for manual entry
- Requires robust key management for signing and rotation
- Revoking a single token can be tricky without a revocation store
2. Redemption API Design
Approach A: Fully Stateless JWT Validation
Client submits a signed token; API verifies signature and expiry, applies discount.
- Pros
- Extremely low latency (cryptographic check only)
- No persistent state required for validation
- Effortless horizontal scaling
- Cons
- Hard to track usage count or multi-use limits without state
- Revocation needs a secondary store lookup anyway
- No native protection against replay unless you embed nonce logic
Approach B: Database-Driven Idempotent Transactions
On redeem, perform a single DB transaction:
- SELECT ... FOR UPDATE
- Check status/limits
- UPDATE usage_count or mark used
- Pros
- Strong consistency guarantees (ACID)
- Easy to enforce single-use or multi-use limits
- Built-in audit trail in redemption records
- Cons
- Higher latency due to DB round-trips (lock contention)
- Scalability bottleneck if redemption volume spikes
- Requires careful sharding or partitioning for throughput
Approach C: Hybrid Caching (Redis) + Persistent Store
- Cache active coupon states in Redis
- On redeem: Lua script atomically checks the coupon and decrements its remaining uses
- Durably record the redemption before confirming to the user: write it to the DB (or a replicated Kafka topic that a consumer applies to the DB). Periodic sync alone isn't enough — if Redis fails before the sync, the decrement is lost and a single-use coupon becomes usable again.
- On restart or failover, rebuild Redis counts from the DB (usage limit − recorded redemptions).
- Pros
- Sub-10 ms lookups and decrements under heavy load
- Lua scripts provide atomic usage-count enforcement
- Fall-back to DB on cache miss or sync lag
- Cons
- Increased system complexity (cache invalidation, sync jobs)
- Risk of cache/DB divergence if not carefully orchestrated
- Redis single-node failure can block redemptions unless clustered
Next Steps
- Choose your generation strategy based on user experience vs. operational simplicity.
- Select an API pattern that meets your latency and consistency SLAs.
- Prototype and load-test each approach under realistic redemption rates.
By weighing these approaches, you’ll tailor a coupon system that balances performance, usability, and security at scale.
Tradeoffs & ExtensionsTrade-offs
7.1 Tradeoffs
- Idempotency vs. Performance: Checking for duplicate redemptions in a global distributed system can introduce significant latency. Using a fast Redis-based "first-capture" lock balances speed with strict single-use requirements.
- Code Length vs. Security: Short codes (6-8 chars) are great for UX but vulnerable to brute-force. We mitigate this with strict rate-limiting and account-level locks.
7.2 Extensions
- Dynamic Pricing: Integrate with a pricing engine to adjust coupon values based on real-time inventory or user behavior.
- Referral Tracking: Expand the data model to track which user "referred" another, rewarding both upon successful coupon redemption.
- Fraud Detection AI: Stream redemption events to a machine-learning model to detect anomalous patterns (e.g., thousands of attempts from one IP).
Wrap-UpWrap-up
The Coupon Distribution System is a critical piece of marketing infrastructure. By decoupling coupon generation from real-time redemption and leveraging a high-performance caching layer, we ensure that the system remains responsive even during the most aggressive sales events.