12 yếu tố tạo nên 1 web app xịn xò – Lược dịch và giải thích Twelve-Factor – Phần 3

Như đã giới thiệu ở bài trước, mình sẽ giới thiệu về twlve-factor app. Đây là 12 yếu tố cần thiết để xây dựng 1 ứng dụng “xịn xò”, ổn định, dễ mở rộng, dễ deploy.

Trong bài này, mình sẽ giải thích các yếu tố từ 8 tới 12 nhé:

  • 8. Concurrency: Một app nên được chia tách thành nhiều process nhỏ để tăng concurrency
  • 9. Disposability: Process của web app nên sống nhanh, chết nhẹ nhàng, để có thể dễ dàng chạy/kill process nhanh chóng
  • 10. Dev/prod parity: Các môi trường dev/staging/production nên giống nhau hết sức có thể
  • 11. Logs: Logs nên được viết ra dạng stream ở stdout
  • 12. Admin Processes: Một số task dạng admin (tạo database, fix dữ liệu) nên được chạy trong cùng môi trường với app đang chạy

 

Đây là phần 3 trong series 3 phần về Twelve-Factor App:

  1. Lược dịch và giải thích Twelve-Factor. Giải thích Codebase và Dependencies
  2. Giải thích Config, Backing Service, Build -> Release -> Run, Processes, Port Binding
  3. Giải thích Concurrency, Disposability, Dev/Prod Parity, Logs, Admin Processes

8. Concurrency – Một app nên được chia tách thành nhiều process nhỏ để tăng concurrency

Trong các ngôn ngữ Java, C#, ta thường quản lý concurrency (chạy song snog) thông qua Thread/Task. Các web app này thường được chạy trong 1 process siêu to khổng lồ, sử dụng thread để xử lý nhiều cùng lúc.

Theo khuyến khích của Twelve-Factor App, do CPU và RAM không thể scale tới vô hạn. Vì vậy, để ứng dụng dễ scale, ta nên scale bằng cách chạy nhiều process. Khi cần ta có thể dễ dàng chạy thêm process, hoặc thêm server rồi chạy process của ứng dụng trong server mới.

Ngoài ra, ta cũng nên tách biệt process theo chức năng. Ví dụ những tác vụ hiển thị trang web, cần xử lý song song nhiều, đưa kết quả nhanh cho user thì ta dùng web process. Những tác vụ chạy ngầm (encode file, tính toán nhiều, gọi API ngoài) tốn nhiều thời gian thì ta có thể cho vào worker/background process.

Nếu các bạn từng deploy ứng dụng NodeJS trên Production, chạy bằng PM2, bạn sẽ thấy nó cũng đi theo mô hình này. PM2 sẽ tạo ra nhiều process NodeJS để scale khi cần.

9. Disposability Process của web app nên khởi động nhanh, chết nhẹ nhàng

Chuyện kể rằng, ngày xửa ngày xưa, có những dự án Java/Rails siêu to khổng lồ, với những process kinh khủng tớm phải, mỗi lần khởi động phải mất tận 1-2 tiếng mới chạy lên được. Hệ quả là mỗi lần cần release code mới, hoặc phải sửa chữa/nâng cấp server, cả team dev và Operation phải đóng cửa/bảo trì tận 3-4 tiếng để nâng cấp.

Mỗi lần cần nâng cấp hệ thống là lại downtime cả nửa ngày trời

Do vậy, process của 1 web app nên khởi động càng nhanh càng tốt (vài giây hoặc vài phút). Nhờ vậy, khi cần release phiên bản mới, scale hệ thống, ta có thể dễ dàng chạy lại process, hoặc chạy process trên server mới.

Ngoài ra, process cũng phải chết nhẹ nhàng. Khi hệ thống ra lệnh cho nó chết, nó sẽ làm xong việc (hoặc ngừng) rồi chết chứ không dây dưa. Ví dụ như web process, khi nhận được SIGTERM signal sẽ tự động tắt exit.

Với các process chạy ngầm thì vấn đề này nan giải hơn tí. Ví dụ process đã nhận 1 task từ message queue, nhưng đang xử lý thì … bị giết. Trước khi chết, nó phải tìm cách đưa task đó lại queue, nếu không task đó sẽ không bao giờ được xử lý; dẫn đến lỗi hệ thống, mất dữ liệu.

