- Twilio Segment từng vận hành cấu trúc microservices với hàng trăm dịch vụ, nhưng đã chuyển sang một dịch vụ duy nhất (monolithic) do độ phức tạp và gánh nặng bảo trì
- Ban đầu, công ty tách riêng từng Destination API để bảo đảm cô lập sự cố và khả năng mở rộng, nhưng khi số lượng dịch vụ tăng lên hơn 140, chi phí vận hành tăng vọt
- Việc quản lý nhiều repository và thư viện dùng chung trở nên khó khăn, đồng thời phát sinh vấn đề ảnh hưởng đến toàn bộ dịch vụ trong quá trình kiểm thử và triển khai
- Để giải quyết, công ty đưa vào hệ thống Centrifuge và cấu trúc monorepo, đồng thời xây dựng Traffic Recorder để tự động hóa kiểm thử
- Kết quả là tốc độ phát triển và độ ổn định được cải thiện đáng kể, và Twilio Segment hiện duy trì cấu trúc monolithic để tối ưu năng suất và hiệu quả vận hành
Việc áp dụng microservices và những giới hạn
- Twilio Segment đã chọn kiến trúc microservices cho hạ tầng dữ liệu khách hàng, thiết kế để từng dịch vụ theo mục đích riêng có thể xử lý sự kiện một cách độc lập
- Truyền dữ liệu đến hàng trăm server-side destinations (ví dụ: Google Analytics, Optimizely, v.v.)
- Ban đầu dùng một hàng đợi duy nhất, nhưng khi một destination gặp sự cố thì phát sinh vấn đề head-of-line blocking, khiến toàn bộ hệ thống bị chậm trễ
- Để giải quyết, công ty xây dựng dịch vụ và hàng đợi riêng cho từng destination, từ đó đạt được cô lập sự cố và khả năng mở rộng độc lập
- Tuy nhiên, khi số lượng dịch vụ tăng lên, độ phức tạp vận hành và chi phí bảo trì cũng tăng mạnh, dẫn đến tốc độ phát triển chậm lại và tỷ lệ lỗi cao hơn
Vấn đề của repository riêng lẻ và thư viện dùng chung
- Mỗi destination sử dụng định dạng API khác nhau nên cần mã chuyển đổi tùy chỉnh
- Ban đầu được quản lý trong một repository duy nhất, nhưng do lỗi kiểm thử ảnh hưởng đến toàn bộ hệ thống nên công ty đã tách repository
- Sau đó, khi bổ sung hơn 50 destination mới, số lượng repository cũng tăng lên hơn 50
- Để phục vụ các chức năng chung, công ty đưa vào thư viện dùng chung, nhưng điều này lại làm tăng gánh nặng do lệch phiên bản và triển khai
- Do mô hình tải của từng dịch vụ khác nhau, việc cấu hình auto scaling trở nên khó khăn, thậm chí đôi khi người vận hành phải điều chỉnh thủ công
Chuyển sang monolithic và đưa vào Centrifuge
- Công ty quyết định hợp nhất hơn 140 dịch vụ thành một dịch vụ duy nhất
- Để thay thế các hàng đợi riêng lẻ, họ phát triển hệ thống Centrifuge để chuyển mọi sự kiện vào một dịch vụ duy nhất
- Về sau, Centrifuge đã phát triển thành hạ tầng backend Connections của Twilio Segment
- Khi chuyển sang cấu trúc một dịch vụ duy nhất, công ty đã giảm được gánh nặng vận hành và đơn giản hóa việc ứng phó sự cố
Monorepo và tự động hóa kiểm thử
- Toàn bộ mã nguồn destination được gộp vào một repository duy nhất, thống nhất hơn 120 dependency về một phiên bản duy nhất
- Việc quản lý phiên bản trở nên đơn giản hơn và hiệu quả bảo trì được cải thiện
- Để tự động hóa kiểm thử, công ty đưa vào Traffic Recorder
- Ghi lại rồi phát lại các yêu cầu/phản hồi HTTP thực tế để loại bỏ phụ thuộc vào mạng bên ngoài
- Tốc độ kiểm thử được rút ngắn từ vài phút xuống mức mili giây, đồng thời độ ổn định tăng mạnh
- Tỷ lệ kiểm thử thất bại giảm xuống và năng suất của lập trình viên được cải thiện đáng kể
Hiệu quả và đánh đổi của cấu trúc monolithic
- Sau khi hợp nhất thành một dịch vụ duy nhất, tốc độ triển khai và hiệu quả phát triển được cải thiện rõ rệt
- Trong 1 năm, số lần cải tiến thư viện dùng chung tăng từ 32 lên 46
- Một kỹ sư duy nhất có thể triển khai chỉ trong vài phút
- Hiệu quả vận hành cũng được cải thiện, nhờ đó ngay cả khi tải tăng đột biến vẫn có thể hấp thụ bằng một worker pool quy mô lớn
- Tuy nhiên, vẫn tồn tại các nhược điểm như khó cô lập lỗi, giảm hiệu quả cache, và rủi ro khi cập nhật dependency
- Một phần tổn thất này được bù lại bằng sự đơn giản trong vận hành và năng suất tăng cao
Kết luận
- Microservices đã giải quyết các vấn đề hiệu năng ban đầu, nhưng không phù hợp với mở rộng ở quy mô lớn và cập nhật hàng loạt
- Việc chuyển sang monolithic đã cải thiện cả độ ổn định vận hành lẫn tốc độ phát triển
- Để chuyển đổi thành công, cần có hệ thống kiểm thử vững chắc và chấp nhận các đánh đổi
- Twilio Segment vẫn duy trì microservices ở một phần hạ tầng, nhưng với server-side destinations thì monolithic được đánh giá là cấu trúc phù hợp hơn
2 bình luận
Có vẻ như việc chia nhỏ và chuẩn hóa mọi thứ đều tiềm ẩn rủi ro.
Ý kiến trên Hacker News
Khi gom mã của mọi đích đến vào một repo, họ có thể hợp nhất thành một dịch vụ duy nhất
Kết quả là năng suất phát triển tăng lên đáng kể. Giờ đây không còn phải triển khai hơn 140 dịch vụ mỗi khi sửa một thư viện dùng chung
Một kỹ sư giờ có thể triển khai chỉ trong vài phút
Nếu mỗi lần thay đổi thư viện lại phải triển khai lại tất cả dịch vụ, thì đó không phải dịch vụ thực thụ mà là một distributed monolith
Bản thân ý tưởng phải cưỡng ép đồng bộ một thư viện dùng chung trên toàn bộ dịch vụ đã không phù hợp với triết lý của kiến trúc dịch vụ
Cái này giống một hệ thống build và triển khai dùng chung kiểu Amazon hơn là “mỗi lần cập nhật thư viện là redeploy toàn bộ”
Thư viện được lấy từ một nguồn duy nhất do trung tâm quản lý, và nếu khác phiên bản thì phải migrate hết vì vấn đề tương thích
Khi cần loại bỏ một phiên bản cụ thể vì lỗ hổng bảo mật thì phải redeploy toàn bộ, nhưng lợi ích của quản trị tập trung vẫn lớn hơn nhiều
Kiểu hệ thống này vẫn được xếp vào microservices, nhưng về mặt chi phí và hiệu quả vận hành thì hoạt động như một môi trường dùng chung
Gọi nó là distributed monolith thì là diễn giải quá mức
Khi đi theo mô hình microservices, rủi ro triển khai tăng lên nhưng ban đầu không dễ nhận ra
Ví dụ nếu bạn sửa bug trong một thư viện liên quan đến tiền bạc, thì thực tế sẽ phải cân nhắc có nên redeploy mọi dịch vụ hay không
Thư viện có lỗ hổng bảo mật thì dù thiết kế hệ thống thế nào cũng phải thay thế toàn diện
Trong trường hợp như vậy, monolith lại dễ xử lý hơn
Nếu là microservices thực thụ thì chúng nên trao đổi thông điệp với nhau và dùng JSON
Chỉ cần biết API chứ không cần biết mã nguồn. Như vậy mỗi bên mới có thể triển khai và mở rộng độc lập
Tận dụng module dùng chung chẳng phải hợp lý hơn sao?
Ở công ty trước, mọi thứ đều chạy bằng microservices, còn công ty trước đó nữa thì dùng AWS serverless
Trong cả hai trường hợp, giao tiếp giữa các dịch vụ là vấn đề lớn nhất. Khó đồng bộ contract, và triển khai cũng phức tạp
Ban đầu mọi thứ tiến triển rất nhanh, nhưng theo thời gian độ phức tạp bùng nổ. Xuất hiện phát triển dựa trên nỗi sợ, và có quá nhiều cuộc họp
Công ty hiện tại của tôi dùng monolith và dễ xử lý hơn hẳn. Kiểu dữ liệu rõ ràng, refactor cũng đơn giản
Thật thú vị khi thấy các AI agent xây trên nền tảng nội bộ tự cải thiện ngay trong codebase
Điểm yếu duy nhất là thời gian build dài, nhưng với tiến bộ của toolchain, tôi kỳ vọng đến 2026 sẽ có triển khai nhanh gấp 10 lần
Kết luận của tôi là, nhờ kiến trúc monolith mà chúng tôi có thể tăng trưởng và mở rộng nhanh hơn nhiều
Với monolith, tách biệt mối quan tâm luôn bị phá vỡ và mức độ ghép nối giữa các nhóm rất nặng
Tốc độ và khả năng mở rộng thực sự chỉ có khi các nhóm được tách rời
Việc chuyển từ ORM sang DTO cần 2 năm, 50 nhóm và hơn 150 người
Loại công việc phức tạp như vậy là không thể nếu không có microservices
Nhìn vào bài này, cốt lõi vấn đề không nằm ở lựa chọn kỹ thuật microservices vs monolith
mà nằm ở chất lượng và cấu trúc của tổ chức kỹ thuật
Cấu trúc repo và cách tổ chức test phản ánh đúng trình độ của tổ chức
Nếu không có ai có thể nói “đừng làm vậy”, độ phức tạp sẽ bùng nổ
Cần có người lãnh đạo đủ thẩm quyền để cả nhóm dừng lại và suy nghĩ
Khi có sự cố API, họ không phân tích nguyên nhân mà chỉ sửa dữ liệu rồi đóng ticket
Dù cùng một vấn đề lặp lại, họ cũng không giải quyết tận gốc nguyên nhân
Thậm chí chỉ cần phỏng vấn thôi cũng có thể phần nào đoán được cấu trúc codebase của công ty
Cái này không hẳn là chuyển sang monolith thực sự, mà vẫn là kiến trúc SOA
Chỉ là phạm vi của dịch vụ đã lớn hơn
Nếu một nhóm quản lý 140 dịch vụ, thì SOA là cấu trúc để mở rộng nhóm chứ không phải để mở rộng dịch vụ
Khi một nhóm quản lý mọi thư viện dùng chung, sẽ nảy sinh lệch phiên bản và hỗn loạn API
Cuối cùng, cấu trúc tổ chức quyết định kiến trúc. Một nhóm đã hợp nhất chúng để giảm độ phức tạp
Đây không phải “monolith” mà là mức dịch vụ được điều chỉnh phạm vi hợp lý theo đơn vị nhóm
Tôi nghĩ cấu trúc này là lý tưởng nhất. Khi nhóm lớn lên thì lại cần tách ra
Tôi không phải người ủng hộ microservices, nhưng sự đối lập giả tạo giữa “monorepo vs microservices” khá dễ thấy
Quá nhiều công cụ mặc định coi dịch vụ và repo là quan hệ 1:1
Nhưng bạn vẫn có thể đặt mọi thứ trong một repo mà triển khai độc lập
Sẽ rất hay nếu ở những nơi như GitHub có thể coi từng thư mục là một dịch vụ độc lập
Dùng Bazel để quản lý cây phụ thuộc, rồi dùng
bazel queryđể tìm các target bị ảnh hưởng và tự động chạy testChúng tôi tạo workflow chặn PR bằng cách tích hợp với GitHub Actions
Nó hoạt động tốt, nhưng mất vài tháng để xây dựng
Vấn đề thực sự là do thiếu vận hành và công cụ — CI, autoscaling, quy trình on-call đều còn yếu
Cả hai cách tiếp cận đều có thể thất bại
Trong các môi trường như Node.js hay Python, có giới hạn về lượng mã mà event loop có thể xử lý
Tôi từng quản lý 200 dịch vụ với 6–8 người, và cũng từng thấy 80 người quản lý một monolith duy nhất
Microservices có lợi cho thay đổi nhỏ, nhưng khó với thay đổi toàn cục, còn
monolith thì ngược lại
Cuối cùng điều quan trọng không phải kiến trúc mà là cách trừu tượng hóa, kiểm thử và tách ghép
Tiêu chí của “micro” không phải là công nghệ mà là đơn vị kinh doanh
Nhỏ hơn mức đó thì sẽ thành nanoservice
Trong các môi trường như Beam, JVM, Rust hay Go thì đây là vấn đề đã được giải quyết
Có phải là vấn đề cache CPU không?
Tôi cứ nghĩ thường sẽ dùng Go, Java hoặc C#
Ở phần lớn công ty, microservices thực ra là nguyên nhân của 90% vấn đề
Nếu không phải tổ chức khổng lồ như AWS, Google hay Netflix thì nó không phù hợp
Việc tự thân chia hệ thống thành các đơn vị có thể lắp ghép đã là khó, lại còn thêm ranh giới mạng vào thì càng ngớ ngẩn
Tôi nghĩ xu hướng tiếp theo sẽ là rời khỏi React hay SPA để quay sang mô hình lấy server làm trung tâm
Lý do chuyển sang microservices lại là “vì test hay bị vỡ”, nghe như một cách tiếp cận ngược đời
Test bị vỡ mà đi thay đổi hoàn toàn cấu trúc codebase thì thật kỳ lạ
Tách riêng VM và cấu hình CI/CD theo nhóm thì xung đột test biến mất
Nhược điểm là không phát hiện ngay xung đột giữa các tính năng, nhưng vì quyền sở hữu mã rõ ràng nên không phải vấn đề lớn
Có người yêu cầu thêm [2018] vào tiêu đề
Họ nói đã tách repo vì “nếu test vỡ thì phải sửa cả những đoạn mã không liên quan”, nhưng
có vẻ vẫn có những cách giải quyết khác như thay đổi cách chạy test hoặc cho phép triển khai thủ công
Tách repo không phải là lời giải duy nhất