0

🗞️ Trận 4: Thiết kế hệ thống News Feed - Facebook đã giải quyết bài toán hàng tỷ bài viết mỗi ngày như thế nào?

"Nếu bạn là kỹ sư tại Facebook, hãy thiết kế hệ thống News Feed phục vụ 500 triệu người dùng hoạt động mỗi ngày."

Đây là một trong những câu hỏi kinh điển nhất trong các buổi phỏng vấn System Design.

Thoạt nghe, bài toán có vẻ khá đơn giản. Một người đăng bài → Bạn bè của họ nhìn thấy bài viết.

Chỉ vậy thôi. Nhưng khi quy mô tăng lên hàng trăm triệu người dùng, mọi thứ lập tức trở nên thú vị hơn rất nhiều.

Hãy thử tưởng tượng...

Mỗi ngày có hàng trăm triệu người mở Facebook ngay sau khi thức dậy. Có người chỉ lướt vài phút. Có người kéo News Feed liên tục hàng giờ. Mỗi giây lại có hàng chục nghìn bài viết mới được đăng.

Nếu hệ thống xử lý không tốt, chỉ cần một người nổi tiếng đăng một bài viết cũng có thể khiến hàng triệu máy chủ bận rộn trong cùng một thời điểm.

Đó chính là lý do News Feed luôn được xem là một trong những bài toán khó nhất của các mạng xã hội.

Trong bài viết này, chúng ta sẽ không bắt đầu bằng sơ đồ kiến trúc.

Thay vào đó, hãy cùng bước vào một buổi phỏng vấn System Design theo đúng phong cách của Alex Xu.

Chúng ta sẽ lần lượt đi qua năm bước:

  • Hiểu yêu cầu
  • Ước lượng quy mô
  • Thiết kế kiến trúc tổng quan
  • Phân tích các thành phần quan trọng
  • Thảo luận các trade-off và khả năng mở rộng

Điểm quan trọng nhất là không nhảy ngay vào giải pháp.

Một System Designer giỏi luôn dành phần lớn thời gian để hiểu bài toán trước khi nghĩ đến cách giải.


Bước 1. Hiểu yêu cầu

Một sai lầm rất phổ biến của người mới học System Design là vừa nghe xong đề bài đã bắt đầu nói về Kafka, Redis hay Cassandra.

Trong thực tế, đó lại là điều người phỏng vấn không muốn thấy.

Nếu chưa hiểu rõ yêu cầu, rất có thể bạn sẽ thiết kế một hệ thống cực kỳ phức tạp... để giải quyết một vấn đề mà khách hàng không hề cần.

Alex Xu luôn nhấn mạnh rằng:

Đừng vội thiết kế. Hãy hỏi.

Giả sử người phỏng vấn chỉ đưa cho chúng ta một câu duy nhất:

Design Facebook News Feed.

Ngay lúc này, thay vì mở bảng trắng để vẽ kiến trúc, chúng ta sẽ bắt đầu bằng việc đặt câu hỏi.


Câu hỏi 1: News Feed bao gồm những gì?

  • Liệu bảng tin chỉ hiển thị văn bản?
  • Hay còn có hình ảnh, video, livestream, Reels...?
  • Nếu phải hỗ trợ video 4K ngay từ đầu, toàn bộ thiết kế lưu trữ sẽ khác hoàn toàn so với chỉ lưu văn bản.

Giả sử người phỏng vấn trả lời:

Hệ thống cần hỗ trợ:

  • Văn bản
  • Hình ảnh
  • Video

Như vậy, ngoài cơ sở dữ liệu lưu metadata của bài viết, chúng ta cũng sẽ cần một hệ thống lưu trữ file đa phương tiện ở phía sau.


Câu hỏi 2: Người dùng có thể làm gì?

Một News Feed thực tế có rất nhiều chức năng:

  • Like
  • Comment
  • Share
  • Tag
  • Story
  • Reels
  • Livestream
  • Reaction
  • Suggested Posts
  • AI Recommendation

Nếu thiết kế tất cả trong một buổi phỏng vấn kéo dài 45 phút thì gần như bất khả thi.

Vì vậy, chúng ta cần thu hẹp phạm vi.

Giả sử người phỏng vấn trả lời:

Hệ thống chỉ cần hai chức năng:

  1. Người dùng đăng bài viết mới.
  2. Người dùng xem News Feed của mình.

Điều này giúp bài toán trở nên rõ ràng hơn rất nhiều.


Câu hỏi 3: News Feed được sắp xếp như thế nào?

Đây là một câu hỏi rất quan trọng.

Facebook ngày nay sử dụng hàng nghìn tín hiệu để quyết định bài viết nào xuất hiện đầu tiên:

  • Bạn thường tương tác với ai?
  • Bài viết có bao nhiêu lượt Like?
  • Có bao nhiêu bình luận?
  • Thời gian xem video?
  • Machine Learning Ranking
  • AI Recommendation

Nếu phải xây dựng toàn bộ hệ thống AI đó thì đây sẽ là một bài toán hoàn toàn khác.

May mắn là người phỏng vấn trả lời:

Hãy sắp xếp theo thời gian.

Nói cách khác:

