- มีการยืนยันผ่านการทดลองว่า โปรแกรม CGI ซึ่งเคยถูกใช้อย่างแพร่หลายใน ยุคแรกของเว็บ ยังสามารถให้ประสิทธิภาพสูงได้บนฮาร์ดแวร์สมัยใหม่
- CGI ประมวลผลคำขอแยกตามโปรเซส ทำให้การจัดการหน่วยความจำเกิดขึ้นโดยอัตโนมัติ และมีข้อดีด้านการดีพลอยที่เรียบง่าย
- ผลการเบนช์มาร์ก แสดงให้เห็นว่า แม้เป็นเซิร์ฟเวอร์ CPU 16 เธรดทั่วไป ก็ยังสามารถรองรับได้มากกว่า 2,400 คำขอต่อวินาที หรือมากกว่า 200 ล้านคำขอต่อวัน
- โค้ดตัวอย่าง guestbook.cgi ที่เขียนด้วย Go และ SQLite พร้อม Dockerfile ถูกเผยแพร่เป็นโอเพนซอร์ส
- แม้ CGI จะไม่ใช่แนวทางที่ใช้กันทั่วไปในปัจจุบัน แต่ก็แสดงให้เห็นว่า ยังเป็นทางเลือกที่ใช้งานได้จริงและทันสมัย
โปรแกรม CGI และหลักการทำงาน
- ในช่วงต้นยุค 2000 โปรแกรม CGI (Common Gateway Interface) เป็นวิธีหลักในการสร้างเว็บไซต์แบบไดนามิก
- ส่วนใหญ่เขียนด้วย Perl หรือ ภาษา C และบางครั้งเลือกใช้ C เพื่อเพิ่มประสิทธิภาพ
- แนวคิดของ CGI นั้นเรียบง่ายแต่ทรงพลัง
- เว็บเซิร์ฟเวอร์ตั้งค่าเมทาดาทาของคำขอ เช่น HTTP header, query เป็นต้น ไว้ในตัวแปรสภาพแวดล้อม
- สร้างโปรเซสแยกต่างหากเพื่อรันโปรแกรม CGI
- ส่ง request body ผ่าน stdin
- จับ stdout ของโปรแกรมเป็น HTTP response
- ส่งผลลัพธ์ stderr ไปยัง server error log
- เมื่อโปรแกรมประมวลผลคำขอเสร็จ โปรเซสจะสิ้นสุดลง ทำให้ file descriptor และหน่วยความจำถูกคืนโดยอัตโนมัติ
- สำหรับนักพัฒนา การปล่อยเวอร์ชันใหม่ก็ ง่ายมากเพียงคัดลอกไฟล์ไปไว้ในไดเรกทอรี
cgi-bin/ ก็ถือว่าดีพลอยเสร็จ
Hug of death (ทราฟฟิกถล่ม)
- ในช่วงต้นยุค 2000 เว็บเซิร์ฟเวอร์ส่วนใหญ่มักอยู่ในสภาพแวดล้อม 1~2 CPU และหน่วยความจำ 1~4GB
- ด้วยลักษณะของ Apache เว็บเซิร์ฟเวอร์ที่ fork โปรเซส
httpd ต่อการเชื่อมต่อแต่ละครั้ง ความต้องการหน่วยความจำจึงเพิ่มขึ้นเมื่อมีการเชื่อมต่อจำนวนมาก
- การรองรับการเชื่อมต่อพร้อมกันเกิน 100 ครั้งทำได้ยาก และ แค่ถูกลิงก์จากเว็บดัง เซิร์ฟเวอร์ก็มักโอเวอร์โหลดได้ง่าย
- ( Slashdot Effect : หากมีลิงก์ขึ้นบน Slashdot ซึ่งโด่งดังมากในเวลานั้น ทราฟฟิกจะไหลบ่าเข้ามา คล้ายกับการขึ้นหน้าแรกของ Hacker News ในปัจจุบัน)
CGI ในสภาพแวดล้อมเซิร์ฟเวอร์สมัยใหม่
- ปัจจุบันมีเซิร์ฟเวอร์ที่มี CPU 384 เธรด แล้ว และแม้แต่ VM ขนาดค่อนข้างเล็กก็ยังให้ CPU ได้ 16 ตัว
- ประสิทธิภาพของ CPU และหน่วยความจำดีขึ้นอย่างมาก
- เนื่องจากโปรแกรม CGI ทำงานบนพื้นฐานของโปรเซสแยก จึง ใช้ประโยชน์จากมัลติคอร์ได้อย่างเป็นธรรมชาติ
- ด้วยเหตุนี้จึงมีการทำเบนช์มาร์กโดยตรง เพื่อดูว่าโปรแกรม CGI จะเร็วได้แค่ไหนบนฮาร์ดแวร์สมัยใหม่
- การทดลองดำเนินการบนเซิร์ฟเวอร์ AMD 3700X (16 เธรด)
ผลลัพธ์สำคัญของเบนช์มาร์ก
- ทดสอบโปรแกรม CGI แบบง่ายทั้งบน Apache และเซิร์ฟเวอร์ Go
net/http
-
คำอธิบายโปรแกรม guestbook.cgi
- ใช้เครื่องมือสร้างโหลด HTTP
plow เพื่อส่ง คำขอครั้งละ 100,000 รายการด้วย 16 การเชื่อมต่อ
- แม้บนฮาร์ดแวร์ทั่วไปก็ยังรองรับได้ มากกว่า 2,400 คำขอต่อวินาที หรือ มากกว่า 200 ล้านคำขอต่อวัน
- แม้ CGI จะไม่ใช่กระแสหลักในปัจจุบัน แต่ก็ยังสามารถนำไปใช้กับบริการจริงได้
-
เบนช์มาร์กการเขียนบนสภาพแวดล้อม Apache
- รองรับคำขอได้ ประมาณ 2468 ครั้งต่อวินาที ด้วยเวลาแฝงเฉลี่ย 6.47ms
- ประมวลผลคำขอ POST 100,000 รายการได้ในเวลาเพียง 40.5 วินาที
- คำขอส่วนใหญ่ตอบสนองภายใน 7ms มีเพียงส่วนน้อยมากที่เกิน 100ms
- พิสูจน์ให้เห็นถึงสมรรถนะการเขียนที่สูงในทางปฏิบัติ
-
เบนช์มาร์กการอ่านบนสภาพแวดล้อม Apache
- รองรับคำขอได้ ประมาณ 1959 ครั้งต่อวินาที ด้วยเวลาแฝงเฉลี่ย 8.16ms
- ประมวลผลคำขอ GET 100,000 รายการได้ในเวลา 51 วินาที
- มากกว่าครึ่งของคำขอตอบสนองภายใน 8ms และเวลาแฝงสูงสุดก็อยู่ที่เพียง 31ms
- ประสิทธิภาพการอ่านก็ดีเพียงพอเช่นกัน
-
เบนช์มาร์กการเขียนบนสภาพแวดล้อม Go net/http
- รองรับคำขอได้ ประมาณ 2742 ครั้งต่อวินาที ด้วยเวลาแฝงเฉลี่ย 5.83ms
- ประมวลผลคำขอ POST 100,000 รายการได้ในเวลา 36.4 วินาที
- throughput เฉลี่ยอยู่ที่ 2,742 RPS และเวลาแฝงเฉลี่ย 5.8ms ซึ่งตามตัวเลขแล้วดีกว่า Apache
- มากกว่า 95% ของคำขอถูกประมวลผลภายใน 6ms
- CGI บนสภาพแวดล้อม Go ก็มีสมรรถนะเพียงพอสำหรับงานจริงเช่นกัน
-
เบนช์มาร์กการอ่านบนสภาพแวดล้อม Go net/http
- รองรับคำขอได้ ประมาณ 2469 ครั้งต่อวินาที ด้วยเวลาแฝงเฉลี่ย 6.47ms
- ประมวลผลคำขอ GET 100,000 รายการได้ในเวลา 40.4 วินาที
- คำขอส่วนใหญ่ให้บริการได้ภายใน 7ms
- ทั้ง throughput การอ่านและความเร็วตอบสนองใกล้เคียงหรือดีกว่า Apache
บทสรุปและลิงก์
- โปรแกรม CGI มี ข้อดี อย่าง การทำงานพร้อมกันความเร็วสูงบนฮาร์ดแวร์รุ่นใหม่, การดีพลอยที่เรียบง่าย, และ ระบบปฏิบัติการคืนทรัพยากรให้อัตโนมัติ
- แม้จะเรียบง่ายอย่างมากเมื่อเทียบกับเฟรมเวิร์กสมัยใหม่ แต่สำหรับบริการในระดับหนึ่งก็ยังนำไปใช้งานจริงได้ในปัจจุบัน
- ตัวอย่างสมุดเยี่ยมและข้อมูลการทดลองเบนช์มาร์กเผยแพร่อยู่บน GitHub ด้านล่าง
https://github.com/Jacob2161/cgi-bin
4 ความคิดเห็น
โห.. นี่จะต้องกลับมาใช้ cgi กันอีกแล้วเหรอ?? 555
ว้าว.. cgi นี่มันยุคไหนแล้วเนี่ย..
มีการอัปเดตเมื่อวันที่ 7/7 ด้วยนะ
Serving a half billion requests per day with Rust + CGI
ตั้ง 500 ล้านรีเควสต์เลยเหรอ...
ความคิดเห็นบน Hacker News
จำสภาพแวดล้อมในยุค 1990s ได้ว่าโปรแกรม CGI ที่เขียนด้วย C นั้นเร็วมากจริง ๆ แต่ก็ยอมรับว่าข้อเสียคือเกิดข้อผิดพลาดบ่อย ส่วนโปรแกรม Go หรือภาษาใหม่อย่าง Nim ที่บทความกล่าวถึงนั้น ถ้าไม่ต้องเชื่อมต่อฐานข้อมูลก็ให้ความรู้สึกว่าเร็วมากและมี latency ต่ำมากบน localhost เหมือนการใช้ fork & exec ใน CLI utility โดยเมื่อเทียบกับ network latency แล้ว ค่าใช้จ่ายแทบเล็กน้อยจนมองข้ามได้
แต่ก็มีการพูดถึงวัฒนธรรมที่ทำให้คนติดกับเทคโนโลยีบางอย่างได้ง่าย เช่น เมื่อคุ้นกับภาษาที่มีต้นทุนการเริ่มทำงานสูงอย่าง Python interpreter แล้ว ก็จะเริ่มรู้สึกว่าจำเป็นต้องใช้โมเดลแบบ multishot หรือ persistent
โมเดล one-shot ของ HTTP ในยุคแรกเริ่มขึ้นจากปัญหาที่หน่วยความจำมีไม่พอให้ FTP server รักษา idle login session ไว้เป็นจำนวนหลายร้อย session ได้นาน ๆ
มีการพูดถึงความเป็นไปได้ของการออกแบบระบบที่ยอดเยี่ยม หากนำ pre-forking ใน CGI (ซึ่งช่วยซ่อน latency ได้) มารวมกับภาษาที่ปลอดภัยอย่าง Rust โดยสามารถให้การทำ TLS termination ไปอยู่ที่ web server แบบ multithread (หรือชั้นอย่าง CloudFront) ได้ จึงสะดวกมาก
fork()และความเสี่ยงของ C และมองว่าตอนนี้อาจกลับไปหาความเรียบง่ายได้อีกครั้งเริ่มพัฒนามาตั้งแต่ยุค CGI จึงมีอคติแรงพอสมควรต่อการรัน subprocess ที่มีอายุสั้น
อธิบายฉากหลังว่า PHP และ FastCGI ถูกสร้างขึ้นมาเพื่อหลีกหนีปัญหาด้านประสิทธิภาพของการสร้าง process ใหม่ทุกครั้งที่มี web request
แต่เมื่อไม่นานมานี้ก็ได้ตระหนักว่า ด้วยพัฒนาการของฮาร์ดแวร์ ต้นทุนในการเริ่ม process ไม่ได้เป็นปัญหาใหญ่อีกต่อไปแล้ว
benchmark นี้จัดการได้ 2000 requests ต่อวินาที และแม้จะต้องรองรับเพียงระดับหลายร้อยก็ยังขยายด้วยหลาย instance ได้ง่ายในสภาพแวดล้อมสมัยใหม่
เห็นด้วยกับความเห็นที่อธิบาย AWS Lambda ว่าเป็นการกลับชาติมาเกิดของโมเดล CGI และคิดว่าเป็นอุปมาที่เหมาะมาก
หากตอนนั้น deploy CGI script เป็น static-linked C binary และใส่ใจเรื่องขนาดด้วย ก็คงผิดหวังน้อยกว่านี้
CGI ไม่ได้มีภาระด้านต้นทุนหรือประสิทธิภาพสูงนักในสภาพแวดล้อมโหลดต่ำ
ก่อนที่ Go จะปรากฏ การสร้างโปรแกรม CGI ด้วย C/C++ ในยุค 2000s นั้นยากทั้งด้านความปลอดภัยและความซับซ้อนในการพัฒนา
ทุกวันนี้เซิร์ฟเวอร์มี 384 CPU threads และแม้แต่ VM ขนาดเล็กก็ยังมี CPU 16 ตัวได้แล้ว
บนฮาร์ดแวร์แบบนี้ ถ้าพัฒนาด้วย Kestrel ก็สามารถรองรับ requests ระดับหลายล้านล้านครั้งต่อวันได้สบาย
ให้ประสบการณ์พัฒนาใกล้เคียง PHP ได้ผ่านตัวดำเนินการ string interpolation
ใช้ LINQ และ String.Join() เพื่อทำ template ของตาราง HTML และองค์ประกอบซ้อนกันได้อย่างง่ายดาย
สิ่งที่ยากจริง ๆ คือการรู้ว่าจะหลบกับระเบิดใน ecosystem อย่าง MVC/Blazor/EF อย่างไร
ยังสามารถรันทั้งโปรแกรมเป็นไฟล์ระดับบนสุดไฟล์เดียวจาก CLI ได้ด้วย แต่ถ้าไม่รู้คีย์เวิร์ด "Minimal APIs" ก็หลงเข้าไปในเขาวงกตของเอกสารที่พาไปผิดทางได้ง่าย
ข้อดีของ CGI คือในสภาพแวดล้อมแบบ multitenant ไม่จำเป็นต้องสร้าง isolation primitives ใหม่
rlimitบังคับ kill request ที่ใช้เวลานานเกินไปได้สคริปต์ CGI เป็นเหตุผลที่ทำให้ perl ถูก optimize เรื่องเวลาเริ่มต้นได้ดี
เมื่อรันคำสั่ง
time perl -e ''จะเห็นว่า perl ใช้ 5ms, python3 ใช้ 33ms และ ruby ใช้ 77ms จึงยืนยันได้ว่า perl เริ่มต้นได้เร็วมาก#!/bin/tcc -runของ tcc mob branch ว่าเร็วกว่า perl 1.3 เท่าถ้าลองใช้ apache tomcat 11 ก็จะพบว่าแค่อัปโหลดไฟล์ .jsp หรือทั้งแอปพลิเคชัน java servlet (.war) ผ่าน ssh แล้วมันก็ทำงานได้เลย
ได้ประสิทธิภาพสูงสุดจาก JVM ที่แชร์ร่วมกันตัวเดียว
สิ่งอย่าง DB connection pool และ cache ก็แชร์กันระหว่างแอปพลิเคชันได้
เป็นประสบการณ์ที่น่าประทับใจมาก
แต่ก็ขึ้นอยู่กับรูปแบบการใช้งานจริง
มันยอดเยี่ยมสำหรับบริการขนาดใหญ่ แต่ถ้าต้องมีแอปเล็ก 50 ตัวที่แต่ละตัวรับเพียงวันละไม่กี่ร้อยคำขอ memory overhead ของ Tomcat ก็ใหญ่เกินไปเมื่อเทียบกับ Apache/Nginx ที่ใช้ CGI script
คิดถึงยุคที่ deploy แค่คัดลอกไฟล์ก็เสร็จ
รู้สึกเสียดายว่าทำไมกระบวนการ deploy ถึงซับซ้อนขึ้นมากขนาดนี้
มีการแชร์ประสบการณ์ว่ายังใช้งาน Jetty ทำเว็บแอปแบ็กเอนด์ได้อย่างมีความสุขอยู่จนถึงตอนนี้
รู้สึกว่า stack อย่าง Tomcat/Jakarta EE/JSP นั้นแข็งแกร่งกว่าที่คิดมาก
สามารถเขียนปน HTML กับโค้ดได้เหมือน PHP และก็ทำ pure Java routes ได้ด้วย
รองรับ Websockets และเป็นโมเดล single-process multithread จึงมีจุดแข็งด้านการสื่อสารแบบเรียลไทม์
ถ้าจำเป็นก็แชร์ข้อมูลข้าม request ได้ และโค้ด JSP โดยพื้นฐานจะถูกจำกัดอยู่ใน request scope
การ deploy ง่ายมาก เพียงอัปโหลดไฟล์ใหม่เข้าไปในไดเรกทอรี webapps แล้ว Tomcat จะโหลดแอปใหม่และ unload แอปเดิมให้อัตโนมัติ
ข้อเสียคืออาจเกิด classloader leak จน garbage collection ไม่สำเร็จ ซึ่งเป็นชะตากรรมของโมเดลแบบ single-process
ได้สร้างเครื่องมือ visualization สำหรับคำขอของ apache ที่ ibrahimdiallo.com/reqvis
ช่วงนี้เริ่มสงสัยกับการมุ่งไปสู่สถาปัตยกรรมที่ซับซ้อน เพราะจริง ๆ แล้วด้วยฮาร์ดแวร์ที่ดี เราอาจยังใช้เทคโนโลยีเดิมได้อยู่
ตอนถูกถามให้ออกแบบระบบที่แจ้งข้อมูลราคาหุ้นแบบเรียลไทม์ให้คนหลายล้าน ตอนแรกก็นึกถึงโครงสร้างสตรีมซับซ้อนอย่าง Kafka, pubsub เป็นต้น แต่สุดท้ายก็หันมาคิดถึงวิธีง่าย ๆ อย่างวาง static file ไว้บนเซิร์ฟเวอร์
อยากรู้ว่าต้นทุนการปฏิบัติการจริงของวิธีแบบนี้เป็นอย่างไร
ย้ำว่ามันคล้ายกับสถาปัตยกรรม serverless แต่เรียบง่ายและถูกกว่ามาก
รู้สึกเสียดายที่ไม่ได้กลับมาทบทวนโครงสร้างดั้งเดิมแบบนี้ แต่กลับสร้างกระบวนทัศน์ใหม่ชื่อ "serverless functions" ขึ้นมาเฉย ๆ
จะว่าไป
cgiก็พอเข้าใจได้อยู่ แต่ปฏิกิริยาต่อjspนี่น่าประหลาดใจจริง ๆ 555jspกลายเป็นโบราณวัตถุถึงขนาดนั้นไปแล้วเหรอครับ