10. Dev/prod parity Các môi trường dev/staging/production nên giống nhau hết sức có thể

Một hệ thống trên production thường có rất nhiều thành phần: Web Server, Database, Cache Server, Message Queue, …

Để chạy ứng dụng ở local/staging, dev cũng phải chạy những thứ này, sử dụng version khác hoặc bản nhẹ hơn, mới hơn. Điều này dẫn đến 1 số bug … lạ, chỉ xảy ra ở production mà không xảy ra ở dev, staging.

Nên đảm bảo các môi trường giống nhau hết mức có thể

Do vậy, ta nên đảm bảo các công nghệ sử dụng ở môi trường dev/staging giống production hết sức có thể! Ví dụ, Production dùng .NET 6.0 + Postgres 12 thì dev/staging cũng nên vậy, đừng đú đởn .NET 7.0 + Postgres 13 để tránh lỗi thư viện, bug không đáng có.

11. Logs – Log nên được viết ra dưới dạng stream và stdout

Logging là một công cụ đơn giản và mạnh mẽ, ghi lại toàn bộ những hoạt động của hệ thống. Nhờ có logging, ta có thể tra cứu lại trạng thái của hệ thống trong quá khứ, những code nào đã được chạy, từ đó tìm ra lỗi và fix dễ dàng hơn.

Mỗi ngôn ngữ như Java, C#, PHP đều có 1 số thư viên phổ biến để quản lý log như log4net, log4j, serilog, … Các thư viện này hỗ trợ ghi log ra file, thiết lập các level log (debug, info, warning).

Twelve-Factor app khuyên rằng ứng dụng không nên tự quản lý log của nó, chỉ gần ghi ra dưới dạng stream trong stdout (giống ghi ra console ấy).

Sau đó, những log này sẽ được các log router như LogplexFluentd xử lý thêm bằng cách lưu vào file, gửi tới nguồn khác để lưu trữ, hiển thị trong dashboard v…v

elk-stack
Các bạn cũng có thể tìm hiểu thêm về ELK Stack để quản lý log

Cá nhân mình thấy lời khuyên này chỉ hữu ích nếu bạn deploy app lên cloud/heroku/Kubernetes. Nếu các bạn chạy app trên VPS hay server thì cứ log file như thường hoặc dùng ELK cũng được nhé.

12. Admin Processes Một số task dạng admin (tạo database, fix dữ liệu) nên được chạy trong cùng môi trường với app đang chạy

Trong quá trình phát triển app, đôi khi bạn sẽ cần chạy 1 số task dạng admin, chỉ chạy 1 lần như sau:

  • Migrate database và dữ liệu (thêm/sửa table hoặc cột). (Chạy rake db:migrate hoặc npm run migrate)
  • Chạy script để sửa dữ liệu, xoá bỏ dữ liệu thừa (python scripts/fix_money.py hoặc node scripts/fix.js)
  • Truy cập REPL của Python, Rails để kiếm tra dữ liệu, chạy thử hàm

Những script này nên được để cùng với codebase để đảm bảo code của scripts luôn là code mới nhất! Mấy anh developer/opeator sẽ ssh vào server đang chạy code production để chạy các script này, đảm bảo script chạy ở cùng môi trường với app đang chạy.

Tạm kết

Như đã nói ở phần 1, 12 lời khuyên này chỉ là kinh nghiệm và khuyến cáo, phù hợp với các ứng dụng web chạy trên cloud, các app sử dụng kiến trúc microservice.

Với các ứng dụng cũ hơn, chạy trên server vật lý, bạn vẫn có thể áp dụng 1 số lời khuyên để ứng dụng chạy ổn định hơn, dễ quản lý và bảo trì hơn nhé.

 

Các nội dung mình chia sẻ trong bài hơi nặng tính chuyên ngành, đôi khi khó hiểu nếu bạn chưa áp dụng vào dự án thực tế. Do vậy, nếu có đoạn nào không hiểu, các bạn cứ comment để mình giải thích kĩ hơn nha!