Bài mới nhất luôn xuất hiện ở đầu News Feed.

Một yêu cầu đơn giản hơn rất nhiều.


Câu hỏi 4: Hệ thống phục vụ bao nhiêu người dùng?

Đây là câu hỏi quyết định gần như toàn bộ kiến trúc.

  • Nếu ứng dụng chỉ có 10.000 người dùng, một máy chủ duy nhất có thể đã đủ.
  • Nếu có 500 triệu người dùng mỗi ngày, mọi quyết định đều phải tính đến khả năng mở rộng.

Giả sử người phỏng vấn đưa ra con số:

500 triệu Daily Active Users (DAU).

Ngay lúc này chúng ta biết rằng:

  • Một database sẽ không đủ.
  • Một cache sẽ không đủ.
  • Một server sẽ không đủ.

Chúng ta sẽ phải nghĩ đến việc phân tán dữ liệu ngay từ đầu.


Tóm tắt yêu cầu

Sau vài phút trao đổi, bài toán đã trở nên cụ thể hơn rất nhiều.

Functional Requirements

Hệ thống cần hỗ trợ:

  • Người dùng đăng bài viết.
  • Người dùng xem News Feed.
  • Bài viết gồm:
    • Text
    • Image
    • Video
  • Feed được sắp xếp theo thời gian giảm dần.

Non-functional Requirements

Ngoài chức năng chính, hệ thống còn phải đáp ứng:

  • Độ trễ thấp khi mở News Feed.
  • Khả năng mở rộng đến hàng trăm triệu người dùng.
  • Tính sẵn sàng cao (High Availability).
  • Hệ thống chịu được lưu lượng truy cập tăng đột biến.

Đây là những yêu cầu sẽ ảnh hưởng trực tiếp đến mọi quyết định thiết kế ở các phần sau.


Bước 2. Ước lượng quy mô

Sau khi hiểu bài toán, Alex Xu luôn dành vài phút để thực hiện một bước mà rất nhiều người mới thường bỏ qua:

Ước lượng quy mô hệ thống.

Mục tiêu của bước này không phải để tính toán chính xác từng con số.

Thay vào đó, nó giúp chúng ta hình dung được quy mô thật sự của hệ thống và biết đâu là "điểm nóng" cần tập trung tối ưu.

Hãy bắt đầu với giả định từ đề bài.

500 triệu người dùng hoạt động mỗi ngày (500M DAU).

Con số này nghe có vẻ lớn, nhưng hãy thử chuyển nó thành những thống kê gần gũi hơn.


Có bao nhiêu người đang online cùng lúc?

Không phải 500 triệu người đều mở Facebook tại cùng một thời điểm.

Trong các hệ thống lớn, một giả định phổ biến là khoảng 10% người dùng đang hoạt động đồng thời.

Như vậy:

500 triệu × 10% ≈ 50 triệu người online cùng lúc

Điều này có nghĩa là bất kỳ thời điểm nào, hệ thống cũng phải phục vụ hàng chục triệu người đang kéo News Feed liên tục.


Có bao nhiêu bài viết mới mỗi ngày?

Giả sử trung bình mỗi người dùng đăng:

  • 2 bài viết/ngày

Khi đó:

500M × 2 ≈ 1 tỷ bài viết/ngày

Hay nói cách khác:

≈ 11.500 bài viết mới mỗi giây

Đây mới chỉ là thao tác đăng bài, chưa tính Like, Comment, Share hay xem bài viết.


Có bao nhiêu lượt đọc News Feed?

Đây mới là phần tạo ra áp lực lớn nhất.

Hầu hết người dùng chỉ đăng vài bài mỗi ngày. Nhưng họ có thể mở ứng dụng hàng chục lần.

Giả sử:

  • mỗi người mở Facebook 10 lần/ngày.

Khi đó:

500M × 10 ≈ 5 tỷ lượt đọc Feed mỗi ngày

Tương đương khoảng:

≈ 58.000 request đọc Feed mỗi giây

Và vào giờ cao điểm, con số này có thể cao gấp nhiều lần.

Ngay lập tức, chúng ta nhận ra một điều rất thú vị:

Đọc nhiều hơn ghi rất nhiều.

Đây sẽ là một trong những lý do quan trọng khiến kiến trúc của News Feed được tối ưu chủ yếu cho đọc, chứ không phải ghi.

Bước 3. Thiết kế kiến trúc tổng quan

Ở phần trước, chúng ta đã làm hai việc quan trọng nhất:

  • Hiểu chính xác bài toán.
  • Ước lượng quy mô của hệ thống.

Lúc này, chúng ta đã biết rằng hệ thống phải phục vụ:

  • 500 triệu DAU
  • Hàng tỷ bài viết mỗi ngày
  • Hàng tỷ lượt đọc News Feed

Nếu bây giờ hỏi:

"Kiến trúc của hệ thống sẽ trông như thế nào?"

Rất nhiều người mới học sẽ bắt đầu vẽ ngay:

User
   ↓
Load Balancer
   ↓
API Server
   ↓
Database

Sơ đồ này đúng nhưng chỉ đúng với một ứng dụng CRUD thông thường.

Với News Feed, mọi thứ phức tạp hơn rất nhiều.

