- zeroserve เป็น เซิร์ฟเวอร์ HTTPS ขนาดเล็กและรวดเร็ว ที่รับ tarball ของเว็บไซต์แล้วให้บริการผ่าน HTTP/2 และ TLS 1.3 พร้อมรัน โปรแกรม eBPF ภายใน tarball เป็นมิดเดิลแวร์แบบแซนด์บ็อกซ์ใน userspace สำหรับทุกคำขอ
- โดยไม่ต้องมีไฟล์คอนฟิก โปรแกรม eBPF จะตัดสินใจเรื่อง routing, header, การยืนยันตัวตน, rate limiting และ proxy แยกตามแต่ละคำขอ ทำให้การตั้งค่าเชิงประกาศของ nginx·Caddy และเลเยอร์สคริปต์แยกต่างหากถูกรวมเป็นหนึ่งเดียว
- เว็บไซต์ถูกทำดัชนีเป็น ไฟล์ tar เดียว และไม่ถูกแตกลงดิสก์ โดยสามารถสลับ tarball และส่ง
SIGHUP เพื่อสลับเว็บไซต์·สคริปต์·ข้อมูล TLS แบบอะตอมมิกโดยไม่ทำให้การเชื่อมต่อหลุด
- ในเบนช์มาร์ก HTTPS แบบคอร์เดียว zeroserve ทำได้ 36,681 req/s สำหรับไฟล์สแตติกขนาดเล็ก, 46,945 req/s สำหรับ JSON แบบไดนามิกผ่าน eBPF 10ms และ 26,486 req/s สำหรับพร็อกซีขนาดเล็ก แต่สำหรับ พร็อกซี 100KB nginx ยังนำอยู่ที่ 5,882 req/s
- zeroserve ตั้งเป้าเป็นทางเลือกแทน nginx และ Caddy โดยรวมการ deploy แบบ tarball เดียว, การตั้งค่าแบบเขียนโปรแกรมได้, eBPF ใน userspace และ TLS สมัยใหม่ไว้ด้วยกัน แต่สำหรับการตอบกลับพร็อกซีขนาดใหญ่ nginx ยังเหมาะกว่า
ภาพรวม
- zeroserve เป็นเซิร์ฟเวอร์ HTTPS แบบไม่ต้องตั้งค่าที่มีขนาดเล็กและรวดเร็ว โดยให้บริการ tarball ของเว็บไซต์เพียงไฟล์เดียวผ่าน HTTP/2 และ TLS 1.3
- โปรแกรม eBPF ที่ใส่ไว้ใน tarball จะถูกรันเป็นมิดเดิลแวร์แบบแซนด์บ็อกซ์ใน userspace สำหรับทุกคำขอ และสามารถทำ request rewrite, การยืนยันตัวตน, rate limiting และ reverse proxy ไปยังแบ็กเอนด์ได้
- เป็นเซิร์ฟเวอร์ที่มุ่งให้มีประสิทธิภาพเหนือกว่า nginx บนคอร์เดียวในเวิร์กโหลดส่วนใหญ่ เช่น ไฟล์สแตติกขนาดเล็ก·ใหญ่, มิดเดิลแวร์แบบสคริปต์ และพร็อกซีการตอบกลับขนาดเล็ก
- สคริปต์ eBPF ถูก JIT คอมไพล์เป็น native code และแซนด์บ็อกซ์ใน userspace โดยตั้งเป้าให้มี overhead ต่ำพอที่จะรันได้ทุกคำขอ
- งานเครือข่ายและดิสก์ถูกส่งผ่าน
io_uring ด้วยรันไทม์ monoio
- รองรับ TLS 1.3, HTTP/2, Encrypted Client Hello, การเลือกใบรับรองตาม SNI และ JA4 fingerprinting
- ทั้งเว็บไซต์และข้อมูล TLS ถูกให้บริการจาก tarball เดียว และรีโหลดแบบ hot reload ได้ด้วย
SIGHUP
โมเดลการตั้งค่า: โปรแกรมก็คือคอนฟิก
- zeroserve ตั้งเป้าเป็นทางเลือกแทน nginx และ Caddy และการตัดสินใจด้านการออกแบบหลักอยู่ที่วิธีการตั้งค่า
- nginx และ Caddy มีภาษาคอนฟิกเชิงประกาศ เช่น
location block, กฎ rewrite, คำสั่ง map, try_files และเมื่อชนข้อจำกัดก็จะพ่วงรันไทม์สคริปต์เสริมอย่าง Lua หรือ Caddy plugin เข้ามา
- ในโครงสร้างนั้น พฤติกรรมจะถูกแยกเป็นชั้นของ directive ที่มี control flow ของตัวเอง และชั้นของสคริปต์ที่รันในบางจุดของวงจรชีวิตคำขอ
- zeroserve ไม่มีไฟล์คอนฟิก โดยใช้โปรแกรม eBPF ตัวเดียวที่เห็นทุกคำขอและตัดสินใจเรื่อง routing, header, การยืนยันตัวตน, rate limiting และ proxy
ให้บริการ single tarball โดยตรง
- ทั้งเว็บไซต์คือไฟล์
tar เดียว โดยตอนโหลด zeroserve จะสร้างแมป path -> byte-range และให้บริการไฟล์ด้วยการอ่านช่วงไบต์จากตัว tarball โดยตรง
- ไม่มีไฟล์ใดถูกแตกลงดิสก์ ดังนั้นเว็บไซต์จะมีอยู่แค่ภายในไฟล์เดียว และไม่มี document root ที่กฎ
location ผิดพลาดจะเผลอเปิดเผยได้
- การ deploy ใช้วิธีสลับไฟล์เดียวแบบอะตอมมิก โดยปล่อยเวอร์ชันใหม่ด้วยการแทนที่ tarball แล้วส่ง
SIGHUP
- รูปแบบการแพ็กไดเรกทอรีและคำสั่งรันมีดังนี้
zeroserve --pack ./public > site.tar
zeroserve --addr 0.0.0.0:8080 site.tar
- คำสั่ง hot reload มีรูปแบบดังนี้
killall -SIGHUP zeroserve
- การรีโหลดจะสลับเว็บไซต์, สคริปต์ และข้อมูล TLS แบบอะตอมมิกภายในโปรเซสเดียว และทำงานได้โดยไม่ทำให้การเชื่อมต่อหลุด
- แต่ละอินสแตนซ์เป็น event loop แบบเธรดเดียว ซึ่งเป็นข้อจำกัดเมื่อมองในระดับหนึ่งโปรเซส แต่ถูกเสนอว่าเหมาะกับรูปแบบการสเกลที่เพิ่มจำนวนโปรเซสแทน
การเขียนสคริปต์ eBPF ใน userspace
- ไฟล์
.c ทั้งหมดภายใต้ .zeroserve/scripts/ จะถูกคอมไพล์เป็น eBPF object ด้วย clang และ llc ตอนแพ็ก และถูกรันสำหรับทุกคำขอ
- eBPF ทำงานใน userspace ผ่านรันไทม์ async-ebpf ภายในโปรเซสทั่วไปที่ไม่มีสิทธิพิเศษ โดยไม่ต้องพึ่ง kernel BPF subsystem หรือ
CAP_BPF
- async-ebpf ฝัง uBPF ไว้ภายใน และทำ JIT คอมไพล์ bytecode เป็น native x86-64 machine code
- pointer cage จะ mask การเข้าถึงหน่วยความจำทั้งหมดของโค้ดที่ JIT คอมไพล์แล้วให้เหลืออยู่ใน arena เฉพาะของโปรแกรม เพื่อกักการเข้าถึงที่ผิดพลาดไว้ภายในหน่วยความจำของสคริปต์
- สคริปต์รันโดยตรงบน event loop เดียวของ zeroserve และเพื่อไม่ให้สคริปต์ที่ช้าหยุดการเชื่อมต่ออื่น ตัวจับเวลาสามารถ interrupt native code ที่ JIT คอมไพล์ระหว่างรันและคืนการควบคุมกลับสู่ event loop ได้
- โมเดลการเขียนโปรแกรมเป็นเชนของสคริปต์ที่รันตามลำดับการเรียงชื่อไฟล์ และสคริปต์เหล่านี้แชร์ metadata map ระดับคำขอร่วมกัน
- ถ้าสคริปต์เรียก
zs_respond หรือ zs_reverse_proxy เชนนั้นจะหยุดทันที
- คีย์ใต้
zs.response.header.* จะกลายเป็น header ของทุกการตอบกลับ ส่วนคีย์อื่นจะถูกใช้ใน template pass ขนาดเล็กสำหรับแทน placeholder อย่าง <zs-meta>visitor</zs-meta> ในไฟล์ HTML ตอนส่งออก
- helper surface รองรับการอ่าน method·path·query·header·peer address ของคำขอ รวมถึงการ rewrite URI และการตั้งค่า·ลบ header
- helper ด้านการเข้ารหัสและการ encode รองรับ SHA-256, HMAC-SHA256, base64, hex และ
getrandom
- helper JSON รองรับการ parse request body, การสร้าง·แก้ไข document tree และการตอบกลับด้วย
zs_json_respond
- rate limiting รองรับ token bucket ตามคีย์ใดก็ได้ เช่น peer IP หรือ API key และสถานะจะยังคงอยู่หลัง hot reload
- helper สำหรับ AWS SigV4 รองรับ header
Authorization แบบ signed และ presigned URL สำหรับสื่อสารกับ S3 และบริการ AWS อื่น
- การล็อกอิน OIDC ให้โฟลว์ relying-party แบบ Authorization Code + PKCE โดยเก็บทั้ง login session ไว้ในคุกกี้ sealed XChaCha20-Poly1305 เพื่อให้เซิร์ฟเวอร์ยังคงไร้สถานะ และสามารถวาง static site ไว้หลัง “Sign in with Google” ได้
- endpoint แบบไดนามิกทำงานโดยให้สคริปต์ตอบกลับเองใน path ที่กำหนด โดยตัวอย่างจะคืน header
application/json และ body {"status":"ok"} สำหรับคำขอ /health
- แต่ละสคริปต์ทำงานภายใต้เพดานหน่วยความจำเริ่มต้น 256KB และรันไทม์จะทำ time slicing ให้กับสคริปต์ที่รันนาน รวมถึง throttle สคริปต์ที่ทำงานผิดปกติ
- สคริปต์สามารถเรียกกันเองได้ผ่าน
zs_call โดยมีการจำกัดความลึกของการเรียก
- สคริปต์ที่ติดลูปไม่สิ้นสุดจะทำให้ช้าเฉพาะคำขอของตัวเองเท่านั้น และ preemption timer จะ interrupt มันเพื่อให้เซิร์ฟเวอร์ประมวลผลคำขออื่นต่อได้
- เลเยอร์ TLS รองรับเฉพาะ TLS 1.3 และ terminate ด้วย BoringSSL
- Encrypted Client Hello ช่วยไม่ให้ SNI จริงปรากฏในรูปแบบ plaintext พร้อมรองรับการเลือกใบรับรองตาม SNI แบบอิงไดเรกทอรี และ JA4 client fingerprinting ที่เปิดให้สคริปต์ใช้งานได้
- โหมด transparent ECH relay จะส่งต่อ handshake ที่ถอดรหัสไม่ได้ไปยัง upstream จริงแบบ byte ต่อ byte ทำให้ชื่อที่ถูกปกป้องสามารถซ่อนอยู่หลังชื่อสาธารณะได้
ประสิทธิภาพ
-
เงื่อนไขเบนช์มาร์ก
- เปรียบเทียบการให้บริการ HTTPS ของ zeroserve, nginx 1.26 และ Caddy 2.11 บน Ryzen 7 3700X แบบ 8 คอร์ ด้วยคอนเทนต์เดียวกันและใบรับรอง self-signed เดียวกัน
- เนื่องจากอินสแตนซ์ของ zeroserve เป็น single-threaded ตามการออกแบบ จึงใช้ประสิทธิภาพต่อคอร์เป็นเกณฑ์เปรียบเทียบ
- ทุกเซิร์ฟเวอร์ถูก pin ไว้กับ CPU เดียวด้วย
taskset โดย nginx ใช้ worker_processes 1, Caddy ใช้ GOMAXPROCS=1 และ zeroserve ใช้โครงสร้าง single-thread เดิม
- โหลดถูกสร้างจากอีกคอร์หนึ่งด้วย
wrk -t4 -c100 และใช้ค่ามัธยฐานจากการรัน 3 ครั้ง ครั้งละ 10 วินาที
wrk ใช้ HTTP/1.1 ดังนั้นตัวเลขทั้งหมดจึงเป็น HTTP/1.1 บน TLS 1.3 และเป็นต้นทุน steady state ของการเชื่อมต่อ HTTPS ที่เปิดค้างไว้แล้ว โดยเฉลี่ยต้นทุน handshake ออกผ่าน keep-alive ที่ยาว
-
ไฟล์สแตติกขนาดเล็ก 174B
| เซิร์ฟเวอร์ |
req/s |
p99 |
| zeroserve |
36,681 |
5.4 ms |
| nginx |
31,226 |
7.8 ms |
| Caddy |
12,830 |
22 ms |
- zeroserve ให้บริการไฟล์ขนาดเล็กบนคอร์เดียวได้เร็วกกว่า nginx ราว 17% และมี tail latency ต่ำกว่า
- กรณีพื้นฐานของ static site เช่น หน้า HTML, JSON ขนาดเล็ก และ CSS คือเป้าหมายการจูนของ zeroserve
-
ไฟล์สแตติกขนาดใหญ่ 100KB
| เซิร์ฟเวอร์ |
req/s |
throughput |
p99 |
| zeroserve |
8,000 |
782 MB/s |
22 ms |
| nginx |
7,600 |
773 MB/s |
28 ms |
| Caddy |
6,084 |
590 MB/s |
44 ms |
- ผลลัพธ์ของทั้งสามเซิร์ฟเวอร์ใกล้เคียงกัน โดย zeroserve นำเล็กน้อยที่ราว 780 MB/s บนคอร์เดียว
- จุดแข็งของ nginx สำหรับไฟล์ใหญ่คือ
sendfile() แต่ไม่ถูกใช้ภายใต้ TLS เพราะไบต์ต้องถูกเข้ารหัสใน userspace ทำให้ทั้งสามเซิร์ฟเวอร์ติดอยู่กับงานเข้ารหัสและ write loop เหมือนกัน
- เมื่อปิด kernel TLS ในทั้งสามเซิร์ฟเวอร์ เส้นทางอ่าน·เขียนด้วย
io_uring ของ zeroserve จึงออกมาเร็วกว่านิดหน่อย
eBPF vs Lua
- คู่เทียบด้านการเขียนสคริปต์คือ nginx + LuaJIT
ngx_http_lua_module ซึ่งเป็นแนวทางทั่วไปในการรันโค้ดเร็วภายในเว็บเซิร์ฟเวอร์
- zeroserve ตั้งค่า preemption timer สำหรับสคริปต์ไว้ที่ทุก 2ms โดยค่า interval ที่ถี่ช่วย throttle สคริปต์ที่มีปัญหาได้เร็ว แต่ก็เพิ่มต้นทุนให้สคริปต์ปกติด้วย
- ที่ค่าเริ่มต้น 2ms eBPF ทำได้ราว 32k req/s สำหรับการตอบกลับแบบไดนามิกเต็มรูปแบบ ซึ่งต่ำกว่า nginx Lua ที่ 41k req/s
- เมื่อเพิ่ม
--preempt-timer-interval-ms เป็น 10 throughput ของสคริปต์จะฟื้นกลับมาราว 40% และผลลัพธ์ก็พลิกกลับ
-
มิดเดิลแวร์ inject header ต่อคำขอ
| เอนจิน |
req/s |
p99 |
| zeroserve eBPF 10ms |
43,709 |
5.1 ms |
| zeroserve eBPF 2ms ค่าเริ่มต้น |
31,334 |
6.7 ms |
nginx Lua header_filter |
28,653 |
8.4 ms |
- ในกรณีมิดเดิลแวร์ที่ยังคงเสิร์ฟไฟล์สแตติกแต่มีการรันสคริปต์ eBPF 10ms ทำได้สูงกว่า nginx Lua ราว 50% และมี tail latency ต่ำกว่า
-
การตอบกลับ JSON แบบไดนามิกเต็มรูปแบบ
| เอนจิน |
req/s |
p99 |
| zeroserve eBPF 10ms |
46,945 |
4.5 ms |
nginx Lua content_by_lua |
41,231 |
6.4 ms |
| zeroserve eBPF 2ms ค่าเริ่มต้น |
32,393 |
6.7 ms |
- eBPF ที่จูน interval เป็น 10ms ทำ throughput ได้สูงกว่า
content_by_lua ของ nginx แม้ในงานตอบกลับที่สังเคราะห์ขึ้นทั้งหมด
- ทั้งสองเอนจินถูกคอมไพล์เป็น native code โดย LuaJIT เป็น tracing JIT ส่วน async-ebpf ทำ JIT ให้ eBPF ผ่าน uBPF
- ภายใต้เงื่อนไขที่ต้นทุนร่วมของแต่ละคำขอคือการเข้ารหัส TLS เส้นทาง eBPF ที่จูนแล้วจึงนำในด้าน throughput
- ที่ค่าเริ่มต้น 2ms eBPF ยังชนะในงานมิดเดิลแวร์ แต่เสียตำแหน่งผู้นำในงานตอบกลับสังเคราะห์ ดังนั้นจึงแนะนำให้ใช้ 10ms สำหรับสคริปต์ในงานจริง
ใช้เป็นรีเวิร์สพร็อกซี
- zeroserve พร็อกซีไปยังแบ็กเอนด์ได้โดยเรียก
zs_reverse_proxy("http://127.0.0.1:9000") จากสคริปต์
- upstream connection pool รองรับได้สูงสุด 128 การเชื่อมต่อต่อแบ็กเอนด์ และนำการเชื่อมต่อที่ idle กลับมาใช้ใหม่ได้ภายใน 30 วินาที
- เพื่อให้เทียบอย่างยุติธรรม nginx จึงตั้งค่า
keepalive 128, proxy_http_version 1.1 และ header Connection ว่างไว้ชัดเจน เนื่องจากโดยปกติมีลักษณะปิด upstream connection ทุกคำขอ
- Caddy ใช้การ reuse การเชื่อมต่อตามพฤติกรรมปกติ
- พร็อกซีแต่ละตัว terminate TLS บนคอร์เดียวแล้วส่งต่อไปยัง plain-text backend ที่ใช้ร่วมกัน โดยแบ็กเอนด์อยู่บนเซิร์ฟเวอร์อีกเครื่องแบบ 2 คอร์ที่คงความสามารถ 100k req/s ไว้เอง เพื่อวัดเฉพาะ overhead ของพร็อกซี
-
พร็อกซีการตอบกลับขนาดเล็ก 174B
| พร็อกซี |
req/s |
p50 |
p99 |
| zeroserve |
26,486 |
3.3 ms |
8 ms |
| nginx |
21,761 |
4.2 ms |
10.5 ms |
| Caddy |
7,683 |
10.3 ms |
33 ms |
- พร็อกซี
io_uring แบบมีพูลของ zeroserve นำ nginx ราว 22% และมี throughput สูงกว่า Caddy ราว 3.4 เท่า
- ในเวิร์กโหลดพร็อกซีทั่วไปอย่าง API call, JSON ขนาดเล็ก หรือ HTML จากแอปเซิร์ฟเวอร์ zeroserve ทำ TLS termination และส่งต่อไปยังแบ็กเอนด์ได้เร็วกว่า
-
พร็อกซีการตอบกลับ 100KB
| พร็อกซี |
req/s |
throughput |
| nginx |
5,882 |
585 MB/s |
| Caddy |
4,285 |
406 MB/s |
| zeroserve |
3,631 |
359 MB/s |
- เมื่อ body ของพร็อกซีมีขนาดใหญ่ buffering ของ nginx จะย้ายไบต์ได้มีประสิทธิภาพกว่า จึงขึ้นนำ โดย Caddy อยู่ตรงกลาง และ zeroserve ตามหลัง
- หากการตอบกลับจากพร็อกซีมีขนาดใหญ่ nginx คือเครื่องมือที่เหมาะกว่า แต่ถ้าเป็นการตอบกลับขนาดเล็กจำนวนมาก zeroserve จะเร็วกว่า
หน่วยความจำ
- อินสแตนซ์ zeroserve เดียวในสถานะ idle ใช้ PSS ราว 15MB ซึ่งมากกว่า nginx ที่ราว 6MB แต่น้อยกว่า Caddy ที่ราว 60MB
- ประเด็นสำคัญคือหน่วยการรันคือทั้งโปรเซส และเมื่อรันสำเนาแยกตามคอร์ก็ยังแชร์ code page กันได้ด้วยการ map ไบนารีเดียวกัน
- โปรเซสเพิ่มเติมจะเพิ่มการใช้หน่วยความจำไม่มากนักนอกเหนือจาก working set ของตัวเอง
การเผยแพร่
- zeroserve เป็นโปรเจกต์โอเพนซอร์สที่เผยแพร่บน GitHub
1 ความคิดเห็น
ความเห็นบน Hacker News
พอ TechEmpower Web Server Benchmarks หายไป ก็ดูเหมือนโปรเจ็กต์ใหม่ ๆ แบบนี้จะมีโอกาสพิสูจน์ตัวเองน้อยลง
แก้ไข: สงสัยว่าฉันจะตามไม่ทันแล้ว และของที่กำลังมาแรงช่วงนี้น่าจะเป็น https://www.http-arena.com/leaderboard/ ขอให้โชคดี
เพียงแต่ว่าเดิมทีก็ไม่ได้รันบ่อยอยู่แล้ว และถ้าดูประวัติแต่ละรอบก็ออกจะน้อยกว่าปีละครั้ง
ชอบที่ได้เห็นความพยายามแบบนี้เกิดขึ้น เพราะ LLM ทำให้การลองสำรวจแนวทางต่าง ๆ ทำได้ถูกลงและเร็วขึ้นมาก
แต่สิ่งที่รู้สึกจากตรงนี้คือ nginx เองก็น่าประทับใจมาก อีกจุดที่สะดุดตาคือคำอธิบายว่าโปรเจ็กต์นี้เป็นทางเลือกแทน nginx และ Caddy และกำลังเดิมพันกับวิธีการตั้งค่า
nginx และ Caddy มีภาษาคอนฟิกแบบประกาศเจตนา และเมื่อไปชนข้อจำกัดก็จะพ่วงรันไทม์สคริปต์อย่าง Lua หรือปลั๊กอินของ Caddy เข้าไป ทำให้การทำงานแยกเป็นสองชั้น
แต่ดูเหมือนการเดิมพันนั้นจะผิด ผู้คนชอบ คอนฟิกมากกว่าโค้ด มาตั้งนานแล้ว และหลายกรณีก็เพียงพอด้วยความสามารถที่มีมาในตัว จนไม่จำเป็นต้องเขียนโค้ด C
รูปแบบไฟล์คอนฟิกทุกอย่างดูเหมือนจะเริ่มต้นแบบเรียบง่าย YAML เองพื้นฐานก็สมเหตุสมผลดี แต่คนก็เริ่มอยากได้อะไรที่ซับซ้อนขึ้นด้วย anchor และ alias
แม้แต่ GitLab ก็ยังมีฟอร์แมตเฉพาะของตัวเองที่คล้ายมีเงื่อนไขและตัวแปร แถมยังเป็นลูกเล่นกึ่งแฮ็กที่ใช้ได้เฉพาะบางตำแหน่ง Apache ก็เดินไปในทิศทางคล้ายกันกับฟอร์แมตคอนฟิกที่อิง XML
สุดท้ายเลยเกิด ภาษาการเขียนโปรแกรมเฉพาะทาง สำหรับจัดการคอนฟิกขึ้นมามากมาย ในองค์กรเองก็มักไม่ได้แก้ตรง ๆ แต่เขียนสคริปต์เวิร์กโฟลว์ Ansible เพื่อผ่าตัดจากระยะไกล
ถ้าฝังอินเทอร์พรีเตอร์อย่าง Lua หรือ Python เข้าไปในเซิร์ฟเวอร์เพื่อจัดการคอนฟิกตั้งแต่แรก ก็คงข้ามขั้นตอนพวกนี้ไปได้ และน่าจะง่ายกว่าการไปแก้ไฟล์คอนฟิกเฉพาะทางด้วยโปรแกรมอีกที
แน่นอนว่าจะเถียงได้ว่าความพยายามแบบเฉพาะทางเหล่านี้เหมาะกับงานเฉพาะมากกว่าภาษาทั่วไป แต่ข้ออ้างแบบนั้นมักใช้ได้แค่กับตัวอย่างของเล่นในขอบเขตแคบ ๆ ที่จริง ๆ แล้วอาจไม่ต้องมีเครื่องไม้เครื่องมือนั้นตั้งแต่แรก
จำไฟล์ INI ของ Windows ได้ไหม เป็นยุคดี ๆ ที่โค้ดคือโค้ด และข้อมูลคือข้อมูล
หรือถ้าให้เรียบง่ายกว่านั้น ก็อาจอ่าน Ingress manifest ทั้งหมดในคลัสเตอร์ Kubernetes แล้วสร้าง pack ขึ้นใหม่ก็ได้
ประเด็นคืออินเทอร์เฟซระหว่างเครื่องมือกับคอนฟิกก็เป็นเพียง API อีกรูปแบบหนึ่ง และผู้ดูแลระบบเองก็บรรยายสถานะของระบบด้วยสิ่งประกอบระดับสูงกว่านั้นอยู่แล้ว ส่วนไบต์ที่เป็นคอนฟิกจริง ๆ ก็เป็นเพียงผลลัพธ์ปลายทาง
จากมุมของ AI วิธีนั้นอาจจัดการได้ง่ายกว่า AI ทำได้ทั้งสองแบบอยู่แล้ว ดังนั้นกว่าการเปลี่ยนผ่านเช่นนี้จะถูกมองว่าเป็นไอเดียที่ดีอย่างชัดเจน ก็คงใช้เวลาอีกนาน
ชอบไอเดียนี้
แต่คงอุ่นใจกว่าถ้าในไดเรกทอรี eBPF สามารถใส่ไฟล์
.rsแทน.cได้ เพราะตัวมันเองก็เป็นโปรเจ็กต์ Rust อยู่แล้วแล้วก็แอบคาดหวังว่าจะเป็นเว็บเซิร์ฟเวอร์ที่เร่งความเร็วด้วยเคอร์เนล ถ้าทำแบบปลอดภัยด้วย eBPF ได้ก็คงสุดยอดมาก
แล้วนี่เป็น single-threaded เหรอ? บน Linux การ fork แล้วแชร์คิวของการเชื่อมต่อขาเข้าแทบจะเป็นเรื่องเล็กน้อย และใน Rust ก็น่าจะเขียนได้ไม่กี่บรรทัด ใช้ SO_REUSEPORT แล้วที่เหลือก็ให้เคอร์เนลจัดการ
อีกอย่าง ถ้าจะผลักดัน io_uring ก็น่าจะผลักดัน kTLS ไปพร้อมกันด้วย ถ้าหลีกเลี่ยงการจัดการ SSL ใน user space หลัง handshake ได้ การออกแบบจะง่ายขึ้นมาก
ก่อนหน้านี้ใช้ nftables กับงานลักษณะนี้มาโดยตลอด เลยยังไม่จำเป็นต้องใช้เองโดยตรง
เจ๋งมาก สงสัยว่าจะเอาไปรวมกับ BPF program ประเภทอื่น เช่น XDP program หรือโปรแกรมที่ไปเกาะกับ socket map เพื่อดึงความสามารถ HTTP ระดับ L7 ลงไปรวมในชั้นที่ต่ำกว่านี้ได้ไหม
ไอเดียดี แต่ไม่แน่ใจว่าควรโฟกัสที่ ไฟล์สแตติก หรือเปล่า ทุกวันนี้ไม่ค่อยมีใครเปิดเซิร์ฟเวอร์ใหม่ขึ้นมาเพื่อจุดประสงค์นั้นกันแล้ว
เพราะงั้นมันเลยให้ความรู้สึกเหมือนถูกสร้างมาเพื่อฉัน แต่ก็ยอมรับว่าฉันไม่ใช่ผู้ใช้ทั่วไป
ดูน่าสนใจและฟีเจอร์ก็ใช้ได้ดี แต่มีอะไรบางอย่างที่รู้สึกว่าประดิษฐ์เกินไปจนยังไม่ค่อยดึงดูดใจนัก
ไม่รู้ว่าเมตริกปลอมหรือเปล่า, ฟังก์ชันอำนวยความสะดวกต่าง ๆ ใช้งานได้จริงไหม, หรือมีการ hardening ที่เหมาะสมหรือยัง
การทำด้วย vibe coding และมี README ที่สร้างอัตโนมัตินั้นยังพอรับได้ แต่พอแม้แต่บทความเปิดตัวบนบล็อกก็ถูก AI สร้างขึ้นด้วย ก็ไม่มีหลักฐานอะไรเลยให้ตัดสินได้ว่าความเข้าใจเรื่องคุณภาพซอฟต์แวร์ของเขาตรงกับของฉันหรือไม่
เป็นโลกที่แปลกดี ถ้าเมื่อไม่กี่ปีก่อนมีการเปิดตัวโดยไม่บอกว่าใช้ AI ฉันก็คงรับไปโดยไม่สงสัย แต่ตอนนี้พอเห็น README ที่ดูดีและพารามิเตอร์บรรทัดคำสั่งที่น่าเชื่อถือ ก็จะสงสัยทันทีว่า README นั้นอาจ หลอน ขึ้นมาเอง และจริง ๆ อาจไม่มีออปชันนั้นเลยก็ได้
ตอนสร้าง zeroserve เองก็ใช้ AI ช่วยเยอะ แต่ผลลัพธ์จาก AI ผมตรวจเองและรับผิดชอบเอง
zeroserve ให้บริการไฟล์เล็กบนคอร์เดียวได้เร็วกว่า nginx ราว 17% และ tail latency ก็แคบกว่า กรณีที่ zeroserve ทำได้ดีคือหน้า HTML, JSON ขนาดเล็ก และ CSS
สำหรับไฟล์ static ขนาดใหญ่ 100KB นั้น zeroserve ทำได้ 8,000 req/s, 782 MB/s, p99 22ms ส่วน nginx ได้ 7,600 req/s, 773 MB/s, p99 28ms และ Caddy ได้ 6,084 req/s, 590 MB/s, p99 44ms
ถึงอย่างนั้น ฉันก็ยังจะเลือก โปรเจ็กต์เก่า ที่ผ่านการตรวจสอบ, ผ่านการใช้งานจริง, และผ่านการ hardening มากกว่าโปรเจ็กต์เกิดใหม่แบบนี้อยู่ดี การปรับปรุงไม่ได้มากพอจะคุ้มกับความเสี่ยง
ฉันตั้งใจจะอยู่กับยุคเก่าให้นานที่สุด คนฉลาดปล่อยซอฟต์แวร์ออกมา และคนฉลาดก็ดูแลรักษามัน พวกเขาไม่ต้องการ AI นั่นแหละคือพื้นที่ของฉัน
เราอาจหายไปก็ได้ แต่ฉันก็ยังชอบแบบนั้นมากกว่า เพียงแต่มีเงื่อนไขว่าคนฉลาดเหล่านั้นต้องเขียนเอกสารด้วย และคนฉลาดจำนวนมากก็ไม่ชอบเขียนเอกสาร
นานมาแล้วฉันตัดสินใจว่าซอฟต์แวร์ที่ไม่มีเอกสาร ต่อให้ยอดเยี่ยมแค่ไหนก็ไม่คุ้มเวลาของฉัน ส่วนใหญ่หมายถึงฝั่งแอปพลิเคชัน และฉันแทบไม่เคยดูเอกสาร Linux เลย แต่ก็มีคนบอกว่าไม่ได้แย่ขนาดนั้นเหมือนกัน เลยไม่แน่ใจ
เป็นแนวคิดใหม่ที่น่าสนใจและฉันชอบมัน
คำถามจริง ๆ คือเรื่อง ความทุ่มเทของนักพัฒนาและชุมชน คนฝั่ง Caddy และ Nginx สนับสนุนผลิตภัณฑ์ของตัวเองมาอย่างสม่ำเสมอ และโปรเจ็กต์นี้ก็คงต้องการสมาธิและความใส่ใจอย่างมากเช่นกัน
ทำไมต้องเป็น tarball?
มันจะไม่แตกอะไรลงดิสก์เลย เพราะเว็บไซต์ทั้งหมดอยู่ในไฟล์เดียว จึงไม่มี document root ที่จะหลุดออกมาจากกฎ location ที่ตั้งผิด และการ deploy ก็กลายเป็นการสลับไฟล์เดียวแบบ atomic
แต่คำอธิบายนั้นก็อาจเป็นการหาเหตุผลแบบ LLM ได้เหมือนกัน มีถ้อยคำอย่าง “the right shape” หรือ “the surface is broad” กระจายอยู่ทั่วบทความ