Wasm คือ CGI ใหม่
(roborooter.com)- Wasm ไม่ใช่โปรโตคอล CGI แต่สามารถมองได้ว่าเป็นโมเดลแอปพลิเคชันถัดไปที่จะเปลี่ยน หน่วยการรันและวิธีการดีพลอย ของเว็บแอป เหมือนที่
cgi-binเคยทำไว้ - CGI, FastCGI, Rack/WSGI และ serverless ต่างออกแบบการจัดการคำขอและการบริหารเซิร์ฟเวอร์ใหม่มาโดยตลอด โดยมีเป้าหมายร่วมกันคือทำให้การสร้างและดูแล แอปพลิเคชันประสิทธิภาพสูง ง่ายขึ้น
- โมดูล Wasm รันอยู่บน host runtime และสามารถลดต้นทุนการเริ่มต้นได้ พร้อมรักษา สถานะการรันที่สะอาด ผ่านหน่วยความจำแบบแยกและ snapshot
- การไม่มี native threads, ไม่สามารถทำ JIT ได้, ต้นทุนการคัดลอกข้อมูล และความยังไม่สุกงอมของ Interface Types กับ module linking เป็นข้อจำกัดหลักของโมเดลการรันฝั่งเซิร์ฟเวอร์ที่ใช้ Wasm
- เมื่อ serverless functions และ edge functions ผสานกับสภาพแวดล้อมการรัน Wasm จะช่วยลด cold start และต้นทุนการแยกกั้น พร้อมประกอบโมดูลจากหลายภาษาได้เบากว่าเดิม
โมเดลเว็บแอปพลิเคชันตั้งแต่ CGI ถึง serverless
- แก่นของ CGI ไม่ได้อยู่ที่โปรโตคอล Common Gateway Interface เองเท่ากับ โมเดลแอปพลิเคชัน ที่เรียกสคริปต์หรือไฟล์ executable ในโฟลเดอร์
cgi-binผ่าน URL - วิธีนี้เป็นโมเดลเว็บแอปพลิเคชันยุคแรกที่เปลี่ยนเว็บจากคลังเอกสารให้เป็นเครือข่ายแอปพลิเคชันแบบโต้ตอบได้
- ด้วยโครงสร้างที่เริ่ม process ใหม่ทุกคำขอ ตามมาตรฐานปัจจุบัน ต้นทุนการเริ่ม process และการ parse สคริปต์จึงกลายเป็นคอขวดขนาดใหญ่
การเปลี่ยนแปลงของ FastCGI และเว็บเซิร์ฟเวอร์ตามภาษา
- FastCGI นำโมเดลที่ใช้ process อายุยาวมาจัดการคำขอ CGI เพื่อลดปัญหาประสิทธิภาพของ CGI
- เว็บเซิร์ฟเวอร์ไม่ต้องเปิด process ใหม่ทุกคำขอ แต่สื่อสารกับ process ที่รันระยะยาวหนึ่งตัวหรือมากกว่า
- แอปพลิเคชัน CGI เดิมถูกสร้างบนสมมติฐานว่า process จะจบหลังคำขอ ทำให้เกิด resource leak ได้ง่ายในสภาพแวดล้อมที่รันระยะยาว
- หลังจากนั้น เว็บเซิร์ฟเวอร์ที่อิงตามภาษาก็กลายเป็นแนวปฏิบัติปกติ และแอปพลิเคชันถูกสร้างโดยมีโมเดล request/response เป็นศูนย์กลาง
- โดยทั่วไปจะวางไว้หลังเว็บเซิร์ฟเวอร์ที่มีฟีเจอร์ครบและผ่านการพิสูจน์แล้ว เช่น Apache หรือ nginx เพื่อแยกแอปพลิเคชันออกจากคำขอที่ช้าและรายละเอียดของ HTTP
- การทำงานของเซิร์ฟเวอร์ใช้ กลยุทธ์การจัดการ process หลากหลาย เช่น fork ต่อคำขอ, thread ของ OS หรือภาษา, event หรือ reactor model
- อินเทอร์เฟซ Rack ของชุมชน Ruby มีอิทธิพลต่อ application server ของ Flask ใน Python และสเปก WSGI ด้วย
- หากอธิบายแบบย่อ คำขอประกอบด้วย HTTP method, hashmap ของ headers และ input byte string หรือ stream
- คำตอบประกอบด้วย status code, header object และ response byte string หรือ stream
การประนีประนอมระหว่าง cloud autoscaling กับ serverless
- ในโมเดลที่ใช้เซิร์ฟเวอร์จริงหรือเซิร์ฟเวอร์เสมือน ต้องจัดการจำนวนเซิร์ฟเวอร์ที่กำลังรันเอง
- หากทราฟฟิกสูง application server อาจช้าลงได้
- หากทราฟฟิกต่ำ คอมพิวเตอร์จำนวนมากอาจเหลือ idle
- autoscaling บนคลาวด์ทำให้ปรับจำนวน application server ตาม CPU, ภาระหน่วยความจำ และช่วงเวลาได้
- การขยายคอมพิวเตอร์ใหม่อาจใช้เวลา 2–20 นาที ขึ้นอยู่กับแอปพลิเคชันและการตั้งค่า
- การขยายยังขึ้นอยู่กับทรัพยากรที่พร้อมใช้ใน region ของ cloud hosting ด้วย
- serverless compute ที่ผสาน Amazon Lambda กับ API Gateway สร้างโมเดลที่จัดการฟังก์ชันแทนเซิร์ฟเวอร์
- รับประกัน process เดี่ยว, CPU แยก และหน่วยความจำแยกสำหรับแต่ละคำขอ
- process อาจถูกนำกลับมาใช้ซ้ำได้นานถึงหลายชั่วโมง แต่หากไม่ถูกใช้งานจะถูกพักหรือกำจัดออก
- AWS สามารถขยายและลดขนาดตามจำนวนคำขอได้ในระดับไม่กี่วินาที
- serverless มี trade-off ที่ชัดเจน
- เนื่องจากต้นทุนของ process ใหม่ เมื่อขยายเพื่อรองรับคำขอพร้อมกัน คำขอบางส่วนจะเจอ cold start
- หลังส่งคำตอบแล้ว process อาจถูกพัก ทำให้การจัดการ TCP connection แบบต่อเนื่องระหว่างคำขอทำได้ยาก
- API ฐานข้อมูลผ่าน HTTP ได้รับความนิยมในโมเดล serverless เพราะรองรับ connection จำนวนมากได้ดี และขยาย/ลดขนาดไปพร้อมกับฟังก์ชันได้ง่าย
- คุณสมบัติที่ให้ CPU และหน่วยความจำเฉพาะต่อคำขอเป็นได้ทั้งข้อดีและข้อเสีย ขึ้นอยู่กับ workload
- workload บางประเภทสามารถลดการจัดการทรัพยากร และลดความกังวลเรื่องต้นทุนกับการขยายได้
- workload อื่นอาจมีประสิทธิภาพกว่าเมื่อ process เดียวจัดการคำขอจำนวนมาก หรือใช้ shared memory เพื่อทำ batching และ caching
- มีการยกตัวอย่างทั้งกรณีที่ย้ายเว็บแอปพลิเคชันแบบ CMS ไป serverless แล้วลดต้นทุนได้ 90% และกรณีที่ย้ายบริการวิเคราะห์ event ไปเป็นโมเดลแบบเซิร์ฟเวอร์แล้วลดต้นทุนได้ 90%
โมเดลการรัน Wasm บนเซิร์ฟเวอร์
- เดิมที Wasm ถูกพัฒนาขึ้นเพื่อรันโค้ดประสิทธิภาพสูงในเบราว์เซอร์ และเกี่ยวข้องกับความพยายามอย่าง asm.js ด้วย
- WebAssembly เป็นรูปแบบคำสั่งไบนารีสำหรับ virtual machine แบบ stack-based และถูกออกแบบให้เป็น compile target แบบพกพาได้สำหรับหลายภาษาโปรแกรม
- ช่วยให้ดีพลอยแอปพลิเคชันฝั่ง client และ server ของเว็บได้
- ออกแบบให้เข้ารหัสเป็นรูปแบบไบนารีที่มีประสิทธิภาพด้านขนาดและเวลาโหลด
- มุ่งรันให้ใกล้เคียง native speed โดยใช้ความสามารถฮาร์ดแวร์ร่วมของแพลตฟอร์มต่าง ๆ
- ปัจจุบันสามารถคอมไพล์หลายภาษาเป็นคำสั่ง Wasm แล้วรันได้ทั้งในเบราว์เซอร์และเซิร์ฟเวอร์
- โมเดล isolation และ security ที่เบราว์เซอร์ต้องการก็มีประโยชน์ต่อแอปพลิเคชันฝั่งเซิร์ฟเวอร์เช่นกัน
- สามารถแยกกั้นโค้ดที่ไม่น่าเชื่อถือได้ในรูปแบบที่เบากว่า VM หรือ Docker container มาก
- สภาพแวดล้อม serverless ที่ใช้ V8 เช่น Node.js, Cloudflare Workers และ Deno ใช้ประโยชน์จากความสามารถในการรัน Wasm ที่สะสมมาจากเบราว์เซอร์
- ยังมีสภาพแวดล้อมแบบ Wasm-native เช่น Fastly, Shopify และ Suborbital ด้วย
โมดูล, host, หน่วยความจำ และ snapshot
- โมดูล Wasm เป็นคำสั่งสำหรับ virtual machine จึงต้องมี runtime เพื่อรัน
- runtime คอมไพล์ Wasm ทั่วไปให้เหมาะกับสถาปัตยกรรมเครื่อง และจัดเตรียมสภาพแวดล้อมการรัน
- บางสภาพแวดล้อมให้อินเทอร์เฟซที่คล้าย POSIX API ของระบบ Linux
- บางสภาพแวดล้อมให้เฉพาะฟังก์ชันบางอย่างของ host system และให้รันฟังก์ชัน export ของโมดูล
- โปรแกรม WebAssembly ประกอบขึ้นเป็นหน่วย modules และ VM ที่รันโมดูลเรียกว่า host
- โมดูลเป็นหน่วยของการดีพลอย การโหลด และการคอมไพล์
- รวมคำจำกัดความของ type, function, table, memory และ global value
- สามารถประกาศ import และ export และให้การ initialization ในรูปแบบ data segment, element segment และ start function ได้
- “memories” ของ Wasm แสดงเป็นอาร์เรย์ไบต์ต่อเนื่องไม่ขาดตอน ซึ่ง host จัดสรรตอน instantiate
- guest module แต่ละตัวได้ memory isolation
- หน่วยความจำนี้ทำงานเหมือน RAM ของ virtual machine
- สามารถให้มาแบบว่าง หรือเติมไว้ล่วงหน้าด้วย data segment ได้
- ด้วยวิธีแยกกั้นโมดูลนี้ จึงสามารถพักโมดูลและบันทึกหน่วยความจำเป็น data segment ได้
- เป็นแนวคิดคล้าย snapshot ของ virtual machine
- สามารถเริ่มสำเนาหลายชุดของโมดูลที่ถูกพักไว้ได้
- Wizer instantiate โมดูล Wasm, รัน initialization function แล้วบันทึกสถานะของ instance เพื่อเขียน binary ของ Wasm ใหม่
- initialize สถานะของ global value โดยตรง
- บันทึกพื้นที่หน่วยความจำที่ไม่ใช่ศูนย์
- ลบ data segment เดิมและแทนที่ด้วย data segment สำหรับพื้นที่หน่วยความจำที่บันทึกไว้
- วิธีนี้ให้การรันที่สะอาดเหมือน process ใหม่ พร้อมหลีกเลี่ยงต้นทุนการเริ่ม process ใหม่
- ใกล้เคียงกับ CGI ที่ไม่มีข้อเสียของ CGI
- หากพูดด้วยคำสมัยใหม่ ก็ใกล้เคียงกับ serverless ที่ไม่มี cold start
ข้อจำกัดและข้อดีของ Wasm
- Wasm ไม่มี threads ในองค์ประกอบแบบ native
- งานแบบ blocking ต้องย้ายไปเป็น host method
- host อาจให้ wrapper สำหรับอ่าน/เขียนไฟล์หรือ network interface, พักโมดูล หรือให้ callback handler
- สามารถสร้าง reactor model หรือ blocking model ได้ แต่ก่อนที่ thread proposal จะถูกนำมาใช้ การออกแบบสภาพแวดล้อมการรันยังมีน้ำหนักมาก
- ด้วยเหตุผลด้านความปลอดภัย จึงไม่อนุญาตให้สร้างโค้ด Wasm แบบ dynamic ทำให้ไม่สามารถทำ JIT compilation ได้
- ใน runtime ไม่สามารถถือว่าตัวโค้ดเองเป็น address ได้
- สภาพแวดล้อมการรันที่พึ่งพา JIT เพื่อประสิทธิภาพ เช่น V8, CRuby อาจรันบน Wasm VM ไม่ได้ หรือจำเป็นต้องยอมทิ้ง JIT
- มีการเสนอแนวทาง “pre-jit” ที่ output runtime สำหรับสคริปต์ที่ optimize แล้วในขั้นตอน build แต่ยังไม่ถูกใช้อย่างแพร่หลาย
- อินเทอร์เฟซพื้นฐานระหว่างโมดูล Wasm กับ host คือหน่วยความจำ ดังนั้นการย้ายข้อมูลอาจต้องคัดลอก
- การแชร์ memory chunk ทำได้ แต่ความเป็นไปได้หรือความเหมาะสมขึ้นอยู่กับ memory model ของ runtime
- ใน runtime ส่วนใหญ่ การสื่อสารแบบ zero-copy ระหว่างโมดูลกับงาน I/O ถือว่าทำได้ยาก
- การย้าย streaming data เข้าและออกจากโมดูล Wasm อาจช้ากว่าการจัดการใน host
- Wasm VM ให้การควบคุมสูงในแง่การจำกัดการใช้งาน
- runtime บางตัวสามารถบังคับ CPU limit ด้วยการนับจำนวนคำสั่ง CPU เช่น Wasmtime fuel concept
- VM สามารถจำกัดหน่วยความจำและเวลา wall clock ได้
- Interface Types และ module linking สามารถลดขนาดโมดูล และปรับปรุงความเร็ว I/O กับความใช้งานง่ายได้
- Interface Types ใช้งานได้ แต่ยังไม่ใช่มาตรฐานที่ผ่านการให้สัตยาบัน
- module linking มี prototype ที่ทำงานได้ แต่ยังไม่มีแนวทางมาตรฐาน
- ปัจจุบัน Wizer อาจขัดกับ module linking และแม้มีแนวทางเฉพาะเพื่อแก้ปัญหานี้ แต่ยังไม่มีผู้ชนะที่ชัดเจน
- Interface Types มุ่งให้ object แบบ language-neutral ผ่านขอบเขตหน่วยความจำของ Wasm ได้โดยไม่ต้อง encode/decode ที่มีต้นทุนสูง
- วิธีที่พบทั่วไปตอนนี้คือคัดลอก JSON เข้าและออกจากหน่วยความจำ
- โดยพื้นฐานแล้ว โมดูล Wasm เข้าถึงได้เฉพาะสิ่งที่ได้รับมา ทำให้ security model มีขนาดเล็กและชัดเจน
- การรันโค้ด Wasm ที่ไม่น่าเชื่อถือโดยทั่วไปถือว่าค่อนข้างปลอดภัย
- attack surface ของ VM เล็กกว่า Docker หรือโมเดล isolation อื่น ๆ
- เนื่องจากรันคำสั่งที่แปลงแล้วบนฮาร์ดแวร์ของ host จึงยังมี timing attack ได้ แต่มีมาตรการบรรเทา
- สามารถคอมไพล์ Wasm เป็น native binary ได้เช่นกัน แต่ attack surface จะใหญ่ขึ้น
Edge functions และการรันฟังก์ชันบน Wasm
- หากสภาพแวดล้อมการรัน Wasm และเครื่องมือพัฒนาแพร่หลาย ภาษา scripting ก็อาจถูกกดดันให้มี preboot แบบ Wasm runtime และ Wizer ด้วย
- ในทางทฤษฎี แอปพลิเคชันสามารถ resume จากสำเนาที่ snapshot ไว้ในแต่ละคำขอ และรันได้เร็วกว่าโมเดลอื่นในปัจจุบัน
- CLI ในเครื่องก็สามารถเขียนด้วย Ruby แล้วดีพลอยเป็นโมดูล Wasm ที่ snapshot ไว้และเชื่อมกับ Wasm Ruby runtime เพื่อให้ได้เวลาเริ่มต้นใกล้เคียง utility ใน C++ และจำกัดให้ทำงานเฉพาะภายใน project directory ได้
- การเปลี่ยนแปลงสำคัญแรกคือการย้ายฟังก์ชันไปยัง edge ที่ใกล้ผู้ใช้
- compute จะทำใกล้ผู้ใช้ ไม่ใช่ใกล้ฐานข้อมูล
- Vercel Edge Functions ถูกยกเป็นตัวอย่างที่ใช้ V8 แต่มีหลักการร่วมกันหลายอย่าง
- next-auth สามารถควบคุมการเข้าถึงหน้า pre-rendered ตาม JWT login token ได้
- สามารถให้บริการเนื้อหาแบบ dynamic personalization ที่ประกอบจากข้อมูลที่ cache อยู่ใน CDN ได้
- อีกการเปลี่ยนแปลงคือการแทนที่ฟังก์ชันแบบ process-based ในแอปพลิเคชัน serverless ด้วยฟังก์ชันแบบ Wasm-based
- Suborbital ช่วยให้สร้างสภาพแวดล้อมการรันฟังก์ชันบน Wasm ได้เอง และยังมีวิธี chain ฟังก์ชัน Wasm เป็น workflow
- แพลตฟอร์มการรันจำนวนมากแนะนำให้ใช้โมดูลเดียวต่อคำขอ และถูกมองว่ายังใช้ประโยชน์จาก memory sharing หรือการเรียกหลายโมดูลอย่างรวดเร็วได้ไม่เต็มที่
- หาก Interface Types ได้มาตรฐาน อาจเกิด data model สำหรับ middleware ที่คล้าย Rack
- Wasm สามารถรันภายใน Lambda function ได้เช่นกัน
- สามารถใช้เวอร์ชันแอปพลิเคชันที่ snapshot ไว้บนโครงสร้างพื้นฐานเดิมได้
- การผสมผสานนี้น่าสนใจ แต่ถูกมองว่าเป็นการผสมเทคโนโลยีที่อาจหายไปในท้ายที่สุด
ทิศทางของโมเดลเว็บแอปพลิเคชันถัดไป
- Wasm ถูกนำเสนอว่าเป็นเทคโนโลยีที่สามารถเพิ่มประสิทธิภาพ ทำให้ความปลอดภัยระดับ process ทำได้ง่ายขึ้น และลดต้นทุนการ build กับการรัน serverless functions
- สามารถรันได้แทบทุกภาษา และผ่าน module linking กับ Interface Types จะลด latency ระหว่างฟังก์ชันได้มาก
- เหตุผลหลักที่มองว่า Wasm เป็น CGI ใหม่ คือเมื่อข้อจำกัดของระบบเปลี่ยนไป สิ่งที่ก่อนหน้านี้เป็นไปไม่ได้ก็จะเป็นไปได้
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ยังไม่ค่อยเห็นว่า WASM ต่างจากเทคโนโลยีเก่า ๆ อย่าง Java Applet, ActiveX, Silverlight, Macromedia Flash ตรงไหน
นึกว่าเราได้บทเรียนกันแล้วเรื่องการรัน โค้ดคอมไพล์ของบุคคลที่สามที่ไม่น่าเชื่อถือ ในเบราว์เซอร์ และมันดูเหมือนสถาปัตยกรรมที่ผลักภาระต้นทุนการคำนวณของเซิร์ฟเวอร์ไปให้ไคลเอนต์ในนามของการปรับปรุงประสบการณ์ลูกค้า
implementation มีช่องโหว่มากมาย และสุดท้ายก็แทบใช้ในเบราว์เซอร์ไม่ได้ ส่วนเทคโนโลยีอื่น ๆ ดูเหมือนจะไม่ได้ให้คำมั่นแบบนั้นตั้งแต่แรกด้วยซ้ำ
JavaScript รักษาสัญญานั้นได้จริง และทุกวันนี้เบราว์เซอร์ก็รัน JavaScript ที่ดาวน์โหลดมาจากโดเมนใดก็ตามโดยไม่ถามผู้ใช้ว่าจะเชื่อถือหรือไม่
WASM ทำงานอยู่บนเอนจิน JavaScript และให้การรับประกันด้านความปลอดภัยคล้ายกัน ดังนั้นจึงไม่ได้ต่างจาก JVM bytecode โดยพื้นฐาน ความต่างในทางปฏิบัติคือ WASM พิสูจน์ความปลอดภัยได้แล้ว แต่ JVM ทำไม่ได้
ตอนนี้ Google Chrome ปลอดภัยพอที่แม้ผู้ใช้หลายพันล้านคนจะรัน WASM ที่เป็นอันตราย โทรศัพท์ก็ยังไม่ถูกเจาะ และเราสามารถนำเอนจินนี้ไปไว้บนเซิร์ฟเวอร์เพื่อรันสคริปต์ของผู้ใช้หลายคนภายในแซนด์บ็อกซ์ที่แข็งแรง พร้อมแชร์ทรัพยากรกันได้
ทางเลือกคือ virtualization โดยอาจคอมไพล์โค้ดเป็นก้อน WASM แล้วรันบนเซิร์ฟเวอร์ WASM ขนาดใหญ่ หรือเอาไบนารี amd64 มามัดรวมกับ Linux kernel ที่ตัดทอนแล้วไปรันใน VM
ตอนนี้ยังบอกไม่ได้ชัดเจนว่าฝั่งไหนเป็นผู้ชนะ แต่ละแนวทางมีข้อดีข้อเสียของตัวเอง
ต่างจาก JVM, WASM ให้หน่วยความจำแบบ linear memory และโดยพื้นฐานไม่มี garbage collection จึงเหมาะจะเป็นเป้าหมายการคอมไพล์สำหรับภาษาที่กว้างกว่า เช่น คอมไพล์ C/C++ ด้วย Emscripten หรือใช้เป็น target สำหรับ Rust
WASM เป็น bytecode และผมมองว่า implementation ส่วนใหญ่แชร์หลายอย่างกับเอนจินและ runtime ของ JavaScript ฝั่งโฮสต์
การย้ายต้นทุนการคำนวณระหว่างไคลเอนต์กับเซิร์ฟเวอร์เป็นส่วนหนึ่งของกระแสที่อุตสาหกรรมแกว่งไปมาระหว่าง ไคลเอนต์หนาและไคลเอนต์บาง มานานแล้ว และลูกตุ้มนี้ก็จะยังแกว่งต่อไป
มี สเปกการตรวจสอบความถูกต้อง ที่ Wasm bytecode ต้องปฏิบัติตาม และด้วย subset ที่ผ่านการตรวจสอบนี้ ช่องโหว่ด้านความปลอดภัยจำนวนมากที่เคยเห็นในเทคโนโลยีก่อน ๆ จึงเป็นไปไม่ได้ตั้งแต่ต้น
การโจมตีที่อาศัยความผิดปกติของฮาร์ดแวร์อย่าง Heartbleed หรือ Rowhammer อาจยังเป็นไปได้ แต่เช่น จะหลอก VM ให้ตีความตัวเลขเป็น pointer แล้วอ้างอิงออกไปนอกหน่วยความจำ Wasm ของตัวเองนั้นทำไม่ได้
Wasm bytecode ค่อนข้างเรียบง่ายมากในการแปลงเป็นภาษาเครื่อง ดังนั้น implementation อาจเล็กและเร็วกว่าเมื่อเทียบกับการใช้ VM
มีสเปกเปิดที่เขียนไว้อย่างดี ไม่ได้เป็นของบริษัทใดบริษัทหนึ่ง และใครก็ใช้ได้ อีกทั้งถูกนำมาใช้เป็นมาตรฐานเว็บ จึงไม่ต้องมีส่วนขยายเบราว์เซอร์
การคำนวณฝั่งไคลเอนต์เกิดขึ้นอยู่แล้วใน JavaScript และโค้ด Wasm ก็สามารถมีประสิทธิภาพในแบบที่ JavaScript ทำไม่ได้ จึงยิ่งเป็นเช่นนั้น
Applet ถูกควบคุมอยู่บ้าง ส่วน ActiveX แทบไม่ถูกควบคุมเลย และ “แพลตฟอร์มภายนอก” ของ WASM โดยคร่าว ๆ คือ JavaScript runtime ขณะที่แพลตฟอร์มภายนอกของ Applet ใกล้เคียงกับ execve(2)
WASM อาจใช้ทำแบบนั้นได้ แต่ไม่ได้เป็นเช่นนั้นเสมอไป และความแตกต่างอย่าง sandboxing กับ isolation ที่ดีกว่าก็มีคำตอบอื่น ๆ อธิบายไว้ดีแล้ว
ถ้าจะบอกว่า “Amazon เปิดยุค serverless computing ด้วย Lambda” นั้น Google App Engine ออกมาในปี 2008 เร็วกว่า Lambda ถึง 6 ปี
ผลิตภัณฑ์แบบนั้นมีอยู่แล้วหลายปีก่อนและก็มีชื่อเรียกอยู่แล้ว เลยไม่ค่อยแน่ใจว่าทำไมถึงมีชื่อ “serverless” เกิดขึ้น หรือมาจากไหน
App Engine ก็มีทั้ง batch worker และ web worker และ Heroku ก็เช่นกัน
ทั้งคู่เป็นผลิตภัณฑ์ก่อนยุค Docker ดังนั้นอาจทำให้ผู้คนรู้สึกต่างออกไป แต่ Lambda เองก็ดูเหมือนจะไม่ได้เปิดตัวด้วย Docker ตั้งแต่แรกเหมือนกัน
คำกล่าวที่ว่า “ด้วยเหตุผลด้านความปลอดภัย จึงไม่อนุญาตให้สร้างโค้ด Wasm แบบไดนามิก ทำให้ JIT compilation เป็นไปไม่ได้” ดูไม่น่าถูกต้อง
ถ้าจะอนุญาตงานอย่าง hot reloading โค้ดที่สะอาด มันก็แทบจะเป็นฟังก์ชันพื้นฐานอยู่แล้ว
ผมมองว่าเหตุผลด้านความปลอดภัยนี้ค่อนข้างหลวม
JS สามารถ hot reload ระหว่างรันหรือสร้างโค้ดที่หนักกว่านั้นได้โดยไม่ทำลายความปลอดภัย และถ้าโหลด Wasm runtime ทั้งก้อนใหม่แบบไดนามิกพร้อมรักษาหน่วยความจำไว้ ก็อาจเลียนแบบการสร้างโค้ดหรือ hot reload ได้ แต่ประสบการณ์ผู้ใช้คงจะหยาบมาก
ไม่เห็นเหตุผลทางเทคนิคว่าทำไมต้องเป็นไปไม่ได้ และถ้าเป็นมาตรการความปลอดภัยก็ดูเหมือนจะหลบเลี่ยงได้ง่ายเกินไป
โดยแนวคิดแล้ว WASM bytecode คล้ายกับสิ่งที่ออกแบบมาโดยคำนึงถึง JIT compilation อย่าง .NET IL หรือ Java bytecode มาก
ผมไม่ค่อยชอบ WASM เพราะรู้สึกว่าเป็นโปรเจกต์ที่ขาดทิศทางที่ชัดเจนและความมุ่งมั่นที่จะทำให้สำเร็จทันเวลา
ชื่อของมันสื่อถึง “assembly สำหรับเว็บ” หรือภาษาเครื่องสำหรับ CPU เสมือน แต่ในความเป็นจริงเป็น intermediate representation สำหรับ compiler backend และยังมีแผนใส่ฟีเจอร์ระดับสูงอย่างการรองรับ garbage collection ด้วย ทำให้แนวคิดไม่ชัดเจน
ฟีเจอร์พื้นฐานอย่าง hot reload ที่กล่าวไปก่อนหน้า, threading ที่ไม่ดูเหมือนการแฮ็ก, การเชื่อมกับ DOM โดยตรงโดยไม่ต้องใช้ JavaScript, API กราฟิก/คำนวณที่ overhead ต่ำ, การเข้าถึงเสียงระดับต่ำ ก็ยังขาดอยู่
การรันแอปมัลติมีเดียขนาดใหญ่โดยไม่ต้องประนีประนอมมากจึงทำได้ยาก
Wasm ไม่สามารถทำเครื่องหมายหน่วยความจำให้เป็น executable ได้ และในทางปฏิบัติโค้ดกับหน่วยความจำถูกแยกกันเหมือน สถาปัตยกรรม Harvard
ยิ่งไปกว่านั้นยังไม่สามารถกระโดดไปยังตำแหน่งใดก็ได้ในโค้ด และไม่มีแม้แต่คำสั่ง jump
JIT ในที่นี้หมายถึงการคอมไพล์และรัน native code ระหว่างการทำงาน ซึ่งในเบราว์เซอร์หรือ Wasm sandbox ถือเป็นช่องโหว่ความปลอดภัยขนาดใหญ่
สิ่งนี้ฝังอยู่ในการออกแบบและ instruction set จึงไม่ใช่ลักษณะที่หลบเลี่ยงได้ง่าย และอ่านรายละเอียดได้ที่ https://webassembly.org/docs/security/
Wasm ก็คล้าย .NET IL หรือ Java bytecode ตรงที่ engine เป็นผู้รับผิดชอบ JIT แต่การให้สิทธิ์ผู้ใช้สร้าง native code นอก runtime แล้วกระโดดไปตรงนั้นเป็นเรื่องอันตราย
ดังนั้นเหมือนกับ JIT เวลาเรียกใช้โค้ด WASM ครั้งแรกอาจเห็น “การกระตุกช่วงอุ่นเครื่อง” เล็กน้อย ซึ่งในช่วงไม่กี่ปีมานี้ดีขึ้นมากแล้ว
อีกอย่าง เท่าที่ทราบ ในเบราว์เซอร์สามารถสร้างก้อน WASM แบบไดนามิก แล้ว instantiate และรันได้
ไม่รู้ว่า runtime ของ WASM อื่น ๆ ทำได้ไหม และในเบราว์เซอร์ก็ยังต้องผ่าน JavaScript แต่ถ้าจะเข้าถึง “web API” ใด ๆ ก็ต้องทำแบบนั้นอยู่ดี
WASM คือการเปลี่ยน VM ที่เฉพาะเจาะจงกับภาษาหนึ่งอย่าง JavaScript VM ให้กลายเป็น VM อเนกประสงค์ ที่ใช้ได้ทุกที่ที่ JavaScript VM ถูกใช้งาน
แน่นอนว่าไม่ได้จำกัดอยู่แค่นั้น
คำว่าอเนกประสงค์หมายความว่า ถ้ามี compiler หรือ interpreter ก็แทบจะรันอะไรก็ได้ รวมถึง JavaScript ด้วย
โดยทั่วไปมันถูก implement เป็นส่วนหนึ่งของ JavaScript engine จึงสืบทอดคุณสมบัติหลายอย่าง เช่น sandboxing และการเข้าถึง API เหล่านั้น
งานในการทำให้การเข้าถึงนั้นเป็นมาตรฐานยังดำเนินอยู่ แต่ท้ายที่สุด สิ่งที่ตอนนี้ทำได้เฉพาะด้วย JavaScript ก็จะทำได้ใน WASM และยังจะทำสิ่งอื่น ๆ อีกมากที่ทำด้วย JavaScript ได้ยากหรือเป็นไปไม่ได้
อาจรันได้เร็วและลื่นไหลขึ้นด้วย
แก่นของ WASM คือการขจัดข้อจำกัดจำนวนมากที่มีอยู่ในสภาพแวดล้อมที่ JavaScript เป็นที่นิยม
JavaScript เป็นภาษาที่คนชอบหรือไม่ชอบชัดเจน ดังนั้นมันกำลังเปลี่ยนจากตัวเลือกเดียวไปเป็นหนึ่งในหลายตัวเลือก
WASM เคยถูกอธิบายว่าเป็นทั้งตัวแทนของ JavaScript, ตัวแทนของ Docker, ตัวแทนของ Java และตัวแทนของ CGI พูดสั้น ๆ คือมันเป็นทั้งหมดนั้นและมากกว่านั้น
มีเครื่องมือที่ทำงานคล้ายกันมากเกินไป และขอบเขตก็ไม่ชัดเจน
ต่อให้สุดท้ายทำให้ทุกอย่างรันได้ ส่วนตัวก็ยังรู้สึกว่ามันเปราะบาง และสำหรับคนที่ไม่ได้ทำเป็นอาชีพก็น่ากลัว
ทุกวันนี้เวลาต้องทำอะไรสำหรับเว็บ ผมใช้ leptos ซึ่งให้ developer experience ดีกว่ามาก และแม้จะยังไม่ถึง 1.x แต่ก็ให้ความรู้สึกมั่นคงกว่าการเอาเครื่องมือ 5 ตัวมาต่อกันเพื่อ transpile, obfuscate, minify และ pack JS bundle
อ่านบทความนี้แล้วนึกถึงกฎซอฟต์แวร์ที่เคยถูกยกมาพูดบ่อย ๆ
กฎที่ว่าแอปพลิเคชันที่ใหญ่พอและอยู่รอดมานานพอ สุดท้ายจะ reimplement software stack ทั้งหมด ที่มันรันอยู่ รวมถึงระบบปฏิบัติการด้วย และจะ reimplement มันได้อย่างห่วยแตก
ไม่แน่ใจแหล่งที่มา แต่โดยรวมมักจะถูกอยู่บ่อย ๆ
“โปรแกรม C หรือ Fortran ที่ซับซ้อนพอ จะมี implementation ของครึ่งหนึ่งของ Common Lisp แบบแก้ขัด ตามสเปกที่ไม่เป็นทางการ เต็มไปด้วยบั๊ก และช้า”
รูปแบบที่ทั่วไปกว่านี้เรียกว่า Inner-Platform Effect
ถ้าขยายสมมติฐานของชื่อเรื่องให้กว้างขึ้น เพื่อให้ WASM เป็นผู้สืบทอดที่แท้จริงของสายตระกูลนั้น มันต้องง่ายเท่ากับการอัปโหลดและ deploy แอป PHP บน LAMP stack ของผู้ให้บริการโฮสติ้งทั่วไป
ตอนนี้ยังดูเหมือนไม่ง่ายเท่า ประสบการณ์การ deploy PHP
ไม่รู้ว่าคาดหวังให้ฝั่งโฮสติ้งต้องเปลี่ยนอะไร
ผมมองเรื่องนี้ต่างออกไป
ผมคิดว่าอนาคตคือ local-first
คือแนวทางที่แอปแทบไม่ต้องพึ่งความช่วยเหลือจากเซิร์ฟเวอร์ และรันส่วนใหญ่ภายในเบราว์เซอร์ของผู้ใช้
แอปอย่าง Figma, Linear, Superhuman ใช้โมเดลนี้ได้สำเร็จมาก และ Stackblitz ก็เป็นแบบนั้นอยู่พอสมควร
ถ้าแอปที่ซับซ้อนพอสมควรอย่าง Figma สามารถรันเกือบทั้งหมดในเบราว์เซอร์ของผู้ใช้ได้ ผมก็มองว่าแอปส่วนใหญ่ก็น่าจะทำได้
ฝั่งเซิร์ฟเวอร์หลัก ๆ มีหน้าที่ซิงก์ข้อมูลระหว่างอินสแตนซ์ต่าง ๆ เมื่อผู้ใช้ใช้แอปจากหลายที่
เครื่องมืออย่าง Electric-SQL กำลังถูกสร้างขึ้นอยู่ แต่ยังไม่สุกงอม และเมื่อไลบรารีแบบนี้สุกงอมแล้ว พื้นที่นี้จะเติบโตอย่างมาก
Serverless โดยมากเป็นสิ่งที่บริษัทอย่าง Amazon และ Azure ใช้ทำเงิน และท้ายที่สุดก็จะกลายเป็นเหมือน CGI
WASM ก็อาจประสบความสำเร็จได้ แต่มีแนวโน้มว่าจะอยู่ภายในเบราว์เซอร์ของผู้ใช้เป็นหลัก
Microsoft ใช้ WASM กับ C#/Blazor อยู่ แต่ผมไม่คิดว่า dotnet ในเบราว์เซอร์จะเร็วเท่า JavaScript ได้ จึงไม่มองว่าเป็นแนวทางที่ถูกต้อง
เพียงแต่ไม่มีใครพูดถึง เพราะมันสเกลไปถึงการแสดงโฆษณา 1 ล้านล้านครั้งต่อวินาทีไม่ได้
ทุกครั้งที่มีคนเขียน serverless function ขึ้นมาหนึ่งตัว ก็เหมือนเพิ่มความยาวให้เรือยอชต์ของ Bezos อีก 10 ฟุต
ถ้าออฟไลน์หรืออยู่ในที่ที่ Wi‑Fi ไม่เสถียร คุณจะโหลดดีไซน์ไม่ได้ และถ้าไม่ได้มีการเปลี่ยนแปลงล่าสุด หากแก้ไขแล้ว Wi‑Fi หลุดก่อนปิดเบราว์เซอร์ งานก็จะไม่ถูกบันทึก
และมันก็ไม่ใช่แนวคิดใหม่แต่อย่างใด
เรากำลังพัฒนา Blazor WASM อยู่ และในแง่ประสิทธิภาพ dotnet ไม่ใช่ปัญหา
โดยพื้นฐานแล้วนี่คือการสร้าง JVM กับระบบนิเวศของมันขึ้นมาใหม่หรือเปล่า?
บางครั้งการสร้าง X ขึ้นมาใหม่โดยสะท้อนบทเรียนจากอดีตก็เป็นไอเดียที่ยอดเยี่ยมจริง ๆ
ตัวอย่างเช่น ตอนราวปี 2010 ที่เริ่มสำรวจการรันโค้ด C/C++ ในเบราว์เซอร์ การคอมไพล์ C/C++ ไปเป็น JVM แทบเป็นไปไม่ได้เลย
ตอนนั้น Java Applet ยังมีความหมายอยู่ ถ้าทำได้ก็คงดี แต่ WASM ก็ยังไม่มี ส่วน Emscripten มีแล้ว และนั่นนำไปสู่ asm.js ก่อนจะกลายเป็นจุดกำเนิดของ WASM ในที่สุด
ในฐานะคนที่เคยยุ่งกับ class loader และเขียน JVM assembly เองในวิชาภาษาโปรแกรมช่วงปี 2000 ผมก็ยังไม่แน่ใจว่า JVM คือจุดสูงสุดของสาขานี้หรือไม่
จริงอยู่ที่มันทำให้เกิดระบบนิเวศขนาดใหญ่ได้ แต่อินเทอร์เฟซที่ JVM ใช้เชื่อมกับโลกภายนอกนั้นเทอะทะและยุ่งเหยิงจริง ๆ
ตลอดกว่า 20 ปีที่ผ่านมา ทุกครั้งที่เจออะไรเกี่ยวกับ JVM ก็อดถอนหายใจไม่ได้
ถ้าเปรียบเทียบระบบแพ็กเกจและระบบนิเวศของ Rust กับ Python หรือที่แย่กว่านั้นคือ C++ จะเห็นว่าการคิดค้นขึ้นใหม่โดยนำบทเรียนตลอดหลายทศวรรษมาใช้ อาจเป็นเรื่องที่ดีมากได้
การรวมไฟล์ wasm สองไฟล์ให้เป็นไฟล์เดียว และจัดการการผสานหน่วยความจำให้ถูกต้อง ยังทำได้ยากมาก
component model อาจแก้ได้ก็จริง แต่มีองค์ประกอบที่บวมเกินจำเป็นมากเกินไป จนอาจต้องใช้เวลานานกว่าจะถูก Safari นำไปใช้
ผมคิดมานานแล้วว่า WASM จะพาเราไปสู่โลกที่มันมาแทนโค้ด Lambda function บนคลาวด์
ตามธรรมเนียมแล้ว WASM มักถูกมองว่ารันอยู่บนแพลตฟอร์มโฮสต์ แต่ก็ไม่มีเหตุผลว่าต้องเป็นอย่างนั้นเสมอไป
ด้วย ลักษณะ sandbox ของ WASM ในเชิงเทคนิคจึงอาจรันนอกระบบปฏิบัติการหรือใน ring0 เพื่อหลีกเลี่ยง overhead จำนวนมากของระบบปฏิบัติการได้
ถ้าคอมไพล์เป็น WASM ปัญหาหลายอย่างด้านการ deploy สำหรับผู้ใช้จะง่ายขึ้นมาก และสภาพแวดล้อมโฮสติงก็มีพื้นที่ให้ปรับแต่งได้มากขึ้น
ถึงขั้นอาจมีฮาร์ดแวร์เฉพาะสำหรับรัน WASM ให้เร็วขึ้นได้ด้วย
การต้องรันโค้ดตามอำเภอใจที่ไม่น่าเชื่อถือจากแหล่งที่มาไม่น่าเชื่อถือโดยอัตโนมัตินั้นเป็นเรื่องไม่ดี และก็ไม่อาจถือได้ว่ามาเติมบทบาทแบบเดียวกับ CGI ฝั่งเซิร์ฟเวอร์