Bởi vì thực tế, đăng bàiđọc bảng tinhai bài toán hoàn toàn khác nhau.

Đó cũng là ý tưởng đầu tiên mà Alex Xu muốn người đọc nhận ra.


Đừng nghĩ News Feed là một hệ thống

Hãy nghĩ nó là hai hệ thống độc lập.

Đây là điểm mà rất nhiều ứng viên bỏ lỡ trong phỏng vấn.

Thoạt nhìn, News Feed chỉ là:

Người A đăng bài → Người B nhìn thấy.

Nghe giống như một luồng xử lý duy nhất.

Nhưng nếu quan sát kỹ hơn, ta sẽ thấy có hai hành động hoàn toàn khác nhau.

1. Hành động thứ nhất

Người dùng nhấn nút Đăng bài.

Hệ thống cần:

  • lưu bài viết
  • lưu hình ảnh
  • cập nhật database
  • chuẩn bị để bạn bè có thể nhìn thấy bài viết

Đây là luồng ghi dữ liệu (Write Path).


2. Hành động thứ hai

Một người khác mở Facebook. Họ muốn thấy News Feed ngay lập tức.

Lúc này hệ thống cần:

  • lấy danh sách bài viết
  • lấy thông tin tác giả
  • lấy số lượt Like
  • lấy bình luận
  • ghép tất cả lại
  • trả về trong vài trăm mili giây

Đây là luồng đọc dữ liệu (Read Path).


Hai luồng này có yêu cầu hoàn toàn trái ngược nhau.

Feed Publishing Feed Reading
Ghi dữ liệu Đọc dữ liệu
Ít request hơn Nhiều request hơn rất nhiều
Có thể xử lý bất đồng bộ Phải phản hồi gần như ngay lập tức

Đó là lý do Facebook không xây dựng một hệ thống duy nhất.

Họ xây dựng hai pipeline riêng biệt.


Kiến trúc tổng quan

Toàn bộ hệ thống có thể hình dung như sau:

image.png

Nhìn vào sơ đồ trên, ta thấy:

  • Bên trái chịu trách nhiệm tạo dữ liệu.
  • Bên phải chịu trách nhiệm đọc dữ liệu.

Hai hệ thống gần như hoạt động độc lập.

Đây là một nguyên tắc rất phổ biến trong các hệ thống quy mô lớn:

Tách Read Path và Write Path.


Luồng 1 — Feed Publishing (Đăng bài)

Hãy tưởng tượng bạn vừa chụp một bức ảnh. Bạn mở Facebook. Viết vài dòng trạng thái.

Sau đó nhấn:

Post

Điều gì xảy ra phía sau?

Rất nhiều người nghĩ rằng:

Server lưu bài viết vào database.

Xong.

Thực tế lại không đơn giản như vậy.


Bước 1. Client gửi request

Ứng dụng gửi request:

POST /v1/posts

bao gồm:

  • nội dung
  • ảnh
  • video
  • thông tin người đăng

Bước 2. Load Balancer

Request đầu tiên đi tới Load Balancer.

Nhiệm vụ của nó rất đơn giản:

Chọn một API Server còn rảnh.

Nhờ vậy hàng triệu request được phân phối đều.


Bước 3. API Server

API Server sẽ xử lý những việc "không liên quan đến business".

Ví dụ:

  • xác thực JWT
  • kiểm tra quyền
  • rate limiting
  • validate request

Nếu mọi thứ hợp lệ...

Request sẽ được chuyển tiếp sang Post Service.


Bước 4. Post Service

Đây mới là nơi bài viết thực sự được tạo.

Post Service sẽ:

  • sinh post_id
  • lưu metadata
  • lưu thời gian tạo
  • lưu author_id

Ví dụ:

Post
post_id = 987654
author = Alex
created_at = ...
content = ...

Nếu có ảnh hoặc video. Media sẽ được upload sang Object Storage (S3, GCS...)

Database chỉ lưu đường dẫn.

image_url
video_url

thay vì lưu file trực tiếp.

Điều này giúp database nhẹ hơn rất nhiều.


Bước 5. Bài toán mới xuất hiện

Đến đây. Ta đã lưu bài viết thành công.

Nhưng bạn bè của Alex làm sao nhìn thấy bài viết?

Có hai cách.

Cách 1:

Không làm gì cả. Đợi khi Bob mở Facebook. Lúc đó mới đi tìm bài viết của Alex. Đây gọi là Pull.


Cách 2:

Ngay khi Alex đăng bài. Hệ thống chủ động đưa bài viết vào Feed của Bob. Đây gọi là Push. Facebook gọi quá trình này là: Fanout

Đây chính là trái tim của toàn bộ News Feed. Ở phần sau chúng ta sẽ dành hẳn một chương để nói về nó.

Hiện tại chỉ cần hiểu:

Sau khi lưu bài viết. Hệ thống sẽ gửi một sự kiện sang Fanout Service.

Post Service
↓
Fanout Service

Fanout Service sẽ chịu trách nhiệm phân phối bài viết đến những người cần nhìn thấy nó.


Bước 6. Notification Service

Song song với Fanout. Một số mạng xã hội còn gửi Push Notification.

