2 điểm bởi GN⁺ 2025-07-07 | 1 bình luận | Chia sẻ qua WhatsApp
  • Jake Gold chạy một chương trình CGI viết bằng Go + SQLite trên AMD 3700X 16 luồng để kiểm tra CGI kiểu thập niên 1990 có thể chịu tải đến mức nào trên phần cứng hiện đại
  • Kết quả cho thấy ngay cả trên phần cứng phổ thông, CGI vẫn có thể xử lý hơn 2.400 yêu cầu mỗi giây, tức hơn 200 triệu yêu cầu mỗi ngày
  • Vì CGI khởi động, chạy rồi kết thúc một tiến trình cho mỗi yêu cầu, trước đây chi phí overhead rất lớn; để giảm chi phí này, các cách tiếp cận như PHPFastCGI đã xuất hiện
  • Trong datasette-ripgrep năm 2020, Simon Willison gọi CLI ripgrep viết bằng Rust để xử lý tìm kiếm, qua đó thay đổi niềm tin lâu nay rằng nên tránh chạy tiến trình trong lúc xử lý yêu cầu web
  • Với các ngôn ngữ khởi động nhanh như Go và Rust cùng môi trường nhiều CPU, xử lý kiểu CGI có thể trở thành một lựa chọn thực tế hơn trước, nhưng không phải là cách được khuyến nghị nói chung

Nhìn lại hiệu năng CGI trên phần cứng hiện đại

  • Serving 200 million requests per day with a cgi-bin là bài viết của Jake Gold thử nghiệm hiệu năng CGI kiểu thập niên 1990 bằng chương trình CGI Go + SQLite
  • Môi trường thử nghiệm là hệ thống dùng AMD 3700X 16 luồng
  • Kết quả cốt lõi là ngay cả với CGI, phần cứng phổ thông vẫn có thể xử lý hơn 2.400 yêu cầu mỗi giây, tức hơn 200 triệu yêu cầu mỗi ngày
  • CGI hoạt động bằng cách khởi động một tiến trình riêng cho mỗi yêu cầu đến, chạy tiến trình đó rồi kết thúc
  • Cộng đồng web thời kỳ đầu đã tạo ra PHPFastCGI, vốn giữ mã thường trú trong bộ nhớ để giảm chi phí bổ sung, nhằm tránh overhead này

Vì sao CGI có thể không còn tệ như trước

  • Một trong những nút thắt cổ chai của CGI trước đây là các web script được viết bằng những ngôn ngữ như Perl, Python, Java, vốn không được thiết kế với mục tiêu khởi động cực nhanh
  • Ngày nay, nếu dùng GoRust, cách xử lý yêu cầu theo phong cách CGI có thể hoạt động hiệu quả hơn nhiều
  • Năm 2020, khi tạo datasette-ripgrep, Simon Willison đã gọi công cụ CLI ripgrep viết bằng Rust qua shell để thực hiện tìm kiếm và thu được kết quả tốt
  • Vì chương trình CGI chạy dưới dạng tiến trình riêng, nó cũng có thể phù hợp với kiến trúc tận dụng nhiều CPU
    • Máy chủ hiện đại có thể có 384 luồng CPU
    • Ngay cả VM nhỏ cũng có thể có 16 CPU
    • Hiệu năng CPU và bộ nhớ cũng đã nhanh hơn trước rất nhiều
  • Cách viết ứng dụng web kiểu năm 1998 cũng trở thành một đối tượng thử nghiệm thú vị khi gặp Go và Rust, nhưng không phải là lựa chọn mặc định mà hầu hết dịch vụ nên làm theo

