7 điểm bởi GN⁺ 2025-07-07 | 4 bình luận | Chia sẻ qua WhatsApp
  • 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 ApacheGo 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

 
kansm 2025-07-09

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..

 
tujuc 2025-07-08

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...

 
GN⁺ 2025-07-07
Ý 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

      • Môi trường không lưu lại trạng thái, rất dễ tạo core dump và debug, và việc mở rộng chủ yếu theo mô hình request tuyến tính cũng khá đơn giản
      • Ca ngợi sự giản dị của việc chỉ cần đọc từ stdin và ghi ra stdout; Websockets có làm tăng độ phức tạp đôi chút nhưng chưa đến mức đáng lo
      • Nhắc lại là cùng với sự trỗi dậy của Java, việc chuyển mạnh sang application server đã diễn ra để tránh chi phí của fork() và sự nguy hiểm của C; giờ đây có thể quay trở lại với sự đơn giản
      • Dù không thích Rust, nhưng nếu đến thời mà kiểu code backend web như vậy có thể được viết dễ dàng, thì nó cũng sẽ rất hấp dẫn với cả lập trình viên node/js, php và python
  • 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

      • Chi phí khởi động tiến trình của liên kết động như tải trình thông dịch PHP, các loại thư viện và phân tích file còn lớn hơn nhiều
      • Tin rằng dùng Go là cách làm đủ sức cạnh tranh ngay cả từ 25 năm trước
      • Nhấn mạnh việc mở cơ sở dữ liệu SQLite có hiệu năng gần tương đương với việc chuyển socket qua context switch, và nhanh hơn rất nhiều so với kết nối mysql từ xa
      • Khẳng định FastCGI vẫn là lựa chọn rất tốt cho cả các ứng dụng mới
    • 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

      • Trong tải cao thì các tiến trình chạy thường trú như FastCGI có lợi hơn
      • CGI cũng có thể xử lý tới 2.000 rps, nhưng FastCGI có thể đạt hiệu năng cao hơn nhiều
      • Chỉ cần thêm tiến trình máy chủ riêng và khởi động lại khi nâng cấp; khi hiệu năng quan trọng thì điều đó rất đáng giá
    • 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

      • Perl và Python có chi phí khởi động trình thông dịch và biên dịch khá lớn, còn Java trên thực tế còn chậm hơn
      • Đồng ý rằng AWS Lambda gần như là sự hồi sinh của mô hình CGI
      • Bây giờ có cảm giác như đã quay lại mô hình gần giống FastCGI được quản lý
      • Thấy tiếc vì đáng lẽ chỉ cần upload file thực thi là chạy được, nhưng lại có cả một làn sóng công nghệ thêm vào quá nhiều độ phức tạp
  • 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

      • Thấy ngạc nhiên vì có rất nhiều trường hợp người ta thêm các lớp trừu tượng lên trên công nghệ cốt lõi rồi từ đó thăng tiến lên vị trí Director/VP
  • Ư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

    • Một request có bug cũng không ảnh hưởng request khác nhờ cách ly tiến trình
    • Ngay cả vòng lặp vô hạn cũng không dẫn đến từ chối dịch vụ (DoS) nhờ lập lịch ưu tiên trước
    • Có thể dùng rlimit để buộc dừng các request chạy quá lâu
    • Có thể dùng cgroup để phân bổ công bằng tài nguyên bộ nhớ, CPU, IO đĩa/mạng theo từng tenant
    • Có thể giới hạn quyền truy cập cho từng request bằng namespace/jail và tách quyền
  • 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

      • Có nhắc rằng script kiểu #!/bin/tcc -run của nhánh tcc mob còn nhanh hơn perl 1,3 lần
      • Julia, Java VM, thread PHP cũng là những ví dụ có thời gian khởi động rất dài
      • Hiện tượng mọi người quen phụ thuộc theo quán tính vào “môi trường lớn”
      • Trong cộng đồng Lisp cũng lặp lại điều này bằng cách dùng image, và meme "emacs is bloated" cũng ra đời từ đó
      • Thời hoàng kim của Perl vào nửa sau thập niên 1990 thực sự có bầu không khí được CGI thúc đẩy
      • Hồi đó ngay cả getline cũng chưa là chuẩn, nên từng có thời người ta làm cả thư viện C bên thứ ba dài vài trăm đến vài nghìn dòng
      • Cuối cùng công nghệ được chọn theo "danh tiếng", và phần lớn việc học là do bạn bè giới thiệu
  • Nế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

    • Mang lại trải nghiệm tốt nhất trên trình duyệt desktop
    • Có thể xem luồng hoạt động thực tế trên web dựa trên dữ liệu traffic từ HN
  • 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

      • Trên thực tế độ trễ của hầu hết web API đều do truy vấn DB hoặc truy vấn mô hình ML quyết định
      • Các phần còn lại, kể cả dùng ngôn ngữ chậm như Python, cũng không đáng kể lắm
      • Nếu chỉ trả về dữ liệu ít thay đổi thì rất dễ chạm tới giới hạn của NIC
  • 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

    • Tò mò liệu trong thực tế kinh doanh có ai đang dùng kiểu này hay không
  • 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"

    • Dù các serverless function như Lambda có cơ chế bảo vệ riêng (micro VM v.v.), nhưng về bản chất có lẽ chỉ với CGI và điều chỉnh quyền hạn cũng đã có thể đi rất xa với ít độ phức tạp hơn nhiều
 
regentag 2025-07-07

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.