Ví dụ:

Alex vừa đăng ảnh mới.

Điều quan trọng là:

  • Notification không nên nằm trong luồng chính.
  • Nếu Notification bị lỗi. Việc đăng bài vẫn phải thành công.

Do đó:

Post Service
↓
Message Queue
↓
Notification Service

Đây là ví dụ điển hình của xử lý bất đồng bộ (Asynchronous Processing).


Tóm tắt Feed Publishing

Toàn bộ luồng đăng bài có thể hình dung như sau: image.png

Mọi thứ diễn ra chỉ trong vài trăm mili giây.

Nhưng phía sau là rất nhiều service phối hợp với nhau.


Luồng 2 — News Feed Generation

Bây giờ hãy đổi góc nhìn. Không còn là người đăng bài nữa. Mà là người mở Facebook.

Bạn mở ứng dụng. Điều đầu tiên bạn mong đợi là gì? Không phải nút Like. Không phải Messenger.

Mà là News Feed phải hiện ra ngay lập tức.

Nếu phải chờ 5 giây. Có lẽ bạn đã đóng ứng dụng.

Đây chính là lý do Read Path được tối ưu mạnh hơn rất nhiều so với Write Path.


Client gửi request

Ứng dụng gọi API:

GET /v1/me/feed

API Server

Sau khi xác thực. Request được chuyển sang:

News Feed Service

Đây là service chịu trách nhiệm xây dựng Feed.


News Feed Service

Nếu mỗi lần người dùng mở Facebook.

Service đều chạy câu SQL kiểu:

SELECT *
FROM posts
WHERE author_id IN (...)
ORDER BY created_at DESC

thì database sẽ nhanh chóng sụp đổ.

Nhớ lại phần ước lượng quy mô. Hệ thống có khoảng:

≈ 58.000 request đọc mỗi giây

Không một database nào muốn xử lý hàng chục nghìn câu truy vấn phức tạp như vậy liên tục.

Do đó, Facebook không đọc trực tiếp từ Database. Họ đọc từ Cache.


News Feed Cache

News Feed Cache không lưu toàn bộ bài viết.

Nó chỉ lưu:

User A  →   [ post_100,
              post_99,
              post_98,
              post_97  ]

Hay nói cách khác. Nó chỉ lưu danh sách post_id.

Điều này có hai lợi ích rất lớn.

  • Thứ nhất: Cache nhỏ hơn rất nhiều.
  • Thứ hai: Nếu bài viết được chỉnh sửa.

Ta chỉ cần cập nhật một nơi duy nhất.

Không phải sửa hàng triệu bản sao.


Ghép dữ liệu

Sau khi lấy được danh sách post_id.

News Feed Service tiếp tục hỏi những cache khác.

Post Cache
↓
Nội dung bài viết
User Cache
↓
Tên
Avatar
Action Cache
↓
Like
Comment
Share

Sau đó, ghép tất cả lại thành:

{
  "author": "...",
  "content": "...",
  "image": "...",
  "like": 125,
  "comment": 18
}

và trả về cho Client.

Đây cũng là lý do News Feed Service đôi khi được gọi là một Aggregation Service.

Nó không tạo dữ liệu mới. Nó chỉ tổng hợp dữ liệu từ nhiều nguồn thành một phản hồi hoàn chỉnh.


Kiến trúc tổng thể

Đến đây, bức tranh của hệ thống đã rõ ràng hơn rất nhiều.

image.png

Mặc dù chỉ có hai chức năng "đăng bài" và "xem bảng tin", nhưng kiến trúc đã được tách thành nhiều service chuyên trách. Mỗi service chỉ tập trung vào một nhiệm vụ duy nhất, giúp hệ thống dễ mở rộngdễ bảo trì khi quy mô tăng lên.

Đây là phần quan trọng nhất của cả chương, và cũng là phần mà Alex Xu dành nhiều thời lượng nhất. Thay vì chỉ giải thích "Push vs Pull", mình sẽ viết theo đúng phong cách dẫn dắt vấn đề → để người đọc tự thấy vấn đề → phân tích trade-off → đưa ra giải pháp, giống như trong một buổi phỏng vấn System Design.


Bước 4. Đi sâu vào thiết kế chi tiết

1. Trái tim của News Feed – Fanout hoạt động như thế nào?

Đến đây, chúng ta đã có một kiến trúc khá hoàn chỉnh.

  • Người dùng đăng bài. Post Service lưu bài viết.
  • News Feed Service đọc dữ liệu từ Cache.

Mọi thứ nghe có vẻ hợp lý.

Nhưng, có một câu hỏi quan trọng mà chúng ta vẫn đang chưa được làm rõ.

Làm sao bài viết của tôi xuất hiện trong News Feed của bạn bè?

Nghe có vẻ đơn giản.

Nhưng đây lại là nơi quyết định hệ thống có thể phục vụ 500 triệu DAU hay sẽ sụp đổ ngay trong ngày đầu tiên.

Đây chính là nhiệm vụ của Fanout Service.


2. Fanout là gì?

Từ Fan-out trong kỹ thuật có nghĩa là:

Một sự kiện được phân phối đến rất nhiều nơi khác nhau.

