tinder-req.excalidraw
tinder-hld.excalidraw
tinder-deep-dive.excalidraw
1. High-Level Design
Profile Service
Responsible for:
- Store preferences
- Fetch candidate profiles
Swipe Service
Responsible for:
- Store swipes
- Detect matches
- Send notifications
Database Choice
Profile DB
Can use:
- PostgreSQL
- MySQL
Reason:
- Moderate traffic
- Relational data
Swipe DB
Heavy write workload. Options:
Cassandra
Pros:
- Optimized for writes
- Handles 100K+ writes/sec
PostgreSQL
Pros:
- Strong ACID guarantees
- Easier consistency handling
Trade-off:
- More nodes required
Match Detection
When User B swipes right:
1. Save swipe
2. Check inverse swipe
3. If exists:
Create Match
Notify both usersNotifications: iOS - Apple Push Notification Service (APNS) Android - Firebase Cloud Messaging (FCM)
Deep Dive #1 β Consistency
Problem
Both users swipe right simultaneously. A likes B B likes A
With eventual consistency:
A cannot see B's swipe
B cannot see A's swipeβ Match missed.
Solution 1
Accept eventual consistency. Run reconciliation job:
Hourly Job
β
Find missed matches
β
Send notificationsSimple but delayed notifications
Solution 2 (Redis)
Use Redis for match lookup.
Redis Key:
userA:userBWorkflow:
Write swipe
Check inverse swipe atomically
Create matchWhy?
- Redis is single-threaded.
- Supports atomic operations.
Redis provides atomic operations on a single-threaded execution model. When a swipe occurs, I can atomically check whether the inverse swipe already exists and create a match without race conditions.
Solution 3 (PostgreSQL)
Store both decisions in same row.
User1
User2
User1Decision
User2DecisionPostgres row locking + ACID guarantees consistency.
Instead of storing two separate swipe records, I store both usersβ decisions in a single row. PostgreSQL row-level locking and ACID transactions guarantee that concurrent updates are serialized, preventing missed matches.
Deep Dive #2 β Fast Stack Loading
How do we show Tinder recommendations in < 300ms when finding nearby compatible users is an expensive query?
BEFORE
SELECT *
FROM profiles
WHERE age BETWEEN 25 AND 30
AND gender = 'Female'
AND distance(currentLocation, profileLocation) < 20kmCurrent Problem
Query:
Find nearby users
Filter by preferencesExpensive because:
- Geospatial queries
- Latitude/Longitude search
Solution A: Precompute Stack
- This is the same idea used by:
Facebook FeedInstagram FeedLinkedIn Feed - Nightly job:
Cron Job -> Generate recommendations -> Store in Cache - User request:
Cache Hit β Return immediately - Pros: Very fast
- Cons: somthing has changed β if someone from your stack cache deleted their acc | change preferences | change in location
- How To Fix Cache Invalidation:
- Whenever something important changes:
Location changedPreference changedProfile deleted - Invalidate cache.
- But now Problem Still Exists that we donβt have the new stack cache.
- Whenever something important changes:
Solution B: Geospatial Index
Option 1: PostgreSQL + PostGIS
Supports: Location-based indexing
Now query becomes: Find users in nearby cells
instead of: Scan millions of rows
but β You still filter on age, gender, interests, etc.
Option 2: Elasticsearch
Elasticsearch Profile DB β CDC β Elasticsearch
Benefits:
- Fast geospatial search
- Fast filtering
Deep Dive #3 β Avoid Repeat Profiles
Simple Solution
Get recommendation stack
Get previous swipes from swipe DB
Remove duplicates
Return resultWorks but expensive.
Better Solution
Maintain cache:
User
β
Set of Swiped UsersExample:
User123
β {User5, User9, User21}Fast lookup.
10M users Γ 100 swipes/day
= 1B swipes/day
1B swipes Γ 100 bytes = 100 GB/day
100 GB/day Γ 365 days
= 36,500 GB
= 36.5 TB Storage Per YearBloom Filter Optimization
Why? Storage becomes huge:
10M users
100 swipes/day
365 days
β 36.5 TBUse Bloom Filter:
- It can say:
- β Β Definitely NOT present
- β οΈΒ Possibly presentΒ (may be wrong)
Before hitting DB: - Check Bloom Filter - If NOT present β skip DB call to check swipesπ - If maybe present β query DB