4 thoughts on “12 yếu tố tạo nên 1 web app xịn xò – Lược dịch và giải thích Twelve-Factor – Phần 3”

  1. Mình thường xuyên làm việc với server và mô hình microservice nên 12 yếu tố này mình nắm tương đối. Tuy nhiên mình có một số góp ý cũng như một số thắc mắc chưa hiểu rõ lắm, ad tường minh giải thích mình với
    – 11. Mình chưa hiểu nguyên tắc này lắm, thường thì mình ghi log của app ra file sử dụng thư viện log4j2, quản lý log mình thường sử dụng một số lệnh thao tác trên file của hệ điều hành, liệu quy trình này đã đúng chưa?. Bạn giải thích rõ “Log nên được viết ra dưới dạng stream và stdout” giùm mình với

    – 12. Mình góp ý như thế này giúp mục này tường minh hơn liệu đã đúng chưa nhé. Những job thường mật độ sử dụng ít (như bài viết nêu), những job mà yêu cầu thay đổi xoành xoạch, job monitor, những job có thể thực hiện với hệ điều hành mang tính cơ động và hiệu năng cao như cron tab gửi mail, cron tab truyền file fpt,…, cast 1 file csv tầm chục GB vào mysql (không nên dùng dao mổ gà để giết trâu) thì nên thực hiện bằng ngôn ngữ python nhẹ nhàng hoặc shell trong môi trường chạy của app trực tiếp trên hệ điều hành

    – 9. Mình thích cách dùng từ chết nhẹ nhàng của bạn. Thường thì 1 hệ thống MQ sẽ có cơ chế khi app throw exception thì queque sẽ được hồi đáp. Mình có 1 app đọc queque nhưng khi app bị kill đột ngột mình bị lost cái queque đó, có cách nào xử lý vấn đề này ko bạn?. Mình chưa khi nào thấy app khởi động tới 1-2 tiếng lận, 5 phút là đã 1 khoảng thời gian quá dài, bạn có chém quá ko đó. Nếu có hệ thống khởi động chậm như vậy thì có thể app được thiết kế tồi nếu là mô hình microservice

    – 8. Quá hay. KO NÊN chạy 1 process lớn với nhiều thread MÀ NÊN chạy nhiều process. Mình bổ sung thêm tí
    Nếu process là 1 đầu mối api khi được scale lên 3 server để giảm tải cho hệ thống chẳng hạn thì phải có bộ định tuyến (routing) để quest từ người dùng tới được phân tải hợp lý vào 3 con server trên (cái này người quản trị hệ thống hoặc mạng sẽ lo, dev ko cần làm). Tuy nhiên process api này phải được thiết kế và đảm bảo tính nhất quán khi xử lý dữ liệu, ko được chồng chéo đá lên nhau, k gây ra deadlock và một số vấn đề khác

    Cảm ơn ad nhiều!

    Liked by 1 person

    1. hi bạn, cảm ơn góp ý của bạn, để mình giải thích thêm cho rõ ràng ha.

      – 11. Với các ứng dụng chạy trên server/VPS có thể viết log vào file, không vấn đề gì. Với các ứng dụng chạy trên môi trường cloud/container/kubernetes, việc access và xem file sẽ khó khăn và tốn công hơn nhiều. Do vậy viết log xuống stdout (như System.Print hoặc console.log) thì Docker, trình quản lý log của Cloud sẽ dễ quản lý, chuyển tiếp các log này ha.
      – 12. Việc quản lý Cron job thì cũng tuỳ vào chuyện ai viết job đó. Nếu đó là business logic, cần dev viết thì dev dùng sẵn thư viện tích hợp crob job của app. Nếu cron job dạng bảo trì, mấy ông Ops viết thì Python hay Shell cũng ok. Còn 1 số job dạng quản lý, truy cập dữ liệu, báo cáo do team data viết thì gần đây họ hay dùng Airflow, viết job Python rồi airflow tự lo, tách thành 1 hệ thống riêng luôn chứ ko phụ thuộc vào app gốc nữa.
      – 9. Phần đó đang nói tới các hệ thống Legacy từ thời Napoleon cơi truồng bạn nhé.

      Like

    2. À quên, đa phần các thư viên như log4J đều hỗ trợ ghi log ra nhiều nguồn, nên muốn viết log ra stdout chỉ cần sửa 1-2 dòng config là nó sẽ viết log ra cả file lẫn console ngay.

      Like

      1. “Docker, trình quản lý log của Cloud sẽ dễ quản lý, chuyển tiếp các log”. Bạn nói tới đây là mình hiểu rồi. Mình từng code hệ thống quản lý log tập trung nên mình hiểu cái config bạn nói. Appender ghi log còn có thể là database, queque.Tks ad!

        Like

Leave a comment