Trong News Feed, sự kiện đó là:

Một người vừa đăng bài viết mới.

Và nhiệm vụ của Fanout là:

Làm thế nào để tất cả những người cần nhìn thấy bài viết đó đều nhìn thấy?

Nếu Alex có 300 người bạn. Fanout sẽ phải "phân phối" bài viết cho 300 News Feed.

Nghe khá đơn giản. Nhưng hãy thử thay Alex bằng Cristiano Ronaldo. Fanout sẽ phải "phân phối" bài viết cho hàng trăm triệu News Feed.


3. Celebrity Problem

Cristiano Ronaldo có hơn 600 triệu follower.

Giả sử anh ấy đăng một bức ảnh. Điều gì xảy ra?

Nếu hệ thống cố gắng đưa bài viết đó vào News Feed của toàn bộ follower ngay lập tức...

Nó sẽ phải thực hiện:

600.000.000 lần ghi

chỉ vì một lần nhấn nút Post.

Giả sử:

  • Nếu mỗi thao tác ghi mất 2ms thì sao?
  • Nếu nhiều người nổi tiếng cùng đăng bài thì sao?
  • Nếu cache bị nghẽn thì sao?

Đây chính là Celebrity Problem.

Một trong những bài toán nổi tiếng nhất trong thiết kế News Feed.

Để giải quyết nó, hãy cùng xem có những cách tiếp cận nào.


Giải pháp số 1 — Fanout on Write (Push Model)

Đây là cách nghĩ đầu tiên mà hầu như ai cũng sẽ nghĩ đến.

Ngay khi Alex đăng bài. Hệ thống lập tức đưa bài viết vào Feed của toàn bộ bạn bè.

Alex đăng bài
↓
Fanout
↓
Bob Feed
Alice Feed
David Feed

Nói cách khác.

Hệ thống Push dữ liệu đến người nhận.

Đó là lý do nó còn được gọi là Push Model.


Ví dụ

Alex có 4 người bạn.

Alex
↓
Đăng bài
↓
Fanout
↓
Bob Feed
Alice Feed
David Feed
Emma Feed

Sau vài mili giây. Nếu Bob mở Facebook.

Feed của Bob đã chứa bài viết.

  • Không cần tìm kiếm.
  • Không cần query.
  • Không cần merge.

Chỉ việc đọc.


Ưu điểm

Đây là ưu điểm lớn nhất.

Tốc độ đọc cực nhanh.

News Feed gần như đã được chuẩn bị sẵn.

Client chỉ cần:

GET /feed
↓
Feed Cache
↓
Response

Không phải thực hiện:

  • JOIN
  • ORDER BY
  • Merge nhiều nguồn dữ liệu

Độ trễ rất thấp.

Đây chính là điều Facebook mong muốn.

Bởi vì người dùng mở ứng dụng để đọc Feed, chứ không phải để chờ.


Nhưng...

Hãy quay lại Celebrity Problem.

Nếu người đăng có:

  • 10 người bạn → Không vấn đề.
  • 100 người bạn → Không vấn đề.
  • 500 người bạn → Vẫn ổn.

Nhưng nếu người đăng là Taylor Swift? Hay Lionel Messi? Hay một fanpage có hàng chục triệu follower?

Mỗi lần đăng bài sẽ trở thành:

Post
↓
50 triệu lần ghi Cache

Ngay lập tức. Một server Cache sẽ trở thành "điểm nóng".

Toàn bộ cluster có thể bị nghẽn.

Đó là nhược điểm lớn nhất của Push Model.


Tóm tắt Push Model

Ưu điểm:

  • ✅ Feed mở cực nhanh
  • ✅ Trải nghiệm người dùng tốt
  • ✅ Database gần như không bị đọc liên tục

Nhược điểm:

  • ❌ Tốn rất nhiều thao tác ghi
  • ❌ Celebrity Problem
  • ❌ Tốn bộ nhớ Cache

Giải pháp số 2 — Fanout on Read (Pull Model)

Nếu Push quá tốn ghi. Vậy tại sao không làm điều ngược lại?

Thay vì chuẩn bị Feed trước. Hãy đợi đến khi người dùng mở Facebook.

Lúc đó mới đi lấy dữ liệu. Đây chính là Pull Model.


Cách hoạt động

Alex đăng bài. Hệ thống chỉ lưu bài viết.

Alex
↓
Post Service
↓
Database
  • Không Fanout.
  • Không Push.
  • Không Cache Feed.

Sau đó, một giờ sau Bob mở Facebook. Lúc này, News Feed Service sẽ làm việc.

Bob mở Facebook
↓
Lấy danh sách người Bob Follow
↓
Đọc bài viết của từng người
↓
Ghép lại
↓
Sắp xếp theo thời gian
↓
Trả về Client

Đây chính là hành động Pull dữ liệu.


Ví dụ

Bob Follow:

  • Alex
  • Alice
  • David
  • Emma

News Feed Service sẽ lần lượt đọc:

Alex Post
Alice Post
David Post
Emma Post
↓
Sort theo thời gian
↓
Feed

Ưu điểm

Việc đăng bài cực kỳ nhẹ.

