Trải nghiệm xử lý 1 tỷ yêu cầu web bằng code nhàm chán
(notes.billmill.org)- Chia sẻ kinh nghiệm khi tái xây dựng hệ thống so sánh gói bảo hiểm y tế Medicare của Mỹ, vận hành ổn định hơn 1 tỷ yêu cầu web với một kiến trúc đơn giản chỉ dùng các công nghệ đã được kiểm chứng như Postgres, golang, React
- Thiết kế kiến trúc với mục tiêu đơn giản và ổn định, đạt thời gian phản hồi trung bình dưới 10ms và tỷ lệ sự cố rất thấp
- Innovation token chỉ được dùng tối thiểu cho phần tách cấu trúc cốt lõi (3 mô-đun lớn, giao tiếp bằng gRPC), còn lại đều chọn các phương pháp tuy nhàm chán nhưng đáng tin cậy
- Từ quản lý schema DB, pipeline ETL, kiểm thử, logging, tài liệu hóa đến công cụ CLI, mọi yếu tố vận hành đều được xây dựng theo cách đơn giản và có thể lặp lại, tạo nên một hệ thống mà cả đội có thể dễ dàng hiểu và bảo trì
- Cho thấy sinh động rằng kiểm soát chất lượng liên tục và tinh thần đồng đội vững mạnh vẫn phát huy hiệu quả ngay cả trong các dự án chính phủ quy mô lớn
Serving a billion web requests with boring code
Tổng quan
- Trong 2 năm rưỡi, tác giả là lead developer cho website của chính phủ Mỹ dùng để so sánh và mua các gói Medicare
- Xử lý 5 triệu yêu cầu API mỗi ngày, với thời gian phản hồi trung bình dưới 10ms, và 95% yêu cầu dưới 100ms
- Tỷ lệ sự cố rất thấp, số lần kỹ sư thực sự bị gọi dậy lúc rạng sáng chỉ đếm trên đầu ngón tay
- Chỉ dùng các công nghệ đã được kiểm chứng mà ai cũng có thể hiểu như Postgres, golang, React để xây dựng một hệ thống ổn định trong thời gian dài
Boring über alles
- Nguyên tắc ưu tiên cao nhất là chỉ chọn “công nghệ nhàm chán nhưng đã được kiểm chứng” (Choose Boring Technology)
- Chỉ tiêu xài innovation token ở những nơi thật sự cần thiết
- Ưu tiên công nghệ và quy trình ổn định, rõ ràng hơn là các giải pháp phức tạp và hào nhoáng
Những phần “nhàm chán”
- Postgres: cốt lõi cho lưu trữ dữ liệu, đáp ứng cả độ tin cậy lẫn khả năng mở rộng. Các truy vấn tìm kiếm phức tạp như faceted search cũng được giải quyết bằng Postgres
- golang: build và deploy nhanh, đầu ra là binary rõ ràng. Xử lý lỗi trực quan, thành viên mới cũng dễ làm quen
- React: là framework SPA được kiểm chứng nhất và đội ngũ đã quen dùng. Khả năng truy cập và hỗ trợ nhiều loại thiết bị cũng là yếu tố quan trọng
- Về dài hạn có phát sinh vấn đề kích thước bundle lớn và tốc độ chậm đi, nhưng ở thời điểm đó đây là lựa chọn tối ưu để ra kết quả đúng hạn
Những innovation token
- Modular backend: toàn bộ backend không đi theo microservice cũng không phải monolith, mà được tổ chức thành 3 mô-đun lớn (
druginfo,planinfo,beneinfo)- Mỗi mô-đun dùng một Postgres DB riêng, và việc chia sẻ dữ liệu chỉ diễn ra thông qua gRPC
druginfo: lập chỉ mục rất tinh vi cho dữ liệu giá thuốc, nơi các tổ hợp như nhà thuốc, bảo hiểm, quy cách đóng gói tăng theo cấp số nhân, nên cần tiền xử lý phức tạp và tối ưu hiệu năngplaninfo: mỗi ngày nhận dữ liệu CMS mới và tạo lại toàn bộ DB để sử dụng, giữ tính bất biếnbeneinfo: phần duy nhất lưu thông tin người đăng ký thực tế, chỉ lưu tối thiểu PII nhạy cảm. Thiết kế và vận hành đều được chú trọng để giảm thiểu rủi ro rò rỉ dữ liệu
- gRPC: có ưu điểm là định nghĩa rõ ràng giao diện giao tiếp giữa các mô-đun bằng code. Khả năng tích hợp với công cụ tự động hóa rất tốt
- Tuy nhiên, build, tooling và debug đều phức tạp hơn, và cũng kém trực quan hơn so với JSON API
- Thông qua grpc-gateway, hệ thống vẫn hỗ trợ web client và xử lý lưu lượng lớn một cách ổn thỏa
Tương thích ngược nghiêm ngặt
- Duy trì tương thích ngược cho API và cơ sở dữ liệu một cách rất nghiêm ngặt
- Các field của API công khai tuyệt đối không bị xóa, và sẽ được giữ suốt vòng đời trừ khi có vấn đề bảo mật
- Với cột trong DB thì có thể thêm thoải mái, nhưng xóa phải qua nhiều bước xác minh như bỏ tham chiếu → chờ vài tuần → mới xóa thật
- Kỷ luật này trở thành nền tảng cốt lõi cho tốc độ thay đổi cao mà vẫn triển khai và vận hành ổn định
Faceted search
- Triển khai faceted search chỉ bằng Postgres thay vì ElasticSearch
- Toàn bộ logic tìm kiếm được xử lý bằng một hàm dài khoảng 250 dòng kết hợp các điều kiện trên bảng plan đã được đánh index tốt
- Tập trung vào yêu cầu nghiệp vụ và giải quyết vấn đề một cách đơn giản, không thêm độ phức tạp không cần thiết
Database
-
tạo lập
- Quản lý schema DB bằng các file
.sqlcó đánh số, nạp theo thứ tự để bảo đảm độ tin cậy - DB
planinfo/beneinfođược tạo lại mỗi ngày nên không cần migration. Nếu có lỗi cấu hình như lệch version, ứng dụng sẽ không được phép khởi động
- Quản lý schema DB bằng các file
-
ETL
- Dùng shell script theo từng nguồn dữ liệu để nạp vào S3 → cron trên EC2 sẽ lấy ETL code/dữ liệu mới nhất và tạo RDS DB mới
- Tận dụng mạnh mẽ câu lệnh COPY của Postgres để nạp dữ liệu hàng loạt hiệu quả hơn so với INSERT
- Mỗi ngày chỉ mất 2–4 giờ để chuyển dữ liệu có quy mô hàng trăm triệu dòng sang DB mới
-
models
- Dùng thư viện xo để tự động sinh model DB, với template tùy chỉnh phù hợp với đội
-
testing
- Sai lầm lớn nhất là viết quá nhiều test dùng sqlmock, khiến việc bảo trì rất phiền phức trong bối cảnh dữ liệu thay đổi thường xuyên
- Nếu dùng DB bất biến thực tế thì test trực tiếp trên DB thật có lẽ sẽ hiệu quả hơn
-
Cơ sở dữ liệu cục bộ cho phát triển
- Có script tự động sinh dữ liệu một phần cho từng bảng, giúp mỗi lập trình viên có một DB local nhỏ để test và phát triển dựa trên dữ liệu thật
- Nếu chuẩn bị được công cụ như vậy trước khi DB trở nên quá lớn, hiệu suất phát triển của cả đội sẽ tăng mạnh
Công cụ linh tinh
- Các công cụ CLI cho tự động hóa vận hành và quan trắc được viết bằng shell script, gom toàn bộ tiện ích quản trị vào một chỗ
- Tích cực phát triển và sử dụng các công cụ thực chiến, như trực quan hóa log splunk thành biểu đồ ngay từ lệnh Slack
Logging
- Tạo request id ngay khi yêu cầu đi vào hệ thống, và gắn id đó vào toàn bộ ngữ cảnh log để có thể truy vết ở mọi nơi
- Thiết kế logging an toàn và có tổ chức với zerolog
- Ghi lại log bắt buộc ở các thời điểm quan trọng như điểm vào/ra của hệ thống hay các tình huống ngoại lệ
Tài liệu hóa
- Chuyển tài liệu Markdown trên GitHub sang dạng wikibook bằng sphinx-book-theme
- Mọi thành viên trong đội đều tích cực đóng góp tài liệu, để toàn bộ tài liệu hệ thống có thể được tìm thấy ở một nơi
- Văn hóa tài liệu hóa xuất sắc đã giúp đội tăng trưởng tốt hơn, bảo trì dễ hơn và onboarding người mới hiệu quả hơn
Tích hợp lúc runtime
- Các yêu cầu từ phía khách hàng có thể làm giảm hiệu năng, như chèn script phân tích, đều được cố gắng thuyết phục để giảm xuống mức tối thiểu
- Các truy vấn cũng được chuyển sang xử lý ở build time thay vì runtime của trình duyệt để giữ hiệu năng dịch vụ
- Dù vậy trên thực tế không thể chặn toàn bộ yêu cầu từ khách hàng, nên một phần vẫn dẫn đến suy giảm hiệu năng
Và còn nữa
- Ngoài công nghệ, tác giả nhấn mạnh rằng bầu không khí nhóm tích cực, hợp tác và động lực mạnh mẽ mới là động lực thật sự giúp hệ thống quy mô lớn thành công
- Đây là một ví dụ cho thấy rõ sức mạnh của những lựa chọn thực tế tuy nhỏ nhưng quan trọng, cùng với việc kiểm soát chất lượng bền bỉ
18 bình luận
Nhàm chán thế này suốt 2,5 năm á?!
Tò mò Faceted Search là gì nên tôi lần theo thì thấy còn có vài thứ đáng đọc thêm.
https://www.cybertec-postgresql.com/en/faceting-large-result-sets/
https://roaringbitmap.org/about/
https://github.com/cybertec-postgresql/pgfaceting
Những ý kiến về từ "nhàm chán" thú vị thật haha. Nếu đổi sang cách nói khác thì từ nào sẽ hợp nhỉ? Tầm như sáo mòn, phổ biến?
Việc dịch
boringlà “nhàm chán” thực sự không truyền tải được đúng sắc thái nghĩa gốc.boringnesslà một trong những triết lý thiết kế của Go.Giả vờ thật~ chán…
Hàn Quốc đúng là từ đầu đến cuối đều là xứ sở Java nên thấy lạ cũng phải thôi kk
Tôi nghĩ golang và React đều là những ngôn ngữ lập trình enterprise kiểu “nhàm chán” của thời đại mới.
Vì
boringkhông thể được dịch sang “nhàm chán” một cách chính xác 100%, nên có vẻ như sắc thái ý nghĩa không được truyền tải đúng cho độc giả Hàn Quốc.Tôi muốn sống trong thế giới nhàm chán với Postgres, golang và React
Đúng vậy, nhìn tiêu đề tôi cũng tưởng là một trò đùa.
Ở nước ngoài, có vẻ đó được xem là một stack nhàm chán.
Thực tế thì Go đơn giản là lựa chọn dễ nhất để làm web server thôi..
Có vẻ họ cho rằng phải phát triển bằng những thứ như Rust hay các ngôn ngữ phía FP thì mới không nhàm chán.
Những điều quá đỗi hiển nhiên... những điểm quan trọng đang bị bỏ qua vì chúng quá hiển nhiên...
Với stack này thì có vẻ cũng không đến mức nhàm chán như vậy. Nếu thật sự nhàm chán thì chắc phải là Java 1.8 trở xuống hoặc tầm như VB mới đúng chăng... tôi chợt có suy nghĩ bất kính như vậy.
Trong nguyên văn có một liên kết liên quan đến
boring, nhưng nếu xem nội dung thì có vẻboringmang nghĩa là "quá quen thuộc".Có những từ phù hợp hơn như
experienced,verified,skillful, vậy mà lại cố tình dùngboring, có vẻ là có chủ ý giật tít để câu tương tác.Có phải ý ở đây không phải là chán để viết, mà là kiểu stack "quốc dân" đến mức dùng quá nhiều nên thành nhàm chán không?
Linux kernel khoảng 2.6.29...
Chỉ riêng việc dùng gRPC thôi thì... haha
Tôi cũng đã nghĩ ngay: golang mà cũng nhàm chán à?
Nếu là mức như classic asp thì còn có thể gọi là nhàm chán chứ.