16 điểm bởi GN⁺ 2025-02-20 | 1 bình luận | Chia sẻ qua WhatsApp
  • Khi thiết kế hệ thống, trên thực tế gần như không thể đồng thời thỏa mãn tính nhất quán hoàn hảo, tính sẵn sàng, độ trễ thấp và thông lượng cao
    • Điều quan trọng là tìm ra thiết kế phù hợp bằng cách xác định điểm cân bằng thích hợp cho ứng dụng
  • Trong thiết kế feed/timeline đang theo dõi của Bluesky, họ áp dụng một sự đánh đổi là hy sinh một phần tính nhất quán để cải thiện hiệu năng ghi
    • Kết quả là độ trễ P99 giảm hơn 96% mà không gây ảnh hưởng tiêu cực đến người dùng

Fanout timeline

  • Khi người dùng đăng bài trên Bluesky, bài viết sẽ được hệ thống lập chỉ mục, lưu vào cơ sở dữ liệu và được cung cấp qua phản hồi API
  • Đồng thời, bài viết này trải qua quá trình “fanout” để chèn vào bảng timeline của từng người theo dõi
  • Việc này được thực hiện bằng cách truy vấn danh sách người theo dõi rồi chèn theo thứ tự ngược vào bảng timeline của từng người
  • Bảng timeline được phân vùng theo từng người dùng và lưu trong cơ sở dữ liệu phân tán (ScyllaDB), đồng thời được sao chép sang nhiều shard để đảm bảo tính sẵn sàng cao
    • Mỗi người dùng có thể được gán vào các shard khác nhau
  • Để tiết kiệm dung lượng lưu trữ, các timeline vượt quá một độ dài nhất định sẽ định kỳ xóa các tham chiếu đến bài viết cũ

Vấn đề hot shard

  • Bluesky có khoảng 32 triệu người dùng, và cơ sở dữ liệu timeline được chia thành hàng trăm shard
  • Trong một hệ thống có hàng triệu người dùng, có thể tồn tại những người dùng có số quan hệ follow cực lớn
    • Ví dụ: người dùng follow hàng trăm nghìn tài khoản
  • Một shard lưu trữ timeline của nhiều người dùng cùng lúc
  • Nếu một người dùng cụ thể tạo ra quá nhiều ghi, shard đó sẽ rơi vào trạng thái quá tải (“hot shard”)
  • Các hot shard như vậy khiến các thao tác đọc hoặc ghi bị dồn vào, làm độ trễ lan sang cả những người dùng khác trên cùng shard

Tích lũy độ trễ

  • Nếu một người dùng có 2.000.000 người theo dõi, việc ghi tuần tự có thể mất hơn 20 phút
  • Để giảm thời gian này, fanout có thể được song song hóa để rút ngắn độ trễ trung bình
  • Tuy nhiên, độ trễ P99 (khoảng từ 15 mili giây trở lên) có thể xảy ra nhiều lần và làm chậm toàn bộ tác vụ song song
  • Khi số người theo dõi rất lớn, độ trễ P99 hoặc P99.9 có thể khiến tổng thời gian fanout trong trường hợp xấu nhất tăng từ vài phút lên đến hàng chục phút

Timeline Lossy (mất mát)

  • Với những người dùng follow quá nhiều tài khoản, việc hiển thị mọi bài viết theo đúng thứ tự một cách chính xác là điều không thực tế
  • Trên thực tế, con người cũng khó có thể tiêu thụ hết toàn bộ số bài viết đó
  • Vì vậy, với timeline của những người dùng có số lượng follow vượt quá một ngưỡng nhất định (ví dụ: reasonable_limit), Bluesky áp dụng cách xác suất “drop” một phần thao tác ghi
  • Công thức được sử dụng là loss_factor = min(reasonable_limit / num_follows, 1)
  • Trong lúc fanout, hệ thống tạo một giá trị ngẫu nhiên và nếu giá trị đó lớn hơn loss_factor thì sẽ bỏ qua thao tác ghi vào timeline
  • Nhờ đó có thể giới hạn lượng ghi quá mức vào timeline của một người dùng cụ thể và ngăn hiệu năng toàn shard bị suy giảm

Về caching

  • Vì số lần ghi timeline vượt quá một triệu mỗi giây, nếu ở mỗi lần ghi lại truy vấn trực tiếp số lượng follow của người dùng từ DB thì tải sẽ rất lớn
  • Thay vào đó, họ cache các tài khoản có số lượng follow cao trong Redis dưới dạng sorted set
  • Các instance của dịch vụ fanout sẽ nạp thông tin cache này vào bộ nhớ mỗi 30 giây
  • Kết quả là ngay trong quá trình fanout, hệ thống vẫn có thể tra cứu nhanh thông tin về người dùng có lượng follow cao
  • Vì thông tin cache không nhất thiết phải hoàn toàn mới nhất, đây là một cách tiếp cận chấp nhận một chút không hoàn hảo để tăng hiệu năng và khả năng mở rộng

Kết quả

  • Sau khi áp dụng timeline lossy, các hot shard trong cơ sở dữ liệu Timelines gần như biến mất
  • Độ trễ P99 để xử lý một trang fanout giảm hơn 90%
  • Xét toàn bộ công việc fanout, những tác vụ từng mất 5~10 phút theo chuẩn P99 đã được rút xuống dưới 10 giây
  • Điều này cho thấy rằng ngay cả khi hy sinh một phần tính nhất quán, vẫn có thể đáp ứng đầy đủ kỳ vọng của người dùng dịch vụ và duy trì khả năng mở rộng ở quy mô lớn
  • Kiến trúc timeline của Bluesky vẫn còn chỗ để cải thiện, nhưng thay đổi lần này đã cải thiện đáng kể thông lượng ghi và khả năng mở rộng