Cho dù người đăng có:

  • 10 follower hay 500 triệu follower thao tác đăng vẫn giống hệt nhau.
Save Post

Done.
  • Không Fanout.
  • Không Push.
  • Không ghi hàng triệu Cache.

Celebrity Problem gần như biến mất.


Nhưng...

Giả sử Bob Follow:

  • 800 người.

Mỗi người đăng vài bài.

Khi Bob mở Facebook. News Feed Service sẽ phải:

  • đọc hàng nghìn bài viết
  • merge
  • sort
  • loại bỏ bài trùng
  • phân trang

Tất cả đều xảy ra trong lúc Bob đang chờ.

Điều này khiến:

  • Latency tăng mạnh.
  • Database chịu áp lực cực lớn.
  • Feed mở chậm.

Tóm tắt Pull Model

Ưu điểm:

  • ✅ Đăng bài rất nhanh
  • ✅ Không có Celebrity Problem
  • ✅ Ít tốn Cache

Nhược điểm:

  • ❌ Đọc Feed chậm
  • ❌ Database rất nặng
  • ❌ Merge dữ liệu phức tạp

Nếu là bạn, bạn sẽ chọn mô hình nào?

Đây là lúc người phỏng vấn thường dừng lại.

Họ sẽ hỏi:

Bạn chọn Push hay Pull?

Rất nhiều ứng viên trả lời ngay:

Push.

Hoặc:

Pull.

Đó chưa phải câu trả lời tốt nhất.

Một System Designer giỏi sẽ nhận ra rằng:

Không có mô hình nào tốt trong mọi trường hợp.

Hãy nhìn lại hai nhóm người dùng.


Người dùng bình thường

Một người có:

  • 300 bạn bè
  • 500 follower

Nếu Push, chỉ khoảng vài trăm thao tác ghi. Rất rẻ.

Đổi lại, bạn bè mở Feed cực nhanh.

Push rõ ràng là lựa chọn hợp lý.


Người nổi tiếng

Cristiano Ronaldo, Taylor Swift, BlackPink, NASA, Apple...

Nếu Push một bài viết, hàng chục triệu thao tác ghi.

Push rõ ràng không hợp lý.


Hybrid Fanout — Giải pháp mà Facebook sử dụng

Đây là lúc Alex Xu đưa ra giải pháp.

  • Không Push tất cả.
  • Không Pull tất cả.

Mà kết hợp cả hai.

Đây gọi là:

Hybrid Fanout


Với người dùng bình thường

Sử dụng:

Fanout on Write

Alex
↓
Fanout
↓
Friend Feed Cache

Feed luôn sẵn sàng.

Đọc cực nhanh.


Với người nổi tiếng

Không Push.

Chỉ lưu bài viết.

Taylor Swift
↓
Database

Khi người dùng mở Feed.

Hệ thống mới:

Pull
↓
Merge
↓
Hiển thị

Nhờ vậy.

Một bài viết của Taylor Swift sẽ không tạo ra hàng chục triệu thao tác ghi.


News Feed cuối cùng được tạo như thế nào?

Giả sử Bob Follow:

  • Alex
  • David
  • Taylor Swift

Alex và David là người bình thường.

Taylor Swift là Celebrity.

Feed Cache của Bob sẽ chứa:

Alex Post
David Post

Khi Bob mở Feed, News Feed Service sẽ:

Đọc Feed Cache
+
Pull bài mới của Taylor Swift
↓
Merge
↓
Sort
↓
Response

Người dùng hoàn toàn không nhận ra sự khác biệt.

Nhưng phía sau, hệ thống đã tiết kiệm được hàng triệu thao tác ghi.


Vậy Facebook xác định Celebrity như thế nào?

Trong thực tế. Không có một con số cố định.

Thông thường. Hệ thống sẽ đặt một ngưỡng.

Ví dụ:

Follower < 10.000
↓
Push
-------------------
Follower ≥ 10.000
↓
Pull

Con số này hoàn toàn có thể thay đổi.

Tùy:

  • lượng RAM
  • tốc độ Cache
  • traffic
  • chi phí vận hành

Đây là một ví dụ rất điển hình của trade-off trong System Design.

Không có đáp án tuyệt đối đúng. Chỉ có đáp án phù hợp với quy mô của hệ thống.

Mình sẽ viết phần này theo đúng tinh thần của Alex Xu: không chỉ liệt kê các loại cache, mà giải thích vì sao phải thiết kế như vậy, những trade-off đằng sau và cách hệ thống tiếp tục mở rộng khi quy mô tăng lên hàng tỷ người dùng.


Bước 5. Hoàn thiện hệ thống – Cache, Trade-off và khả năng mở rộng

Ở phần trước, chúng ta đã giải quyết bài toán khó nhất của News Feed:

Làm thế nào để phân phối một bài viết đến hàng triệu người dùng mà không làm hệ thống sập?

Câu trả lời là Hybrid Fanout.

Tuy nhiên, Fanout mới chỉ giải quyết được một nửa bài toán.

Hãy nhớ lại luồng đọc Feed.

User
↓
GET /feed
↓
News Feed Service

