- Ngay cả với lập trình CGI, vẫn có thể xử lý hơn 200 triệu yêu cầu web mỗi ngày
- Nhờ hiệu năng phần cứng được cải thiện gần đây, các nhược điểm của mô hình CGI đã giảm đi đáng kể
- Chương trình CGI sử dụng Go và SQLite cho thấy hiệu năng vượt trội trên CPU 16 luồng
- CGI cung cấp cấu trúc đặc biệt phù hợp để tận dụng nhiều lõi CPU
- Nhờ công nghệ hiện đại, cách phát triển ứng dụng web trong quá khứ cũng cho thấy hoàn toàn có tính thực tiễn
Quá khứ và hiện tại của CGI
- Vào cuối thập niên 1990, tác giả bắt đầu phát triển web bằng CGI và khi đó đã dùng các hệ thống như NewsPro
- CGI tạo ra overhead lớn vì với mỗi yêu cầu web lại phải lặp lại việc khởi chạy và kết thúc tiến trình mới
- Vì lý do đó, các công nghệ thay thế hiệu quả hơn như PHP, FastCGI đã được phát triển
Sự phát triển của hiệu năng phần cứng
- Trong hơn 20 năm qua, tốc độ và hiệu năng máy tính đã tăng mạnh
- Năm 2020, tác giả sử dụng các công cụ được viết bằng Go và Rust (như ripgrep) và khám phá lại tính thực tiễn của cách tiếp cận dựa trên việc khởi chạy tiến trình
Ưu điểm của cách làm CGI hiện đại
- Nếu triển khai CGI bằng các ngôn ngữ có tốc độ thực thi nhanh như Go và Rust, phần lớn nhược điểm của CGI kiểu cũ sẽ được giải quyết
- Chương trình CGI hoạt động như một tiến trình riêng cho mỗi yêu cầu, nên rất tối ưu để tận dụng CPU đa lõi
- Ví dụ, trong môi trường 16 luồng có thể đạt hơn 2400 yêu cầu/giây = khả năng xử lý hơn 200 triệu yêu cầu/ngày
- Máy chủ cỡ lớn có thể cung cấp hơn 384 luồng CPU
Góc nhìn về văn hóa phát triển
- Hiện nay, với sự xuất hiện của các ngôn ngữ như Go và Rust, mô hình CGI của thập niên 1990 có thể lại mang ý nghĩa mới
- Tuy vậy, đây vẫn không phải là cách phù hợp cho mọi môi trường và cũng không được khuyến nghị như phương pháp chủ đạo
- Điểm quan trọng là ở thời điểm hiện tại, bằng thực nghiệm có thể chứng minh CGI không còn là một giải pháp kém hiệu quả như trước nữa
Kết luận
- Nhờ phần cứng hiện đại và sự hỗ trợ của các ngôn ngữ nhanh, lập trình CGI cho thấy hiệu năng không thể so sánh với quá khứ
- Đây là một ví dụ có thể tận dụng tối đa ưu điểm của mô hình đa tiến trình, mang lại nhiều gợi mở thú vị cho các nhà phát triển web
1 bình luận
Ý kiến trên Hacker News
Dạo này ngay cả với Python, CGI cũng cho cảm giác hiệu năng khá nhanh
Ngay cả khi script CGI dùng 400 mili giây CPU lúc khởi động, nếu máy chủ có 64 lõi thì vẫn xử lý được 160 yêu cầu mỗi giây, tương đương 14 triệu lượt truy cập mỗi ngày trên mỗi máy chủ
Nghĩa là với lưu lượng hàng trăm triệu yêu cầu mỗi ngày (không tính tài nguyên tĩnh), việc khởi động tiến trình CGI không hẳn là nút thắt cổ chai
Trước đây tôi nghĩ đây là kiểu công nghệ "ổn định đến mức nhàm chán" nên lúc nào cũng có trong thư viện chuẩn Python, nhưng những người bảo trì Python hiện nay lại có xu hướng không mấy tích cực với tính ổn định và tương thích ngược
Vì vậy những module quá "nhàm chán và ổn định" đang bị loại khỏi thư viện chuẩn, và thực tế module
cgiđã bị xóa ở phiên bản 3.13Có lẽ do thói quen dùng Python để tạo prototype suốt gần 25 năm, nhưng giờ tôi bắt đầu thấy hối tiếc
Tâm trạng hiện tại là đang phân vân giữa JS và Lua
Liên kết giải thích chính thức về việc loại bỏ
cgilà PEP 594 cgiTừ đó dẫn tới PEP 206 được viết từ năm 2000 (25 năm trước), nơi đã mô tả rằng "gói cgi có thiết kế không tốt và cũng khó đụng vào"
Có thể xem kho jackrosenthal/legacy-cgi để biết về một drop-in replacement thay thế nguyên xi module trong thư viện chuẩn
Các nhà phát triển Python chỉ loại bỏ module mang tên
cgiViệc triển khai script CGI vẫn được hỗ trợ trong
CGIHTTPRequestHandlercủa modulehttp.serverCũng cần nhắc rằng module
cgivốn chỉ có vài hàm để phân tích dữ liệu form HTMLTôi hiểu việc chỉ trích Python vì loại module
cgikhỏi thư viện chuẩn, nhưng JS thường được đem ra làm lựa chọn thay thế thì ngay từ đầu lại còn chẳng có thư viện chuẩnLua cũng không có module CGI trong stdlib
Cá nhân tôi thích PHP hoặc JS hơn
Trong các trường hợp như thế này, việc JIT có sẵn ngay trong hộp là một điểm tiện lợi
Tôi dùng Python từ 1.6 tới nay nhưng chủ yếu chỉ cho scripting ở mức hệ điều hành
Ngày xưa tôi từng tích hợp Tcl với module Apache hoặc IIS, rồi cứ lặp đi lặp lại chuyện phải viết lại module bằng C (1999~2003)
Nếu một script CGI dùng 400 mili giây CPU thì thời gian phản hồi của endpoint đó cũng sẽ tối thiểu chừng ấy, nên trải nghiệm sử dụng sẽ bị ảnh hưởng
Gần đây tôi chạy
golangbinary, rabbitmq, redis và MySQL trên một mini server giá 350 USD, và duy trì được 5.000 req/s ngay trên cùng một máyTức là trong 24 giờ có thể xử lý 400 triệu yêu cầu
Điều đó khiến tôi cảm nhận rõ là các công cụ miễn phí ngày nay thật sự rất tuyệt
Dù vậy tôi vẫn thấy chi phí cloud quá cao
Tất nhiên khó so sánh 1:1, nhưng cảm giác được tự tay phát triển và tuning trên một máy chủ dưới tầng hầm nhà mình vẫn rất thỏa mãn
Có những trường hợp dùng cả một khối microservice dựa trên Kubernetes làm tốc độ phát triển chậm đi 10 lần
Nhiều người dường như không nhận ra rằng máy chủ không phải là cỗ máy chỉ xử lý được 1 yêu cầu mỗi giây
Thực tế là họ đang trả một mức overhead quá mức chỉ vì Google làm như vậy nên cũng bắt chước theo
Tôi cũng nghĩ mình nên viết một bài về kiến trúc 'modular monolith' vốn đang rất hợp với đội của chúng tôi
Tôi từng định tự host side project ở nhà, nhưng rủi ro khá lớn như mất điện, ISP downtime, không truy cập từ xa được, ổ cứng hỏng...
Cuối cùng nếu tính cả thời gian của bản thân thì lợi ích kinh tế cũng không rõ ràng
Dịch vụ cloud tận dụng được lợi thế kinh tế theo quy mô nên thực ra vẫn là một lựa chọn hợp lý
Không nhất thiết phải dùng cloud, cũng có thể thuê dedicated server từ nhà cung cấp hosting
Dĩ nhiên vẫn có giới hạn về băng thông/lưu lượng
Cloud trở thành xu hướng chủ đạo là vì VC và nhà đầu tư có cổ phần trong các công ty đó, hoặc vì nỗi lo rằng "biết đâu lưu lượng sẽ bùng nổ vô hạn"
Những người bán hàng cloud rất khéo khai thác nỗi bất an của nhà đầu tư
Không phải ai cũng chỉ dùng cloud
Trong dịch vụ thực tế, lý do chi phí VM cao không phải vì cần compute mạnh mà vì cần dung lượng đĩa cục bộ rất lớn
Không cần năng lực tính toán cao, chỉ cần 4 ổ cứng 20TB và một CPU vừa phải là đã có thể hình dung ra những dịch vụ rất đáng kể
Trên cloud gần như không thể tìm được kiểu cấu hình như vậy
Nếu trong
cgi-bincần truy cập DB, thì sự bất tiện là mỗi lần đều phải tạo mới DB connection cho tiến trìnhNếu code chạy trong bộ nhớ như fastcgi thì không chỉ giảm thời gian khởi động, mà còn có thể dùng connection pool tới DB hoặc duy trì connection lâu dài cho từng thread
Khi chạy ở quy mô lớn, số lượng DB connection tăng quá nhiều sẽ khiến DB quá tải
Người ta vận hành nhiều tiến trình vì lý do kiểu như "Python là single-thread, Python chậm nên cần thêm nhiều tiến trình"
Cuối cùng lại phải tách shared connection pool ra ngoài tiến trình Python bằng thứ như pg bouncer và cần đủ kiểu tuning
Sau rốt tôi đã phải viết lại bằng một ngôn ngữ dễ kiểm soát hơn, hỗ trợ multithread và hiệu năng tốt hơn, và mọi thứ trở nên đơn giản hơn hẳn
Vì vậy rốt cuộc CGI đã tiến hóa thành mô hình giữ lại thông tin giữa các yêu cầu như fastcgi
Theo cách truyền thống, người ta cũng từng chạy một daemon độc lập để làm nhiệm vụ proxy, và nếu dùng Unix socket thì kết nối cũng hiệu quả hơn TCP/IP rất nhiều
Có ý kiến bảo nên dùng UDP
Với tôi,
inetdchính là CGI đúng nghĩaNhờ nó mà Internet trở nên thú vị hơn rất nhiều
Đã từng có thời mà tôi trực tiếp dùng
inetdđể chạy đủ loại shell script, thậm chí cả HTTP viết hoàn toàn bằng BashNhững VPS cũ, laptop không sao lưu hay không dùng version control giờ đều biến mất, nhưng đó là những ký ức rất vui
Deploy cũng đơn giản với Makefile +
scp, còn test thì có thể viết bằng script Bash dùngnetcatvàgrepĐúng là thời buổi đáng sống
Việc một ứng dụng hello world đạt 2400 rps theo tiêu chuẩn phần cứng hiện nay không tạo cảm giác ấn tượng lắm
Mà code cũng đâu có đơn giản hơn, nên tôi tự hỏi rốt cuộc đang hy sinh hiệu năng để đổi lấy điều gì
Nếu không cần xử lý quá 2000 rps thì cũng chẳng có vấn đề gì
Lập luận là số website cần mức lưu lượng như vậy thực ra rất ít
Xét về con số thì không cao, nhưng trong thực tế vẫn là quá đủ cho nhiều môi trường
Thậm chí còn có thể chịu được kiểu 'hug of death' mà các lập trình viên HN hay nhắc tới, tức là lưu lượng tăng đột biến trong một thời điểm
Ở công ty tôi đến giờ vẫn dùng cách dựng nhanh các webapp nội bộ đơn giản trong thư mục
cgi-binNếu dùng đơn giản thì hiệu quả phát triển rất cao
Ngay cả với CGI cũng không cần phải tự
printtrực tiếphttp/1.0, mà có thể dùngPythonwsgiref.handlers.CGIHandlerđể chạy bất kỳ ứng dụng wsgi nào dưới dạng script CGIVí dụ với Flask cũng đơn giản như dưới đây
Trong công việc thực tế, chúng tôi chạy script bằng plugin cgi của uwsgi
Cảm giác là đơn giản và linh hoạt hơn nhiều so với chạy
mod_cgitrong Apache hay lighttpdVì uwsgi chạy ở cấp hệ thống nên còn có thể tận dụng đầy đủ hardening và sandboxing của systemd
Ngoài ra, trong phần xử lý cgi của uwsgi còn có thể chỉ định interpreter cho từng loại file
Thời gian tới byte đầu tiên là 250~350ms, và với nhu cầu của chúng tôi thì hoàn toàn chấp nhận được
Tài liệu uwsgi về cgi
Việc
wsgiref.handlers.CGIHandlervẫn chưa bị deprecated là thông tin rất hữu íchThread liên quan được thảo luận hôm qua: liên kết
Gần đây khi dùng Apache cho side project, tôi thấy tính năng
.htaccessrất hữu ích nên đã dùngChỉ cần đặt file
.htaccesstrong bất kỳ thư mục nào là mỗi request sẽ nạp thêm cấu hình máy chủ tương ứngTài liệu chính thức về htaccess
Trước đây người ta khuyên nên tránh
.htaccessvì overhead hiệu năng do phải truy cập đĩa ở mỗi request, và tốt nhất là gom vào cấu hình chínhNhưng bây giờ SSD và RAM đều đã dư dả, nên dẫu hiệu năng có thiệt đi một chút thì CPU cũng đủ mạnh để trong đa số trường hợp có thể bỏ qua
[Dự án StaticPatch của tôi][https://github.com/StaticPatch/StaticPatch/tree/main] cũng đã áp dụng như vậy
Câu nói nổi tiếng của người tạo ra PHP, Rasmus Lerdorf
"Tôi không phải lập trình viên thực thụ, tôi chỉ làm cho nó chạy rồi đi tiếp. Lập trình viên thực thụ sẽ nói 'bị rò rỉ bộ nhớ nghiêm trọng thế này thì phải sửa'. Còn tôi thì chỉ restart apache mỗi 10 request thôi"
Sau đó PHP đã trải qua một hành trình dài, vượt qua những sai lầm ban đầu và phát triển rất mạnh
Cũng có giai thoại rằng ông từng nói: "PHP 8 càng ít code của tôi thì càng tốt"
Lẽ ra Apache nên theo dõi file system và chỉ đọc khi có thay đổi, tôi không hiểu vì sao lại khiến nó phải thực hiện truy cập đĩa không cần thiết ở mỗi request
Kết quả là 99,99% request HTTP đều bị chậm đi
Gần đây trong workflow của tôi, tôi cũng cân nhắc kiểu cấu trúc này cho việc tạo prototype nhanh
Với các ngôn ngữ JIT, nếu không theo kiểu fastcgi thì khâu import sẽ trở thành nút thắt
Web server h2o mà tôi từng dùng có file cấu hình handler cho mruby và fast-cgi rất đơn giản, nên đặc biệt phù hợp cho công việc script cục bộ
Tài liệu fastcgi của h2o
Một ưu điểm nữa là nó hữu ích khi cho phép khách hàng tự thêm mã tùy biến để mở rộng phần mềm cục bộ
Ví dụ trước đây muốn mở rộng thì phải dùng MCP, còn giờ chỉ cần triển khai các request có cấu trúc bằng CGI là xong
Nếu dùng ở môi trường end-user thì việc nối chương trình CGI vào phía trước của MCP cũng là một ý tưởng đáng cân nhắc
Bản thân dịch vụ MCP có lẽ cũng hoàn toàn có thể được triển khai bằng CGI
Tôi thấy cần xem kỹ đặc tả hơn
Tôi thắc mắc liệu fastcgi có làm mất đi gần như mọi ưu điểm mà cgi vốn có hay không
Trước đây tôi từng trực tiếp dùng kết hợp chương trình C và CGI
Hồi đó không có hơn 100 lõi hay RAM dư dả, và mọi thứ vẫn chạy với tối đa khoảng 1GB bộ nhớ
Nghĩ đến những gì khi đó còn làm được, tôi tin rằng bây giờ mọi chuyện hẳn còn dễ hơn nhiều
Sau này khi cần load balancing thì họ gặp khó khăn trong việc mở rộng, nhưng trước đó mô hình này hoạt động khá tốt
Nhân tiện, frontend và backoffice khi đó là 2 file thực thi riêng biệt