- Chương trình CGI từng được dùng rộng rãi trong giai đoạn đầu của web đã được xác nhận qua thực nghiệm rằng vẫn có thể đạt hiệu năng cao trên phần cứng hiện đại
- CGI xử lý request theo từng process, nên việc quản lý bộ nhớ được thực hiện tự động và việc triển khai có ưu điểm là rất đơn giản
- Kết quả benchmark chứng minh rằng ngay cả trên một máy chủ CPU 16 luồng thông thường, vẫn có thể xử lý hơn 2.400 request mỗi giây, tức hơn 200 triệu request mỗi ngày
- Mã ví dụ guestbook.cgi được viết bằng Go và SQLite cùng Dockerfile đã được công bố dưới dạng mã nguồn mở
- Dù CGI ngày nay không còn phổ biến, bài thử nghiệm cho thấy đây vẫn có thể là một lựa chọn thực dụng và hiện đại
Chương trình CGI và nguyên lý hoạt động
- Vào đầu những năm 2000, chương trình CGI (Common Gateway Interface) là cách chủ yếu để xây dựng website động
- Phần lớn được viết bằng Perl hoặc C, và đôi khi C được chọn để cải thiện hiệu năng
- Khái niệm của CGI đơn giản nhưng mạnh mẽ
- Web server thiết lập metadata của request trong các biến môi trường (HTTP header, query, v.v.)
- Tạo một process riêng để chạy chương trình CGI
- Chuyển phần thân request qua stdin
- Ghi nhận stdout của chương trình làm phản hồi HTTP
- Chuyển đầu ra stderr vào log lỗi của server
- Khi chương trình xử lý xong request, process kết thúc nên file descriptor và bộ nhớ được giải phóng tự động
- Từ góc nhìn của lập trình viên, việc triển khai phiên bản mới cũng cực kỳ đơn giản, chỉ cần chép file vào thư mục
cgi-bin/ là xong
Hug of death (bùng nổ lưu lượng)
- Vào đầu những năm 2000, đa số web server chỉ có 1~2 CPU và 1~4GB bộ nhớ là phổ biến
- Do đặc tính Apache web server fork process
httpd cho mỗi kết nối, nhu cầu bộ nhớ tăng lên khi có nhiều kết nối đồng thời
- Rất khó vượt quá 100 kết nối đồng thời, và chỉ cần được một trang nổi tiếng đặt link là server cũng dễ dàng quá tải
- ( Slashdot Effect : khi đó, nếu một liên kết được đăng lên Slashdot nổi tiếng, lưu lượng sẽ đổ dồn tới. Tương tự như việc lên top Hacker News ngày nay)
CGI trong môi trường server hiện đại
- Hiện nay đã xuất hiện cả server có 384 luồng CPU, và ngay cả VM cỡ nhỏ cũng có thể cung cấp 16 CPU
- Hiệu năng CPU và bộ nhớ đã tăng mạnh
- Vì chương trình CGI chạy trên process riêng nên có thể tận dụng multicore một cách tự nhiên
- Chính vì vậy, tác giả đã trực tiếp benchmark để xem CGI nhanh đến mức nào trên phần cứng hiện đại
- Thử nghiệm được thực hiện trên máy chủ AMD 3700X (16 luồng)
Các kết quả benchmark chính
- Một chương trình CGI đơn giản được thử nghiệm trong cả môi trường Apache và Go
net/http server
-
Giải thích về chương trình guestbook.cgi
- Dùng công cụ tạo tải HTTP
plow để gửi 100.000 request với 16 kết nối
- Ngay cả trên phần cứng thông thường cũng có thể xử lý hơn 2.400 request mỗi giây, tức hơn 200 triệu request mỗi ngày
- Dù CGI hiện không còn là xu thế chủ đạo, nó vẫn có thể được dùng để vận hành dịch vụ thực tế
-
Benchmark ghi trong môi trường Apache
- Xử lý khoảng 2.468 request/giây, độ trễ phản hồi trung bình 6,47ms
- Xử lý 100.000 POST request chỉ trong 40,5 giây
- Phần lớn request phản hồi trong vòng 7ms, chỉ một số cực ít vượt quá 100ms
- Chứng minh hiệu năng xử lý ghi ở mức cao trong thực tế
-
Benchmark đọc trong môi trường Apache
- Xử lý khoảng 1.959 request/giây, độ trễ phản hồi trung bình 8,16ms
- Xử lý 100.000 GET request trong 51 giây
- Hơn một nửa request hoàn tất trong vòng 8ms, và độ trễ tối đa cũng chỉ 31ms
- Hiệu năng đọc cũng đủ tốt
-
Benchmark ghi trong môi trường Go net/http
- Xử lý khoảng 2.742 request/giây, độ trễ phản hồi trung bình 5,83ms
- Xử lý 100.000 POST request trong 36,4 giây
- Throughput trung bình là 2.742 RPS, độ trễ trung bình 5,8ms, tốt hơn Apache về mặt số liệu
- Hơn 95% request được xử lý trong vòng 6ms
- CGI trong môi trường Go cũng có hiệu năng thực chiến hoàn toàn đủ dùng
-
Benchmark đọc trong môi trường Go net/http
- Xử lý khoảng 2.469 request/giây, độ trễ phản hồi trung bình 6,47ms
- Xử lý 100.000 GET request trong 40,4 giây
- Phần lớn request đều có thể được phục vụ trong vòng 7ms
- Cả throughput đọc lẫn tốc độ phản hồi đều tương đương hoặc tốt hơn Apache
Kết luận và liên kết
- Chương trình CGI trên phần cứng mới nhất có các ưu điểm như đồng thời tốc độ cao, triển khai đơn giản, hệ điều hành tự động giải phóng tài nguyên
- So với các framework hiện đại, nó cực kỳ đơn giản, nhưng với các dịch vụ ở một quy mô nhất định thì hiện nay vẫn có thể được dùng trong thực tế
- Ví dụ sổ lưu niệm và dữ liệu benchmark được công khai trên GitHub bên dưới
https://github.com/Jacob2161/cgi-bin
4 bình luận
Trời.. lại sắp phải dùng lại cgi sao?? haha
Wow.. đúng là cgi từ thời nào rồi..
Có nội dung được cập nhật tính đến ngày 7/7.
Serving a half billion requests per day with Rust + CGI
Tận 500 triệu request sao...
Ý kiến trên Hacker News
Nhớ lại môi trường mà ngay cả trong thập niên 1990, các chương trình CGI viết bằng C cũng cho tốc độ thực sự rất nhanh, nhưng thừa nhận nhược điểm là lỗi xảy ra khá nhiều; các ngôn ngữ hiện đại như chương trình Go hay Nim được nhắc trong bài, miễn là không kết nối cơ sở dữ liệu, thì trên localhost có cảm giác cực nhanh và độ trễ thấp, giống như dùng fork & exec trong tiện ích CLI; so với độ trễ mạng thì chi phí gần như không đáng kể
Tuy vậy cũng nhắc đến văn hóa dễ bị “nghiện” một công nghệ cụ thể; ví dụ khi đã quen với các ngôn ngữ có chi phí khởi động lớn như trình thông dịch Python thì sẽ cần mô hình multi-shot hoặc thường trú
Mô hình one-shot của HTTP thời kỳ đầu bắt nguồn từ vấn đề bộ nhớ không đủ để máy chủ FTP duy trì hàng trăm phiên đăng nhập nhàn rỗi trong thời gian dài
Có ý kiến cho rằng nếu kết hợp pre-forking trong CGI (có thể che giấu độ trễ) với ngôn ngữ an toàn như Rust thì có thể tạo ra thiết kế hệ thống xuất sắc; phần kết thúc TLS có thể xử lý ở máy chủ web đa luồng (hoặc lớp như CloudFront), nhờ đó càng tiện lợi hơn
Bắt đầu phát triển từ thời CGI nên từng có ác cảm rất mạnh với việc chạy các subprocess sống ngắn
Giải thích bối cảnh PHP và FastCGI ra đời để thoát khỏi vấn đề hiệu năng khi tạo tiến trình mới cho mỗi request web
Gần đây mới nhận ra nhờ phần cứng phát triển mà chi phí khởi động tiến trình thực ra không còn là vấn đề lớn
Nhắc đến benchmark này có thể xử lý 2000 request mỗi giây, và ngay cả khi chỉ xử lý vài trăm thì trong môi trường hiện đại vẫn rất dễ mở rộng bằng nhiều instance
Đồng ý với ý kiến mô tả AWS Lambda là sự tái sinh của mô hình CGI; thấy đây là một phép so sánh khá phù hợp
Có nhắc rằng nếu ngày xưa triển khai script CGI dưới dạng binary C liên kết tĩnh, lại còn chú ý đến kích thước, thì có lẽ đã bớt thất vọng hơn
CGI trong môi trường tải thấp không phải gánh nặng lớn về tiền bạc hay hiệu năng
Trước khi Go xuất hiện, vào thập niên 2000 việc làm chương trình CGI bằng C/C++ vừa kém an toàn vừa khó phát triển
Ngày nay là thời đại mà máy chủ có 384 luồng CPU, thậm chí VM nhỏ cũng có thể có 16 CPU
Nếu phát triển bằng Kestrel trên loại phần cứng đó thì xử lý hàng nghìn tỷ request mỗi ngày cũng không khó
Có thể mang lại trải nghiệm phát triển tương tự PHP bằng toán tử string interpolation
Dùng LINQ và String.Join() để template hóa bảng HTML và các phần tử lồng nhau một cách đơn giản
Điểm thực sự khó là biết cách tránh bãi mìn trong hệ sinh thái như MVC/Blazor/EF
Cũng có thể chạy toàn bộ chương trình như một file cấp cao nhất từ CLI, nhưng nếu không biết từ khóa "Minimal APIs" thì rất dễ lạc vào mê cung tài liệu sai hướng
Ưu điểm của CGI là trong môi trường multi-tenant không cần xây lại các primitive cách ly
Nhờ các script CGI mà perl đã được tối ưu cho thời gian khởi động nhanh
Khi chạy lệnh
time perl -e '', perl mất 5ms, python3 là 33ms, ruby là 77ms, cho thấy thời gian khởi động rất nhanh của perl#!/bin/tcc -runcủa nhánh tcc mob còn nhanh hơn perl 1,3 lầnNếu dùng apache tomcat 11 thì chỉ cần upload file .jsp hoặc cả ứng dụng java servlet (.war) bằng ssh là chạy ngay
Tận dụng một JVM dùng chung để đạt hiệu năng tối đa
Pool kết nối DB, cache v.v. cũng có thể được chia sẻ giữa các ứng dụng
Một trải nghiệm thực sự ấn tượng
Tất nhiên còn tùy theo mẫu sử dụng thực tế
Rất tuyệt cho dịch vụ lớn, nhưng nếu có 50 ứng dụng nhỏ mà mỗi ứng dụng chỉ xử lý vài trăm lượt mỗi ngày, thì overhead bộ nhớ của Tomcat là quá lớn so với Apache/Nginx dựa trên script CGI
Có cảm giác nhớ thời chỉ cần sao chép file là triển khai xong
Bày tỏ tiếc nuối vì không hiểu sao quy trình triển khai lại trở nên phức tạp đến thế
Chia sẻ rằng hiện giờ vẫn đang dùng Jetty cho backend web một cách rất hài lòng
Cảm nhận rằng stack Tomcat/Jakarta EE/JSP hóa ra khá vững chắc
Có thể trộn HTML và code như PHP, hoặc cũng có thể dùng route Java thuần
Hỗ trợ Websockets, mô hình single-process multi-thread nên cũng mạnh cho giao tiếp thời gian thực
Nếu cần thì có thể chia sẻ dữ liệu giữa các request, còn code JSP về cơ bản bị giới hạn trong phạm vi request
Triển khai thực sự dễ; chỉ cần upload file mới vào thư mục webapps là Tomcat tự động nạp app mới và gỡ app cũ
Nhược điểm là có thể gặp rò rỉ classloader khiến garbage collection thất bại, đúng kiểu số phận của mô hình single-process
Tạo công cụ trực quan hóa cho request apache ibrahimdiallo.com/reqvis
Vốn đã nghi ngờ việc hiện nay mọi thứ đang đi theo kiến trúc quá phức tạp; có lẽ với phần cứng tốt thì vẫn đủ khả năng dùng công nghệ cũ
Khi được hỏi cách thiết kế hệ thống thông báo giá cổ phiếu thời gian thực cho hàng triệu người, ban đầu đã nghĩ tới cấu trúc stream phức tạp như Kafka, pubsub..., nhưng rồi cuối cùng cũng cân nhắc phương án đơn giản là đặt file tĩnh trên máy chủ
Tò mò về chi phí vận hành thực tế của cách làm này
Nhấn mạnh rằng nó giống kiến trúc serverless, nhưng đơn giản và rẻ hơn nhiều
Thấy tiếc vì người ta không xem xét lại cấu trúc truyền thống này mà chỉ tạo ra một “mô hình mới” gọi là "serverless functions"
Cgi thì còn tạm hiểu được, nhưng phản ứng về JSP mới thật sự gây ngạc nhiên haha
JSP đã trở thành một di vật cổ đại đến mức đó rồi sao.