News Feed Service vẫn cần rất nhiều dữ liệu:

  • Nội dung bài viết
  • Ảnh
  • Video
  • Avatar người đăng
  • Tên người đăng
  • Số lượt Like
  • Bình luận
  • Danh sách bạn bè
  • Quyền riêng tư

Nếu mỗi lần mở Feed, hệ thống đều truy vấn trực tiếp Database cho tất cả dữ liệu này, thì dù Fanout có tối ưu đến đâu, database vẫn nhanh chóng trở thành nút thắt cổ chai.

Đó là lý do Facebook xây dựng một hệ thống Cache nhiều tầng (Multi-layer Cache).


Tại sao không dùng một Cache duy nhất?

Một suy nghĩ rất tự nhiên là:

"Nếu Cache nhanh hơn Database, vậy cứ đưa toàn bộ dữ liệu vào Redis là xong."

Nghe có vẻ hợp lý, nhưng thực tế lại tạo ra rất nhiều vấn đề.

Hãy tưởng tượng một bài viết như sau:

Post
↓
Nội dung
Ảnh
Video
Tên tác giả
Avatar
Số Like
Số Comment
Số Share

Trong số này, có dữ liệu gần như không bao giờ thay đổi.

Ví dụ:

  • Nội dung bài viết
  • Tác giả
  • Ảnh

Trong khi đó, có dữ liệu thay đổi liên tục.

Ví dụ:

  • Like
  • Comment
  • Share

Nếu tất cả nằm chung một Cache.

Chỉ cần ai đó bấm Like. Ta phải cập nhật toàn bộ object. Điều này gây lãng phí rất lớn.

Facebook giải quyết vấn đề bằng cách chia Cache theo chức năng.


1. News Feed Cache

Đây là tầng Cache quan trọng nhất.

Điều đặc biệt là, nó không lưu bài viết.

Nó chỉ lưu:

User A  → [ Post 100
            Post 99
            Post 98
            Post 97 ]

Hay nói cách khác. Nó chỉ lưu danh sách post_id.

Tại sao? Có hai lý do.


Lý do thứ nhất

Rất tiết kiệm bộ nhớ. Một post_id chỉ khoảng vài byte.

Trong khi toàn bộ bài viết có thể dài hàng KB.


Lý do thứ hai

Một bài viết có thể xuất hiện trong Feed của hàng triệu người.

  • Nếu lưu cả nội dung. Khi bài viết được chỉnh sửa. Hệ thống phải cập nhật hàng triệu bản sao.
  • Nếu chỉ lưu post_id. Ta chỉ cần sửa một nơi duy nhất.

2. Content Cache

Sau khi có post_id.

News Feed Service sẽ hỏi:

Content Cache
↓
Cho tôi nội dung bài viết này.

Content Cache lưu:

  • Text
  • URL ảnh
  • URL video
  • Thời gian tạo

Đây là dữ liệu rất ít thay đổi.

Tỷ lệ Cache Hit cực kỳ cao.


3. User Cache

Tiếp theo, Feed cần hiển thị:

Alex Xu

Avatar

Nếu mỗi bài viết đều truy vấn bảng User.

Database sẽ phải đọc đi đọc lại cùng một dữ liệu.

Do đó, thông tin người dùng được Cache riêng.

Ví dụ:

user_123
↓
Name
Avatar
Verified Badge

Một người có thể đăng hàng nghìn bài.

Nhưng Profile chỉ cần lưu một lần.


4. Action Cache

Đây là Cache "bận rộn" nhất.

Nó lưu:

Like Count
Comment Count
Share Count

Đây là dữ liệu thay đổi từng giây.

  • Nếu Like được ghi trực tiếp xuống Database, nó sẽ phải xử lý hàng triệu UPDATE.
  • Thay vào đó, Like trước tiên được cập nhật vào Cache. Sau đó đồng bộ xuống Database theo từng đợt.

Đây là kỹ thuật rất phổ biến trong các hệ thống chịu tải lớn.


5. Social Graph Cache

Cuối cùng, làm sao Fanout biết phải gửi bài viết cho ai?

Nó cần biết:

Alex
↓
Friend List

Nếu mỗi lần đăng bài đều truy vấn Database, Fanout sẽ rất chậm.

Do đó, danh sách Follow/Friend cũng được Cache.

Ví dụ:

Alex  → [ Bob
          David
          Emma ]

Nhờ vậy, Fanout có thể bắt đầu làm việc gần như ngay lập tức.


Toàn bộ hệ thống Cache

Đến đây, News Feed Service sẽ làm việc như sau.

image.png

Mỗi Cache chỉ chịu trách nhiệm một loại dữ liệu.

Đây là nguyên tắc rất quan trọng trong thiết kế hệ thống:

Mỗi thành phần chỉ nên làm một việc và làm thật tốt việc đó.


Trade-off 1 – Cache bao nhiêu là đủ?

Nếu Cache nhanh, Tại sao không Cache toàn bộ Feed?

Ví dụ:

100.000 bài viết cho mỗi người dùng

Nghe có vẻ tuyệt vời. Nhưng hãy thử tính toán.

Giả sử 500 triệu người dùng, mỗi người Cache 100.000 post_id. → Dung lượng RAM sẽ tăng khổng lồ.