1 bình luận

 
GN⁺ 2025-02-20
Ý kiến trên Hacker News
  • Là người yêu thích hệ thống, tôi rất thích những bài như thế này. Rất dễ rơi vào lối nghĩ rằng "mọi thứ phải hoàn hảo"

    • Tôi từng xây dựng một chỉ mục "eventually consistent" ở backend của công cụ tìm kiếm Blekko. Cách này giúp đưa bản cập nhật đến người dùng nhanh hơn, nhưng hai người dùng chạy cùng một truy vấn có thể nhận kết quả hơi khác nhau
    • Có rất nhiều lý thuyết hệ thống được áp dụng ở đây, và nếu có phản hồi dương thì hệ thống có thể dao động. Với công cụ tìm kiếm, bộ xếp hạng gán trọng số cho các liên kết mà người dùng nhấp vào chính là nguồn phản hồi dương
    • Điều quan trọng là giữ cho hệ thống ở trạng thái "critically damped". Nhờ vậy nó hội tụ nhanh
    • Cách timeline của người dùng được sharding và có các vòng phản hồi (ví dụ: 'thích' hoặc 'đăng lại') có vẻ là một không gian bài toán rất thú vị
  • Tôi thắc mắc vì sao không triển khai timeline theo kiểu hybrid dựa trên độ nổi tiếng của tài khoản

    • Với các tài khoản người nổi tiếng, thay vì phát tán mọi tin nhắn đến hàng triệu follower, sẽ rẻ hơn nếu lấy bài đăng của người nổi tiếng rồi trộn vào khi phục vụ timeline cho follower
    • Nếu hàng triệu follower đều làm như vậy, việc chỉ đọc từ hot cache sẽ rẻ hơn
  • Đây là một lời giải thú vị cho một bài toán thú vị. Cảm ơn vì đã chia sẻ

    • Tôi hơi khó hiểu đoạn tác giả chuyển từ "người nổi tiếng" sang "bot"
    • Có vẻ như tác giả đã đưa vào một khái niệm hoàn toàn khác là "timeline mất mát"
  • Tôi tò mò về chiến lược hy sinh tính nhất quán này. Cũng muốn biết liệu có suy nghĩ nào về các cách khác ngoài fan-out hoàn toàn ở phía đọc hoặc ghi không

    • Thay vì ghi vào timeline của mọi người dùng, tôi hình dung có thể chỉ ghi một lần vào mỗi shard có ít nhất một follower
    • Khi đọc, lấy nội dung dành cho người dùng đó rồi lọc ra follower thực tế
    • Việc đọc diễn ra trong shard nên độ trễ thấp
    • Với các tài khoản có lượng follower cực lớn, trang sẽ không thấy các mục cũ
  • Không nhất thiết phải cung cấp mọi thứ mà hàng nghìn người một người dùng theo dõi đã đăng theo đúng thứ tự thời gian tuyệt đối, nhưng việc cung cấp đủ nội dung để timeline luôn có nội dung mới thì là hợp lý

    • Có vẻ như giải pháp ở đây không phải là thứ tự thời gian không hoàn hảo, mà là các bài đăng bị thiếu khỏi feed
  • Tôi tự hỏi việc giới hạn số follower để tránh vấn đề hot shard sẽ hoạt động thế nào

    • Mỗi người dùng sẽ có một timeline riêng cho mỗi 1000 follower và client sẽ trộn chúng lại
    • Nếu cần, có thể chỉ tải một phần của timeline thực để tạo ra phần mất mát
  • AWS có một cách tiếp cận tổng quát khá hay cho bài toán này

    • Gán mỗi người dùng vào nhiều shard để giảm khả năng những người dùng khác cùng chia sẻ toàn bộ các shard đó
    • Nếu ngay từ đầu dùng shuffle sharding thì có thể giải quyết các vấn đề mới mà không ảnh hưởng đến quá nhiều người dùng khác
  • Các tài khoản theo dõi hàng trăm nghìn người dùng rõ ràng là bot cào nội dung. Tôi sẽ chặn luôn cho xong

    • Tôi thích đọc về các thách thức kỹ thuật. Twitter có một kiến trúc đặc biệt dành cho người nổi tiếng có hàng triệu follower
    • Nếu Bluesky là một bản sao tương tự, tôi tự hỏi vì sao họ không đi theo hướng đó
  • Khi vào thẳng hồ sơ của một người dùng để xem tất cả bài đăng, đôi khi có những bài đáng lẽ phải có trong timeline nhưng lại không có

    • Điều này giải thích vì sao trên Bluesky, dù tôi theo dõi chưa đến 100 người dùng, thỉnh thoảng tôi vẫn không thấy bài đăng của ai đó trong timeline
  • Tôi thắc mắc vì sao họ triển khai fan-out theo cách mỗi "trang" lại chặn việc lấy trang tiếp theo

    • Hoạt động lấy trang nên lấy follower một cách liên tục và không nên chờ đến khi mọi mục trong trang được cập nhật xong
    • Tôi nghĩ đến việc có một thành phần lấy trang, lưu vào S3 rồi đăng metadata và vị trí S3 vào hàng đợi (SQS)
    • Trong hệ thống này có thể kiểm soát concurrency tốt hơn, đồng thời có thể partition trong hàng đợi bằng shard làm khóa để "làm chậm" công việc