1 bình luận

 
GN⁺ 2025-07-07
Các ý kiến trên Hacker News
  • Ngày nay CGI khá nhanh, kể cả khi dùng những thứ như Python
    Nếu khởi động một script CGI tốn 400ms CPU và máy chủ có 64 core, thì có thể xử lý 160 request mỗi giây, tức 14 triệu lượt hit mỗi ngày trên mỗi máy chủ
    Nếu một dịch vụ web không tính tài nguyên tĩnh mà còn chật vật với vài triệu request mỗi ngày, thì nút thắt không phải là thời gian khởi động tiến trình CGI
    Trước đây có lẽ tôi đã gọi nó là “một công nghệ nhàm chán đã được hỗ trợ lâu trong thư viện chuẩn Python”, nhưng những maintainer Python còn lại dường như xem sự ổn định và tương thích ngược là có hại, và đã loại bỏ khỏi thư viện chuẩn những module quá nhàm chán và ổn định
    module cgi đã bị loại bỏ trong Python 3.13
    Tôi đã dùng Python hằng ngày trong phần lớn 25 năm qua nên vẫn có thói quen dùng Python để tạo prototype, nhưng giờ thấy tiếc vì điều đó và đang phân vân giữa JS và Lua

    • Cơ sở cho việc loại bỏ cgi nằm ở https://peps.python.org/pep-0594/#cgi
      Điều thú vị là ở đó có liên kết tới https://peps.python.org/pep-0206/ ngày 14/7/2000, tức 25 năm trước, khi đó gói cgi cũng đã được mô tả là “thiết kế kém và giờ gần như không thể sửa được”
      Có vẻ gói https://github.com/jackrosenthal/legacy-cgi cung cấp một bản thay thế drop-in cho module trong thư viện chuẩn
    • Thứ các maintainer Python loại bỏ là module có tên cgi, chứ không phải bản thân việc hỗ trợ triển khai script CGI
      Hỗ trợ đó vẫn còn trong CGIHTTPRequestHandler của module http.server
      Những gì có trong module cgi chỉ là vài hàm để parse dữ liệu form HTML
    • Có thể hiểu được việc không hài lòng khi Python loại bỏ CGI khỏi thư viện chuẩn
      Nhưng tôi không chắc vì sao sau đó lại cân nhắc JS, vốn không có thư viện chuẩn đúng nghĩa, làm phương án thay thế
      Lua cũng không có module CGI trong thư viện chuẩn
    • Trước đây web hiệu năng cao là một dạng nghệ thuật kỹ thuật
      Giờ thì nó gần với việc tìm ra những phần lãng phí đến vô lý được đưa vào để phát hành nhanh, rồi dừng chúng lại
      Nếu làm đúng cách, ứng dụng có thể hầu như không thêm độ trễ nào ngoài việc truy cập storage
    • Trong những trường hợp như thế này, tôi thích PHP hoặc JS hơn vì mặc định có JIT
      Tôi học Python từ bản 1.6, nhưng chủ yếu dùng cho scripting hệ điều hành
      Giai đoạn 1999–2003, tôi dùng Tcl trong các module Apache và IIS, rồi liên tục viết lại module bằng C và đã phải học quá nhiều bài học theo cách khó khăn
  • Gần đây tôi thử chạy binary Golang, RabbitMQ, Redis, MySQL đều trên cùng một máy chủ mini giá 350 USD, và nó xử lý ổn định 5.000 request mỗi giây
    Quy đổi 24 giờ mỗi ngày là 400 triệu request
    Thật ngạc nhiên khi các công cụ miễn phí ngày nay tuyệt vời đến vậy, và cũng ngạc nhiên không kém khi chúng ta vẫn trả nhiều tiền như thế cho các nhà cung cấp cloud
    Đây không phải là so sánh hoàn toàn tương đương, nhưng quá trình phát triển và tinh chỉnh mọi thứ trên thiết bị dưới tầng hầm thật sự rất thú vị

    • Tôi đã thấy quá nhiều trường hợp triển khai một con quái vật Kubernetes microservices làm chậm tốc độ phát triển đi 10 lần, trong khi họ không biết rằng máy chủ có thể xử lý nhiều hơn rất nhiều so với 1 request mỗi giây
      Thật kỳ lạ khi cứ tiếp tục chịu khoản overhead này mà không có lý do nào ngoài “vì Google làm thế”
      Tôi nhất định phải viết một bài về kiến trúc monolith dạng module mà chúng tôi đang dùng rất hiệu quả
    • Không nhất thiết phải trả tiền cho nhà cung cấp cloud
      Bạn có thể thuê máy chủ dedicated từ một công ty hosting và dùng thoải mái. Chỉ là sẽ bị ràng buộc khá nhiều bởi giới hạn băng thông hoặc lưu lượng truyền tải
      Cloud được dùng vì có nhiều lợi ích liên quan. Ví dụ VC và nhà đầu tư cũng nắm cổ phần trong các công ty cloud, và họ sợ khoản đầu tư thất bại vì một đợt tăng vọt traffic tưởng tượng vốn thực tế sẽ không đến
      Đội bán hàng cloud rất giỏi đánh vào nỗi bất an của nhà đầu tư
    • Tôi từng nghĩ đến việc host side project dưới tầng hầm, nhưng như vậy sẽ dễ bị ảnh hưởng bởi mất điện và sự cố ISP
      Nếu ổ đĩa hỏng khi tôi đang đi du lịch thì bó tay, và nếu lỡ tự khóa mình ra ngoài thì cũng không có terminal serial dự phòng để fallback
      Cũng có thể cấu hình như dân r/homelab, nhưng khi đó không rõ liệu có thật sự tiết kiệm tiền hay không, nhất là nếu tính cả thời gian của tôi
      Kết luận lại, tôi cho rằng nhà cung cấp cloud là một thương vụ khá tốt nhờ lợi thế kinh tế theo quy mô
    • Nhìn vào chi phí VM, lý do nó cao không phải vì cần nhiều tính toán, mà vì cần máy chủ có nhiều đĩa cục bộ
      Lạ là không thể lấy riêng từng thứ một
      Hãy tưởng tượng có thể vận hành một doanh nghiệp hay dịch vụ lớn đến đâu chỉ với 4 ổ 20TB và một CPU vừa phải
      Không dễ có được cấu hình như vậy từ nhà cung cấp cloud
  • Phần nói rằng “cộng đồng web thời kỳ đầu đã nhanh chóng học được rằng đây là ý tưởng tồi và phát minh ra các công nghệ như PHP” thì nói chính xác, công nghệ cốt lõi là mod_php
    Bản thân PHP không khác Perl về cách thực thi, nhưng nhờ các lựa chọn thiết kế của mod_php so với mod_perl, script PHP có thể được ném thẳng lên server mà vẫn chạy nhanh, còn mod_perl thì cần một chút suy nghĩ và phép thuật để chạy được

    • Hồi đó tôi cùng một người bạn đang phát triển thứ mà về sau có thể gọi là hệ thống quản lý học tập
      Nó có quản lý nội dung, upload bài tập, lịch sự kiện, quản lý điểm, chat thời gian thực, forum, và tất cả đều là C thuần trên CGI, làm việc với nó đúng là địa ngục
      Ngày tôi biết đến PHP, tôi gần như muốn khóc, vì mọi thứ mà trước đó tôi phải vất vả viết từ đầu bằng cách đọc RFC hoặc reverse-engineer HTTP thì trong PHP chỉ là các lệnh gọi hàm đơn giản
      Tôi không còn phải debug một implementation urlencode vụng về hay mất cả ngày vì một ký tự carriage return kỳ lạ trong header HTTP nữa
    • Đúng, nhưng mod_php đã được thêm vào hệ sinh thái PHP từ rất sớm và nhanh chóng trở thành cách phân phối mặc định
      Có vẻ phiên bản đầu tiên của module Apache là dành cho PHP/FI Version 2.0 vào năm 1996: https://www.php.net/manual/phpfi2.php#module
  • Nếu cgi-bin cần truy cập DB thì mỗi lần tiến trình khởi động đều phải mở kết nối
    Việc giữ mã trong bộ nhớ như FastCGI không chỉ để tránh chi phí thời gian khởi động, mà còn vì có thể có pool kết nối DB, hoặc ít nhất là kết nối bền vững theo từng thread

    • Khi quy mô lớn lên, database sẽ chật vật vì số lượng kết nối
      Đã từng như vậy khi vừa làm “Python là single-thread nên cứ chạy nhiều instance” vừa làm “Python chậm nên cứ chạy nhiều instance”
      Khi quy mô lớn lên, cuối cùng phải dùng pool kết nối dùng chung bên ngoài Python như pgbouncer, cùng rất nhiều tinh chỉnh, để xử lý tải mà không làm DB chết
      Tất nhiên sau đó, khi triển khai lại bằng một ngôn ngữ đa luồng có hiệu năng ở mức ổn, mọi thứ lại trở nên rất đơn giản
    • Vì vậy rốt cuộc CGI đã tiến hóa thành mô hình duy trì một phần trạng thái giữa các request
    • Cũng từng có cách tiêu chuẩn để xử lý việc này
      Về cơ bản là chạy một daemon riêng đóng vai trò proxy
      Nếu dùng Unix socket thay vì TCP/IP thì chi phí kết nối tương đối thấp hơn
    • Dùng UDP là được
  • Trên phần cứng này mà ứng dụng hello world đạt 2.400 request/giây thì chẳng phải hơi tệ sao
    Cũng không rõ đánh đổi hiệu năng để lấy chính xác điều gì. Mã cũng đâu có đơn giản hơn

    • Chỉ là con số tệ khi cần vượt quá 2.000 request/giây
      Những website như vậy chỉ là một phần trong tổng số
    • Không xuất sắc, nhưng đủ cho nhiều trường hợp sử dụng
      Có vẻ cũng chịu được một đợt traffic bomb từ HN
  • Hôm qua cũng đã được thảo luận: https://news.ycombinator.com/item?id=44464272

  • Ở công ty, thỉnh thoảng chúng tôi vẫn phục vụ thư mục cgi-bin cho các web app nội bộ cần làm nhanh
    Nếu giữ mọi thứ đơn giản thì tính tiện dụng rất tốt
    CGI không có nghĩa là phải trực tiếp print HTTP/1.0 ra stdout
    Ví dụ, dùng wsgiref.handlers.CGIHandler tích hợp sẵn của Python thì có thể chạy bất kỳ ứng dụng WSGI nào như một script CGI
    import wsgiref.handlers, flask
    app = flask.Flask(name)
    wsgiref.handlers.CGIHandler().run(app)
    Chúng tôi chạy script bằng uwsgi và plugin CGI của nó[1]
    Cảm giác cách này đơn giản và linh hoạt hơn việc chạy Apache hoặc lighttpd chỉ vì mod_cgi
    Vì uwsgi chạy dưới dạng unit systemd, nên cũng có thể tận dụng toàn bộ các tính năng hardening và sandboxing của systemd
    Điểm tiện lợi trong xử lý CGI của uwsgi mà mod_cgi không có là có thể chỉ định interpreter dùng cho một định dạng file cụ thể
    cgi = /cgi-bin=/webapps/cgi-bin/src
    cgi-allowed-ext = .py
    cgi-helper = .py=/webapps/cgi-bin/venv/bin/python3 # all dependencies go here
    Thời gian đến byte đầu tiên là 250~350ms, chấp nhận được với mục đích của chúng tôi
    [1]: https://uwsgi-docs.readthedocs.io/en/latest/CGI.html

    • Thông tin hay, và có vẻ wsgiref.handlers.CGIHandler vẫn chưa bị lên kế hoạch loại bỏ
  • Gần đây tôi dùng Apache cho một side project, và một trong các lý do là tính năng .htaccess, nên cũng đã có cuộc trò chuyện tương tự
    Đặt file .htaccess ở bất cứ đâu thì Apache có thể đọc nó như cấu hình server bổ sung cho mỗi request: https://httpd.apache.org/docs/2.4/howto/htaccess.html
    Lý do lớn khiến người ta tránh việc này là hiệu năng. Mỗi request cần thêm một lần truy cập đĩa, và nếu có thể thì đưa vào file cấu hình chính luôn vẫn tốt hơn
    Nhưng hiện nay hầu hết server đều có SSD, và nhiều khả năng Linux cũng có RAM dư để dùng cho cache hệ thống file
    Tất nhiên Apache vẫn phải parse cấu hình cho từng request chứ không phải chỉ một lần, nên hiệu năng vẫn kém đi đôi chút
    Dù vậy, xét việc CPU server ngày nay phần lớn mạnh hơn, trong nhiều trường hợp sử dụng thì có thể chấp nhận được
    Side project này đang ở phiên bản rất sớm nhưng đã được dùng: https://github.com/StaticPatch/StaticPatch/tree/main

    • Trích lời Rasmus Lerdorf:
      “Tôi không phải lập trình viên thực thụ. Tôi cứ ghép thứ này thứ kia cho đến khi nó chạy rồi chuyển sang việc khác. Các lập trình viên thực thụ sẽ nói ‘Ừ, nó chạy đấy, nhưng chỗ này chỗ kia đang rò rỉ bộ nhớ. Chẳng phải nên sửa sao?’ Tôi thì chỉ cần khởi động lại Apache sau mỗi 10 request.”
      PHP đã tiến rất xa kể từ đó, nhưng phần lớn là quá trình sửa các sai lầm ban đầu
      “Lý do PHP 8 tốt hơn nhiều là vì trong đó có ít mã của tôi hơn nhiều.”
    • Tôi không hiểu tại sao Apache không đơn giản là theo dõi hệ thống file
      Vì lựa chọn này mà 99,99% request HTTP bị chậm lại bởi những lần đọc đĩa không cần thiết
  • Tôi đã suy nghĩ thêm về việc này như một phần của quy trình tạo prototype nhanh
    Với các ngôn ngữ JIT hiện đại, nếu không đi theo mô hình FastCGI thì thời gian khởi động có lẽ phần lớn sẽ bị chi phối bởi import
    Tôi nảy ra ý này khi bắt đầu dùng web server h2o cho các script cục bộ; với mruby và handler FastCGI, có thể viết file cấu hình gọn gàng, nhanh chóng, và tốc độ cũng rất nhanh: https://h2o.examp1e.net/configure/fastcgi_directives.html
    Một trường hợp hữu ích khác là khi cho phép khách hàng mở rộng phần mềm cục bộ bằng code của họ
    Ví dụ, thay vì bắt họ dùng MCP để mở rộng công cụ AI, có thể để họ triển khai một cấu trúc request cụ thể bằng CGI

    • MCP frontend cho chương trình CGI trong môi trường người dùng cuối không phải là một ý tưởng tệ
      Tôi tự hỏi liệu bản thân dịch vụ MCP có thể được triển khai bằng CGI không
      Framework MCP cũng có thể phơi bày chức năng dưới dạng một chương trình hỗ trợ cả hai chế độ thực thi
      Có lẽ cần đào sâu hơn vào đặc tả
    • FastCGI thường đánh mất gần như toàn bộ ưu điểm của CGI
  • Với tôi, inetd chính là CGI
    Nhờ vậy mà Internet trở nên thú vị hơn nhiều
    Tôi từng tự host nhiều shell script bằng inetd, và thậm chí có cả một HTTP server viết hoàn toàn bằng Bash
    VPS đó đã biến mất từ lâu, và hồi ấy tôi cũng không dùng quản lý phiên bản
    Chiếc laptop dùng để viết nó cũng không còn
    Dù vậy, nó thực sự rất vui
    Việc triển khai dễ như Makefile + scp, còn kiểm thử là một Bash script khác kết hợp nhiều netcat với grep
    Đúng là những ngày tháng tuyệt vời