Trong khi thực tế, có bao nhiêu người cuộn xuống bài viết thứ 50.000? → Gần như không có.

Facebook thường chỉ Cache:

  • 300 bài
  • 500 bài
  • hoặc khoảng vài trăm bài mới nhất

Nếu người dùng kéo tiếp, lúc đó hệ thống mới đọc từ Database.

Đây là một trade-off giữa:

  • RAM
  • Chi phí
  • Trải nghiệm người dùng

Trade-off 2 – Privacy

Một câu hỏi mà người phỏng vấn rất thích hỏi là:

Nếu bài viết đặt chế độ "Chỉ bạn bè" thì sao?

Hay:

Người dùng vừa Block nhau thì sao?

Nếu Fanout Push bài viết vào Feed trước.

Liệu người kia vẫn nhìn thấy?

Đây là lý do News Feed Service thường phải thực hiện bước cuối cùng.

Feed
↓
Privacy Filter
↓
Response

Trước khi trả dữ liệu, hệ thống sẽ kiểm tra:

  • Có phải bạn bè không?
  • Có bị Block không?
  • Có quyền xem không?

Nếu không, bài viết sẽ bị loại bỏ.

Điều này giúp đảm bảo tính đúng đắn của dữ liệu.


Trade-off 3 – Stateless Web Tier

Một nguyên tắc khác trong System Design là:

Web Server nên Stateless.

Điều đó có nghĩa là API Server không lưu Session.

  • Không nhớ người dùng.
  • Không nhớ Feed.
  • Mọi request đều độc lập.
  • Lợi ích rất lớn.

Nếu traffic tăng gấp đôi, ta chỉ cần:

10 API Server
↓
20 API Server
↓
50 API Server

Không cần thay đổi logic.

Đây là nền tảng của Horizontal Scaling.


Trade-off 4 – Đồng bộ bất đồng bộ

Hãy quay lại luồng đăng bài.

Sau khi lưu bài viết, có rất nhiều việc phải làm.

Fanout
Notification
Analytics
Search Index
Recommendation

Nếu làm tất cả trong request chính. Người dùng sẽ phải chờ rất lâu.

Do đó, Facebook thường sử dụng Message Queue.

Post Service
↓
Kafka
↓
Fanout
↓
Notification
↓
Analytics

Người dùng chỉ cần chờ đến khi bài viết được lưu thành công.

Những công việc còn lại sẽ chạy ở phía sau.

Đây là lý do nhiều hệ thống lớn có thể phản hồi rất nhanh mặc dù phía sau còn hàng chục service đang hoạt động.


Nếu quy mô tiếp tục tăng thì sao?

Giả sử vài năm sau.

Facebook không còn: 500 triệu DAU mà là: 2 tỷ DAU.

Kiến trúc này có còn hoạt động?

Câu trả lời là: Có.

Bởi vì ngay từ đầu. Chúng ta đã xây dựng hệ thống theo hướng có thể mở rộng.

Ví dụ:

  • Nếu Feed Cache quá tải. Ta chỉ cần thêm Redis Node.
  • Nếu Fanout quá tải. Ta thêm Worker.
  • Nếu Database quá tải. Ta Sharding.
  • Nếu API quá tải. Ta thêm Server.

Không cần viết lại toàn bộ hệ thống.

Đó chính là mục tiêu lớn nhất của System Design.


Tổng kết

Hãy nhìn lại toàn bộ hành trình của chúng ta.

Ban đầu, News Feed chỉ là một yêu cầu rất đơn giản.

  • Người dùng đăng bài.
  • Bạn bè nhìn thấy bài viết.

Nhưng để hiện thực hóa điều đó ở quy mô hàng trăm triệu người dùng, chúng ta đã phải giải quyết rất nhiều bài toán:

  • Tách riêng luồng đăng bàiđọc Feed để tối ưu cho từng loại workload.
  • Sử dụng Hybrid Fanout để cân bằng giữa tốc độ đọc và chi phí ghi, đồng thời tránh Celebrity Problem.
  • Thiết kế Cache nhiều tầng để giảm áp lực lên Database và tối ưu từng loại dữ liệu.
  • Kiểm tra Privacy trước khi hiển thị Feed nhằm đảm bảo tính đúng đắn.
  • Áp dụng kiến trúc Stateless để dễ dàng mở rộng theo chiều ngang.
  • Sử dụng Message Queue để xử lý bất đồng bộ các tác vụ nặng như Fanout, Notification hay Analytics.

Nếu nhìn toàn bộ hệ thống dưới góc độ kiến trúc, chúng ta sẽ thấy một điều thú vị:

News Feed không phải là một thuật toán phức tạp.

Thứ khiến nó trở thành một bài toán kinh điển của System Design là khả năng đưa ra những trade-off hợp lý giữa hiệu năng, chi phí và khả năng mở rộng.

Đó cũng là điều Alex Xu luôn muốn truyền tải trong các cuốn sách của mình.

Một System Designer giỏi không phải là người biết nhiều công nghệ nhất.

Mà là người hiểu rõ bài toán, biết đâu là nút thắt của hệ thống và lựa chọn giải pháp phù hợp nhất với bối cảnh hiện tại.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí