Ứng dụng hẹn hò quẹt thẻ
Hiểu đề
Yêu cầu chức năng
- Người dùng có thể tạo hồ sơ với ảnh và đặt tiêu chí (độ tuổi, giới tính, bán kính tìm)
- Người dùng có thể xem một chồng (stack) hồ sơ ở gần, phù hợp tiêu chí
- Người dùng có thể quẹt phải (thích) hoặc quẹt trái (bỏ qua) từng hồ sơ
- Khi hai người cùng quẹt phải nhau, cả hai nhận thông báo match ngay lập tức
Yêu cầu phi chức năng
- Quẹt p95 < 100 ms, cảm giác tức thì kể cả lúc 115.000 lượt quẹt/s
- Lấy stack p95 < 300 ms, hồ sơ trong stack cách tối đa bán kính người dùng chọn
- Không bỏ sót match: 2 người quẹt phải nhau thì 100% phải ra match, kể cả khi quẹt cùng một lúc
- Không hiện lại hồ sơ đã quẹt trong 90 ngày (chấp nhận bỏ sót khoảng 1% hồ sơ chưa xem)
- Độ sẵn sàng 99,9%; ảnh tải qua CDN p95 < 200 ms
Ngoài phạm vi: Nhắn tin sau khi match (xem bài Chat); Gói trả phí, Super Like, Boost; Mô hình ML xếp hạng hồ sơ (chỉ nói ở mức có một bước xếp hạng); Kiểm duyệt ảnh và xác minh danh tính.
Thực thể và API
| Method | Endpoint | Ghi chú |
|---|---|---|
| PUT | /v1/me/profile | Cập nhật hồ sơ và tiêu chí tìm. |
| POST | /v1/me/photos | Xin presigned URL để client upload ảnh thẳng lên S3. |
| PUT | /v1/me/location | Gửi {lat, lng} khi mở app hoặc khi di chuyển xa. Server làm tròn vị trí trước khi lưu. |
| GET | /v1/feed?limit=20 | Lấy 20 hồ sơ tiếp theo trong stack, kèm URL ảnh trên CDN. |
| POST | /v1/swipes | Body {targetId, direction}. Idempotent theo (swiper, target). Trả {matched, matchId}. |
| GET | /v1/matches?cursor= | Danh sách match, phân trang theo cursor. |
Thiết kế tổng thể
Đi lần lượt từng yêu cầu chức năng. Mục tiêu ở đây là chạy đúng, việc tối ưu để dành cho phần đào sâu.
1) Người dùng có thể tạo hồ sơ với ảnh
Ảnh là phần nặng nhất nên không đi qua API. Client xin presigned URL rồi upload thẳng lên S3, CDN phục vụ khi người khác xem.
- 1Client gọi POST /v1/me/photos, API trả presigned URL có hạn 5 phút
- 2Client upload ảnh lên S3, sau đó gọi PUT /v1/me/profile để lưu thứ tự ảnh
- 3Worker tạo các kích thước nhỏ (thumbnail, 640px) cho tải nhanh
- 4Hồ sơ và tiêu chí lưu ở Postgres, vị trí lưu ở cột geography có index
Nguồn: Grokking Modern System Design · ch.20 Blob Store ↗ · mục "High-level design" ; Grokking Modern System Design · ch.11 CDN ↗ · mục "Pull CDN" ; Chưa có nguồn trong thư viện: presigned URL là tính năng của nhà cung cấp object storage ; Chưa có nguồn trong thư viện: PostGIS, geohash, OpenSearch geo_point không có sách trong thư viện
2) Người dùng có thể xem stack hồ sơ ở gần
Tìm ứng viên cần lọc theo khoảng cách, tuổi, giới tính và loại người đã quẹt. Việc này tốn kém nên tính sẵn khoảng 100 ứng viên rồi phát dần cho client.
- 1Client gọi GET /v1/feed, API lấy 20 id đầu từ stack trong Redis
- 2API lấy hồ sơ rút gọn của 20 id (cache), trả kèm URL ảnh CDN
- 3Khi stack còn dưới 20 id, API phát sự kiện yêu cầu dựng thêm
- 4Stack builder truy vấn geo index theo bán kính và tiêu chí, loại id có trong Bloom filter, xếp hạng, ghi 100 id mới vào Redis
Nguồn: Grokking Modern System Design · ch.29 Design a Proximity Service (Yelp) ↗ · Design Considerations of Yelp · mục "Search using a QuadTree" ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · Background of Distributed Cache · mục "Writing policies" (write-around: ghi vào cache sau lần trượt) ; Seven Databases in Seven Weeks ↗ (.zip) · ch.3 HBase · Day 2: Working with Big Data · mục "Compression and Bloom Filters" (How Do Bloom Filters Work?, tr. 110–111) ; Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · mục "Motivation" (Improved performance, Granular scalability)
3) Người dùng có thể quẹt trái hoặc phải
Quẹt là thao tác nhiều nhất hệ thống, khoảng 100 lần mỗi người mỗi ngày. Ghi thẳng vào store chịu ghi tốt và trả lời ngay, các việc phụ đi qua queue.
- 1Client gửi POST /v1/swipes với targetId và direction
- 2Swipe API ghi Swipe (swiper_id, target_id) và thêm target_id vào Bloom filter của người quẹt
- 3Nếu là quẹt phải thì cập nhật LikePair của cặp (xem luồng match)
- 4Phát sự kiện swipe lên queue cho thống kê và mô hình xếp hạng
Nguồn: Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · mục "Motivation" (Improved performance, Granular scalability) ; Grokking Modern System Design · ch.10 Key-value Store ↗ · Design of a Key-value Store (kho khóa-giá trị chịu ghi lớn) ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · mục "Cache client" (Note: Bloom filter kiểm tra phần tử chắc chắn không có)
4) Hai người cùng thích nhau thì được báo match
Hai lượt thích của một cặp ghi vào cùng một bản ghi LikePair, nên DB tự xếp chúng tuần tự. Lượt ghi nào thấy cả hai cờ đều true thì tạo match.
- 1Tính low = min(A, B), high = max(A, B)
- 2Cập nhật nguyên tử: đặt cờ của người quẹt = true, trả về bản ghi sau khi cập nhật
- 3Nếu cả low_liked và high_liked đều true thì ghi Match với match_id = hash(low, high), ghi trùng cũng không sao
- 4Trả matched=true cho người vừa quẹt và gửi push cho người kia qua FCM/APNs
Nguồn: Redis in Action ↗ · ch.11 · §11.1 Adding functionality without writing C (tr. 250) ; Redis in Action ↗ · ch.4 · §4.4 Redis transactions (tr. 78) ; Chưa có nguồn trong thư viện: cập nhật nguyên tử LikePair low/high và match_id tất định là thiết kế của khoá học
Hạ tầng theo quy mô
Cùng một sản phẩm, ba mức người dùng. Chọn từng mức để xem sơ đồ, phép tính và cấu hình server thay đổi thế nào.
App hẹn hò cho một thành phố. Hai instance API và một Postgres có PostGIS làm hết: tìm người gần bằng index không gian, ghi lượt quẹt và LikePair ngay trong Postgres.
Ước lượng
- Quẹt: 10.000 × 100 lượt / 86.400 ≈ 11,6 lượt/s, buổi tối ×10 ≈ 116 lượt/s
- Request khác: 10.000 × 30 / 86.400 ≈ 3,5 req/s, ×10 ≈ 35 req/s
- API: ceil((116 + 35) / (1.000 × 0,6)) = ceil(0,25) = 1 → lấy tối thiểu 2 instance
- Lượt quẹt lưu: 10.000 × 100 = 1.000.000 lượt/ngày × 30 B × 365 ≈ 11 GB/năm (chưa tính index)
- Ảnh: 50.000 người × 5 ảnh × 300 KB = 75 GB trên S3
- Match: 1.000.000 lượt × 1,5% ≈ 15.000 match/ngày
Nguồn con số: Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations ↗ · mục "Examples of Resource Estimation" (Number of servers required, Storage requirements, Bandwidth requirements) ; Quy tắc ước lượng của khoá học · /hoc/chon-server (1.000 req/s mỗi máy 2 vCPU, chạy tối đa 60% CPU, Redis ×1,3) ; Chưa có nguồn trong thư viện: 100 lượt quẹt/người/ngày và tỷ lệ match 1,5% là giả định của khoá học
Bảng hạ tầng
| Thành phần | Chọn | Số lượng | Cấu hình | Vì sao |
|---|---|---|---|---|
| CDN | Cloudflare / CloudFront | 1 | Cache ảnh hồ sơ | Ảnh chiếm gần hết băng thông, CDN gánh thay origin. |
| API | Node.js (NestJS), Docker trên Cloud Run | 2 | 2 vCPU · 4 GB RAM | 150 req/s chỉ cần 1 instance, chạy 2 để dự phòng. |
| Database | PostgreSQL managed + PostGIS | 1 | 2 vCPU · 8 GB · 100 GB SSD | 50.000 hồ sơ, 116 ghi/s; index GiST cho tìm theo bán kính, ON CONFLICT cho LikePair. |
| Lưu ảnh | S3 / GCS | 1 | 75 GB, presigned upload | Không để ảnh trong DB; client upload thẳng. |
| Push | FCM / APNs | 1 | Dịch vụ bên ngoài | 15.000 match/ngày, gửi trực tiếp từ API là đủ. |
- Điểm xuất phát: một API, một Postgres có PostGIS, ảnh trên S3 + CDN
- Stack tính ngay khi mở app bằng truy vấn geo, loại người đã quẹt bằng NOT EXISTS
- LikePair và Match nằm chung Postgres, nguyên tử nhờ ON CONFLICT
Bảng swipes là thứ lớn nhanh nhất: lên 1 triệu DAU là 100 triệu hàng mỗi ngày, và truy vấn NOT EXISTS khi dựng stack chậm dần theo lịch sử quẹt.
Cấu hình và chi phí là ước lượng để học và trình bày trong phỏng vấn. Xem cách tính ở bài Chọn server.
So sánh nhanh ba mức
| Thành phần | Mức 1 | Mức 2 | Mức 3 |
|---|---|---|---|
| Người dùng | 10.000 DAU, 50.000 người đăng ký, một thành phố | 1 triệu DAU, 5 triệu người đăng ký, toàn quốc | 50 triệu DAU, 100 triệu người đăng ký, 3 region |
| Chi phí | ≈ 60–150 USD/tháng (ước lượng) | ≈ 4.000–10.000 USD/tháng (ước lượng, chưa tính băng thông ảnh) | ≈ 120.000–300.000 USD/tháng (ước lượng, chưa tính băng thông CDN cho ảnh) |
| CDN | Cloudflare / CloudFront 1 × Cache ảnh hồ sơ | Chưa cần | Chưa cần |
| API | Node.js (NestJS), Docker trên Cloud Run 2 × 2 vCPU · 4 GB RAM | Node.js, container autoscale 2–13 (autoscale) × 2 vCPU · 4 GB RAM | Chưa cần |
| Database | PostgreSQL managed + PostGIS 1 × 2 vCPU · 8 GB · 100 GB SSD | PostgreSQL + PostGIS (HA) 1 primary + 1 read replica × 4 vCPU · 16 GB · 200 GB SSD | Chưa cần |
| Lưu ảnh | S3 / GCS 1 × 75 GB, presigned upload | Chưa cần | Chưa cần |
| Push | FCM / APNs 1 × Dịch vụ bên ngoài | Chưa cần | Chưa cần |
| CDN + S3 | Chưa cần | CloudFront + S3 1 × 5 triệu × 5 ảnh × 300 KB = 7,5 TB | CloudFront + S3 theo region 3 region × 100 triệu × 5 ảnh × 300 KB = 150 TB |
| Swipe store | Chưa cần | DynamoDB on-demand 1 bảng swipes + 1 bảng like_pairs × Đỉnh ≈ 5.800 ghi/s, ≈ 520 GB | ScyllaDB, RF 3 trong mỗi region 21 node × 32 vCPU · 256 GB · 7,5 TB NVMe (dùng 50%) |
| Redis | Chưa cần | ElastiCache + RedisBloom 1 primary + 1 replica × 2 vCPU · 13 GB | Chưa cần |
| Queue | Chưa cần | Amazon SQS 1 × Managed, có dead-letter queue | Chưa cần |
| Worker | Chưa cần | Node.js consumer (stack builder + push) 2–4 × 1 vCPU · 2 GB RAM | Stack builder + Notifier trên Kubernetes ≈ 30 pod × 2 vCPU · 4 GB mỗi pod |
| Swipe service | Chưa cần | Chưa cần | Node.js trên Kubernetes, 3 region 193 pod (HPA) × 2 vCPU · 4 GB mỗi pod |
| Profile/Feed API | Chưa cần | Chưa cần | Node.js trên Kubernetes 58 pod (HPA) × 2 vCPU · 4 GB mỗi pod |
| Redis Cluster | Chưa cần | Chưa cần | ElastiCache Redis Cluster + RedisBloom 10 shard × (1 primary + 1 replica) × 64 GB mỗi node |
| Geo index | Chưa cần | Chưa cần | OpenSearch geo_point, shard theo vùng 6 node mỗi region × 8 vCPU · 32 GB mỗi node |
| Database hồ sơ | Chưa cần | Chưa cần | Aurora PostgreSQL, mỗi region một cụm 1 primary + 2 replica mỗi region × 8 vCPU · 64 GB |
| Kafka | Chưa cần | Chưa cần | MSK / Confluent 6 broker (2 mỗi region) × Topic swipes 24 partition, RF 3 |
Đào sâu
Mỗi câu hỏi đi từ cách đơn giản đến cách người phỏng vấn mong đợi ở cấp senior.
1) Làm sao tạo stack ứng viên ở gần nhanh khi có hàng chục triệu hồ sơ?
SELECT ... WHERE haversine(lat, lng, :lat, :lng) < :r. Hàm tính trên từng hàng nên không dùng được index, phải quét cả bảng.
Đánh đổi: Chạy được với vài nghìn hồ sơ, với hàng triệu hồ sơ thì mỗi lần mở app mất vài giây và DB nóng máy.
Nguồn: Grokking Modern System Design · ch.29 Design a Proximity Service (Yelp) ↗ · Design Considerations of Yelp · mục "Searching" (quét toàn bộ để tìm gần chậm) ; Chưa có nguồn trong thư viện: hàm Haversine không có trong sách
Index không gian chia bản đồ thành ô, truy vấn chỉ đọc các ô quanh người dùng rồi lọc tuổi, giới tính. Cách chia ô và tìm ô lân cận giống phần QuadTree ở bài Yelp trong Grokking ch.29.
Đánh đổi: Nhanh hơn nhiều nhưng mỗi lần mở app vẫn là một truy vấn geo cộng lọc người đã quẹt, ở thành phố đông có thể vài trăm ms và dồn tải vào DB giờ cao điểm.
Nguồn: Grokking Modern System Design · ch.29 Design a Proximity Service (Yelp) ↗ · Design Considerations of Yelp · mục "Search using a QuadTree" ; MongoDB: The Definitive Guide 2e ↗ (.zip) · ch.6 Special Index and Collection Types · mục "Geospatial Indexing" (tr. 120) ; Chưa có nguồn trong thư viện: PostGIS, geohash, OpenSearch geo_point không có sách trong thư viện
Hồ sơ đang hoạt động được đánh index trong OpenSearch với geo_point, chia shard theo vùng địa lý. Stack builder chạy nền: truy vấn theo bán kính và tiêu chí, loại id có trong Bloom filter, xếp hạng (gần, hoạt động gần đây, mức hợp), ghi 100 id vào Redis. API chỉ việc lấy 20 id từ list nên p95 vài ms. Stack được dựng lại khi còn dưới 20 id hoặc khi người dùng đổi vị trí hay tiêu chí.
Đánh đổi: Stack có thể cũ vài phút (người kia vừa tắt tài khoản hoặc đổi vị trí), nên lúc phát ra vẫn kiểm tra nhanh trạng thái hồ sơ; thêm một cụm search và worker phải vận hành.
Nguồn: Grokking Modern System Design · ch.29 Design a Proximity Service (Yelp) ↗ · Design Considerations of Yelp · mục "Search using segments", "Data partitioning" ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · Background of Distributed Cache · mục "Writing policies" (write-around: ghi vào cache sau lần trượt) ; Seven Databases in Seven Weeks ↗ (.zip) · ch.3 HBase · Day 2: Working with Big Data · mục "Compression and Bloom Filters" (How Do Bloom Filters Work?, tr. 110–111) ; Chưa có nguồn trong thư viện: OpenSearch geo_point và mô hình xếp hạng stack là thiết kế của khoá học
2) Ghi hàng trăm nghìn lượt quẹt mỗi giây thế nào?
Đơn giản và đúng ở quy mô nhỏ. Ở 5 tỷ lượt/ngày, bảng tăng 150 GB mỗi ngày, index B-tree lớn hơn RAM và một primary không nhận nổi hơn 100.000 ghi/s.
Đánh đổi: Tốt cho mức khởi đầu, sẽ thành nút thắt đầu tiên khi tăng trưởng.
Nguồn: Grokking Modern System Design · ch.4 Non-functional System Characteristics ↗ · Scalability · mục "Vertical scalability--scaling up" và "Horizontal scalability--scaling out" ; Grokking Modern System Design · ch.9 Databases ↗ · Data Partitioning · mục "Why do we partition data?" ; Quy tắc ước lượng của khoá học · /hoc/chon-server (1.000 req/s mỗi máy 2 vCPU, chạy tối đa 60% CPU, Redis ×1,3)
API ghi vào queue và trả lời ngay, worker gom 500 lượt một lần INSERT vào bảng chia partition theo hash(swiper_id). Số transaction giảm hàng trăm lần.
Đánh đổi: Lượt quẹt nằm trong queue vài giây nên kiểm tra match và Bloom filter phải dựa vào chỗ khác; vẫn phải tự chia shard khi một node đầy.
Nguồn: Grokking Modern System Design · ch.17 Distributed Messaging Queue ↗ · mục "Motivation" (Improved performance, Granular scalability) · mục "Improved performance" ; Grokking Modern System Design · ch.9 Databases ↗ · Data Partitioning · mục "Sharding" (Key-range based sharding, Hash-based sharding, Consistent hashing) (hash-based sharding)
LSM-tree ghi tuần tự nên mỗi node nhận hàng chục nghìn ghi/s. Partition key swiper_id, clustering key target_id: kiểm tra 'đã quẹt người này chưa' là một lần đọc theo khóa. Lượt quẹt trái đặt TTL 90 ngày để dữ liệu không phình mãi. Sự kiện quẹt đi qua Kafka cho thống kê và mô hình xếp hạng.
Đánh đổi: Không có join hay transaction nhiều hàng; truy vấn nào cũng phải thiết kế bảng theo đúng mẫu truy cập từ trước.
Nguồn: Grokking Modern System Design · ch.10 Key-value Store ↗ · mục "Consistent hashing" và "Data replication" ; Grokking Modern System Design · ch.9 Databases ↗ · Data Partitioning · mục "Sharding" (Key-range based sharding, Hash-based sharding, Consistent hashing) ; Chưa có nguồn trong thư viện: LSM-tree và TTL của ScyllaDB/Cassandra/DynamoDB (thư viện không có sách về các sản phẩm này)
3) A và B quẹt phải nhau gần như cùng lúc. Làm sao chắc chắn ra đúng một match?
Kiểm tra rồi mới làm (check-then-act) trong code ứng dụng. Nếu hai request chạy song song, cả hai cùng đọc thấy 'chưa', cùng ghi, và không ai tạo match.
Đánh đổi: Lỗi chỉ xảy ra khi hai người quẹt sát nhau nên khó tái hiện, nhưng ở 34.000 lượt thích/s thì ngày nào cũng gặp.
Nguồn: Redis in Action ↗ · ch.4 · §4.4 Redis transactions (tr. 78, vì sao đọc-rồi-ghi cần giao dịch) ; Grokking Modern System Design · ch.19 Rate Limiter ↗ · mục "Race condition"
Một script ghi 'A thích B' và đọc 'B thích A' trong cùng một lệnh. Redis chạy lệnh tuần tự nên script là nguyên tử (Redis in Action ch.11).
Đánh đổi: Redis không phải nguồn sự thật: failover có thể mất lượt thích vừa ghi, và giữ mọi lượt thích trong RAM rất tốn.
Nguồn: Redis in Action ↗ · ch.11 · §11.1 Adding functionality without writing C (tr. 250) ; Redis in Action ↗ · ch.11 · §11.3 Doing away with WATCH/MULTI/EXEC (tr. 260) ; Grokking Modern System Design · ch.9 Databases ↗ · Data Replication · mục "Single leader/primary-secondary replication" và "Synchronous versus asynchronous replication" (failover bất đồng bộ có thể mất bản ghi cuối)
Cả hai lượt thích ghi vào cùng một bản ghi, nên DB tuần tự hóa chúng. Postgres: INSERT ... ON CONFLICT DO UPDATE SET high_liked = true RETURNING low_liked, high_liked. DynamoDB: UpdateItem với ReturnValues=ALL_NEW. ScyllaDB: lightweight transaction (Paxos) chỉ cho lượt quẹt phải. Ai thấy cả hai cờ true thì tạo match với match_id = hash(low, high); nếu cả hai cùng thấy thì ghi trùng cùng một id, không sinh hai match.
Đánh đổi: LWT tốn khoảng 4 vòng mạng nên chỉ dùng cho quẹt phải (30% lượt quẹt); push báo match phải chống gửi trùng theo match_id.
Nguồn: Chưa có nguồn trong thư viện: LikePair theo khóa (low, high), lightweight transaction của ScyllaDB và UpdateItem của DynamoDB là thiết kế/sản phẩm không có sách trong thư viện ; Grokking Modern System Design · ch.9 Databases ↗ · mục "Hash-based sharding" (khóa quyết định cùng một nơi ghi)
4) Làm sao không hiện lại hồ sơ người dùng đã quẹt?
Mỗi lần dựng stack lại đọc toàn bộ lịch sử quẹt. Người quẹt 100 lần/ngày sau 3 tháng có 9.000 lượt, truy vấn ngày càng chậm.
Đánh đổi: Chính xác tuyệt đối nhưng chi phí tăng theo tuổi tài khoản.
Nguồn: Grokking Modern System Design · ch.29 Design a Proximity Service (Yelp) ↗ · Design Considerations of Yelp · mục "Improve data fetching" (tránh đọc toàn bộ dữ liệu mỗi lần) ; Chưa có nguồn trong thư viện: truy vấn NOT IN là ví dụ minh họa của khoá học
SISMEMBER cho mỗi ứng viên là O(1). Nhưng một phần tử trong SET tốn khoảng 50 B, 5.000 id là 250 KB mỗi người, 50 triệu người là khoảng 12,5 TB RAM.
Đánh đổi: Nhanh và chính xác nhưng quá đắt ở quy mô lớn.
Nguồn: Redis in Action ↗ · ch.3 · §3.3 Sets (tr. 46) ; Quy tắc ước lượng của khoá học · /hoc/chon-server (1.000 req/s mỗi máy 2 vCPU, chạy tối đa 60% CPU, Redis ×1,3) (Redis ×1,3)
Bloom filter trả lời 'chắc chắn chưa thấy' hoặc 'có thể đã thấy'. Với tỉ lệ dương tính giả 1% cần khoảng 9,6 bit mỗi id, 5.000 id chỉ khoảng 6 KB. Sai thì chỉ bỏ qua nhầm khoảng 1% hồ sơ chưa xem, không bao giờ hiện lại hồ sơ đã quẹt. Cơ chế này cũng là cách HBase dùng Bloom filter để tránh đọc đĩa vô ích (Seven Databases in Seven Weeks, phần HBase).
Đánh đổi: Không xóa được phần tử, nên để quẹt trái hết hạn sau 90 ngày phải xoay vòng 2 filter theo thời gian; filter mất thì dựng lại từ ScyllaDB.
Nguồn: Seven Databases in Seven Weeks ↗ (.zip) · ch.3 HBase · Day 2: Working with Big Data · mục "Compression and Bloom Filters" (How Do Bloom Filters Work?, tr. 110–111) (không có âm tính giả, dương tính giả tăng khi thêm nhiều phần tử, dùng bộ nhớ cố định) ; Grokking Modern System Design · ch.16 Distributed Cache ↗ · mục "Cache client" (Note: Bloom filter kiểm tra phần tử chắc chắn không có) ; Chưa có nguồn trong thư viện: 9,6 bit mỗi id cho tỉ lệ dương tính giả 1% và việc xoay vòng 2 filter là ước lượng/thiết kế của khoá học
Trong phỏng vấn
- “Đây là hệ thống ghi nhiều: mỗi người quẹt khoảng 100 lần/ngày, nên tôi chọn store tối ưu cho ghi với khóa là swiper_id.”
- “Tôi tính sẵn stack ứng viên ở nền và phát từ Redis, để lấy feed chỉ là một lần đọc list.”
- “Hai lượt thích của một cặp ghi vào cùng một bản ghi (low_id, high_id), DB tuần tự hóa nên không bao giờ mất match.”
- “Bloom filter loại người đã quẹt với khoảng 1,2 B mỗi id; sai thì chỉ bỏ qua nhầm hồ sơ, không bao giờ hiện lại.”
- Kiểm tra 'B đã thích A chưa' rồi mới ghi, bị race khi hai người quẹt cùng lúc.
- Tính khoảng cách cho mọi hồ sơ trong mỗi lần mở app.
- Lưu toàn bộ id đã quẹt dạng SET trong RAM, tốn hàng TB.
- Cho ảnh đi qua API hoặc lưu ảnh trong DB.
Đọc thêm
Sách trong thư viện của bạn. Số chương có thể lệch nhẹ so với bản PDF.