- เมื่อการรั่วไหลและการแครชยังคงเกิดขึ้นต่อเนื่องใน Zig ที่ไม่มีความปลอดภัยด้านหน่วยความจำ Bun จึงย้ายโค้ด 535,496 บรรทัด ไปยัง Rust ด้วย AI agent 64 ตัว ทำให้งานที่อาจใช้เวลา 1~2 ปีสั้นลงเหลือ 11 วัน
- จุดเริ่มต้นของความสำเร็จคือ
PORTING.mdยาว 600 บรรทัด จากนั้นจึงทำการแปลงแบบขนานแยกตามไฟล์ พร้อม การรีวิวเชิงปฏิปักษ์ 2 รอบ, แก้ข้อผิดพลาดจากการคอมไพล์, ทดสอบในเครื่อง, และผ่าน CI ตามลำดับ - การสร้างคอมมิต 6,500 รายการ ใช้ต้นทุนตามราคา API 165,000 ดอลลาร์ พร้อมโทเคนอินพุตที่ไม่แคช 5.9 พันล้าน, เอาต์พุต 690 ล้าน, และการอ่านโทเคนอินพุตจากแคช 72 พันล้านโทเคน
- หากทำด้วยมือ คาดว่าวิศวกร 3 คนที่รู้จักโค้ดเบสดีจะต้องหยุดการปรับปรุงผลิตภัณฑ์, การแก้บั๊กและความปลอดภัย, และการพัฒนาฟีเจอร์ใหม่เป็นเวลาราว 1 ปี ทำให้การเขียนใหม่แทบเป็นไปไม่ได้
- หากจะทำวิธีเดิมซ้ำอีก จำเป็นต้องมีวิศวกรที่เข้าใจโค้ดเบสอย่างลึกซึ้ง, ชุดทดสอบที่แข็งแรง พอจะเชื่อถือผลลัพธ์ได้, และความพร้อมจะรับต้นทุนโทเคนแม้ความสำเร็จยังไม่แน่นอน
เหตุผลที่ Bun เลือกเขียนใหม่เป็น Rust
- Bun เป็น โปรเจ็กต์ production ที่ซับซ้อน ซึ่งให้ความสามารถมากกว่าการเป็น JavaScript runtime
- การแปลง JavaScript·TypeScript·CSS, bundling, minify
- test runner และ package manager ที่เข้ากันได้กับ npm
- การ resolve โมดูล, WebSocket client, implementation ของ Node.js และโมดูลอีกหลายตัว
- มียอดดาวน์โหลดรายเดือน 22 ล้านครั้ง และ Claude Code กับ OpenCode ก็พึ่งพา Bun ขณะที่ Vercel·Railway·DigitalOcean ให้การสนับสนุนโดยตรง
- Zig ไม่ใช่ภาษาแบบ memory-safe จึงยังเกิด memory leak, การแครชจากปัญหาหน่วยความจำ, และการเขียนเกินขอบเขต heap แม้ใน Bun เวอร์ชันล่าสุด
- ทีม Bun แพตช์คอมไพเลอร์ Zig และนำการทดสอบ memory leak แบบ end-to-end มาใช้ แต่ก็ยังไม่สามารถกำจัดปัญหาได้
- ระหว่างจัดการอายุการใช้งานของค่าจาก garbage collection และค่าที่ต้องจัดการด้วยมือร่วมกัน ก็เกิดการรั่วไหลเล็ก ๆ และการแครชเป็นพัก ๆ
- ทุกครั้งที่มีการจัดสรรหน่วยความจำ ต้องตรวจตำแหน่งที่จะ free, ความเป็นไปได้ของการ free ซ้ำ, การจัดการ JavaScript exception, และการมองเห็น pointer ของ conservative stack scanner
- ใน Rust แบบปลอดภัย ปัญหา use-after-free และ double-free จะกลายเป็นข้อผิดพลาดตอนคอมไพล์ และปัญหาการลืม free ในเส้นทางข้อผิดพลาดสามารถจัดการได้ด้วยการ cleanup อัตโนมัติบนพื้นฐาน
Drop - มีการพิจารณาวิธีนำ smart pointer แบบ Rust มาใช้ในโค้ดของ Bun เองเช่นกัน แต่ใช้งานได้แย่กว่า Rust และก็ยังให้การรับประกันแบบเดียวกันไม่ได้
ทำไมโปรเจ็กต์เขียนใหม่แบบเดิมจึงใช้เวลานาน
- ระหว่างเขียนใหม่ ฟีเจอร์ก็ยังถูกเพิ่มเข้าไปในโค้ดเบสเดิมอย่างต่อเนื่อง จึงมักทำให้กำหนดเสร็จเลื่อนออกไปซ้ำ ๆ
- งานที่คาดว่าใช้ 9 เดือน อาจผ่านไป 9 เดือนแล้วยังเหลืออีกประมาณ 6 เดือน
- แม้ผ่านไป 15 เดือน ก็อาจยังเหลืองานอีกหลายเดือนเพราะต้องตามฟีเจอร์ใหม่ให้ทัน
- ถึงจะโชคดี ก็อาจต้อง freeze ฟีเจอร์ 2 เดือน แล้วค่อยเสร็จในราว 18 เดือน ทำให้การประเมินแรกที่ 9 เดือนยืดเป็นเกิน 2 ปี
- โค้ด Zig ของ Bun เมื่อตัดคอมเมนต์ออกแล้วมี 535,496 บรรทัด จึงคาดว่าทีมวิศวกรขนาดเล็กจะต้องใช้เวลาราว 1 ปีหากย้ายไปภาษาอื่น
- การใช้เวลา 1 ปีโดยไม่มีการปรับปรุงที่ผู้ใช้มองเห็นไม่ใช่ทางเลือกที่สมจริง จึงตัดสินใจทดลองกับ Fable เพื่อดูว่าสามารถพิสูจน์ความเป็นไปได้ของการพอร์ตไป Rust ภายใน 1 สัปดาห์ได้หรือไม่
การออกแบบล่วงหน้าและการตรวจสอบเพื่อการพอร์ต
- ในขั้นแรก มีการคุยกับ Claude ราว 3 ชั่วโมง ถึงวิธีแม็ปแพตเทิร์นของ Zig ให้ใกล้เคียงกับ Rust และสรุปออกมาเป็น
PORTING.mdยาว 600 บรรทัด - แนวทางการพอร์ตมีข้อจำกัดเฉพาะเพื่อคงโครงสร้างการทำงานเดิมของ Bun
- ไม่ใช้
tokio,rayon,hyper,async-trait,futures - ห้ามใช้โมดูลที่เข้าถึง I/O เช่น
std::fs,std::net,std::process - เพราะ Bun เป็นเจ้าของ event loop และ system call ของตัวเอง จึงใช้ callback และ state machine แบบ Zig เดิมแทน
async fn - หากชนกับ borrow checker ให้เก็บค่า scalar ที่จำเป็นไว้ในตัวแปรภายในก่อน จบการยืมแล้วจึงยืมใหม่
- ห้ามใช้ raw pointer เพื่อหลบเลี่ยง borrow checker และให้ทิ้งบันทึกการพอร์ตไว้ในจุดที่มีการเปลี่ยนโครงสร้าง
- ไม่ใช้
- จากทั้งหมด 1,448 ไฟล์ มีการเขียนใหม่ 3 ไฟล์ ก่อน แล้วให้ Claude รีวิวเชิงปฏิปักษ์ 2 รอบในเซสชันที่แยกจากงานแก้ไข
การทำงานแบบขนานของ AI agent 64 ตัว
- แบ่งงานเพื่อให้ประมวลผลไฟล์ได้อย่างอิสระต่อกัน และรัน AI agent 64 ตัว แบบขนาน
- ช่วงแรกเกิดการชนกัน เพราะหลาย agent แตะสถานะเดียวกันของ repository
- agent ตัวหนึ่งรัน
git stashแล้วอีกตัวรันgit stash popกับgit reset HEAD --hard - หากจัด worktree แยกให้แต่ละ agent ก็จะติดปัญหาพื้นที่ดิสก์ไม่พอเพราะขนาดของ repository Bun และสุดท้ายก็ยังต้องคอมไพล์การเปลี่ยนแปลงรวมกันอยู่ดี
- agent ตัวหนึ่งรัน
- จึงแก้ workflow โดยห้ามคำสั่ง Git อย่าง
git stash,git resetนอกจากคำสั่งที่คอมมิตไฟล์ที่กำหนดทันที และยังห้ามใช้cargoกับคำสั่งที่ใช้เวลารันนาน - สุดท้ายแบ่งงานลงใน 4 worktree และในแต่ละอันให้ Claude 16 ตัวคอมมิตและ push ไฟล์
- agent ใช้เวลา 2 วันในการย้ายโค้ด Zig 535,496 บรรทัด โดยแต่ละคอมมิตต้องผ่านการรีวิวเชิงปฏิปักษ์ 2 รอบก่อนนำเข้า
การแก้ข้อผิดพลาดจากการคอมไพล์และการทดสอบ
- แม้การแปลงรอบแรกจะเสร็จแล้ว แต่โค้ดยังคอมไพล์ไม่ผ่าน จึงให้ Claude แก้ข้อผิดพลาดแยกตาม crate ซึ่งเป็นหน่วยคอมไพล์ระดับบนสุดของ Rust
- แม้ชื่อขั้นตอนจะระบุว่ามีข้อผิดพลาดจากการคอมไพล์ราว 1,600 รายการ แต่ในคำอ้างอิงระบุว่าระหว่างแก้ circular dependency มีข้อผิดพลาดโผล่ออกมาราว 16,000 รายการ
- กระบวนการแก้ข้อผิดพลาดถูกทำแบบขนานเช่นกัน
- รัน
cargo checkในแต่ละ crate - จัดกลุ่มเอาต์พุตตามไฟล์และบันทึกไฟล์ข้อผิดพลาด
- แก้ข้อผิดพลาดจากการคอมไพล์ทั้งหมดของ crate นั้น
- ผู้รีวิวเชิงปฏิปักษ์ 2 คนตรวจการเปลี่ยนแปลง
- agent ผู้รับผิดชอบการแก้ไข 1 คนสะท้อนผลรีวิวเข้าไป
- รัน
- agent แก้ข้อผิดพลาดจากการคอมไพล์ตั้งแต่เที่ยงคืนถึง 11:30 น. โดยไม่มีมนุษย์แทรกแซง
- หลังจากนั้นใช้เวลาอีกราว 2 วันเพื่อให้ชุดทดสอบขนาดใหญ่รันในเครื่องได้โดยไม่มีข้อผิดพลาดจากการคอมไพล์ และทุ่มเวลาเพิ่มอีกหลายวันเพื่อแก้เทสต์ที่ล้มเหลวให้ ผ่าน CI
- หลังจากผ่านทุกเทสต์และยืนยันการทำงานแล้วจึงรวมการเปลี่ยนแปลง โดยตั้งแต่เริ่มวางแผนจนเสร็จสิ้นใช้เวลารวม 11 วัน
- พอร์ตโค้ดประมาณ 550,000 บรรทัด
- คอมมิต 6,500 รายการ
- ใช้ agent 64 ตัว
ต้นทุนและผลลัพธ์เมื่อเทียบกับการทำด้วยมือ
- อ้างอิงตามราคา Fable API ต้นทุนรวมของการเขียนใหม่ทั้งหมดคือ 165,000 ดอลลาร์
- โทเคนอินพุตที่ไม่แคช 5.9 พันล้าน
- โทเคนเอาต์พุต 690 ล้าน
- การอ่านโทเคนอินพุตจากแคช 72 พันล้าน
- Anthropic ขาย API token แบบบวกมาร์จิน จึงทำให้ต้นทุนภายในจริงต่ำกว่านี้
- แม้ค่า API จะใกล้เคียงเงินเดือนฐานรายปีของวิศวกรซอฟต์แวร์องค์กรระดับกลางในสหรัฐฯ แต่ก็ประเมินว่าวิศวกรคนหนึ่งที่รับเงินเดือนระดับนั้นไม่อาจทำผลงานระดับเดียวกันได้ภายใน 11 วัน
- สิ่งนี้ยังสอดคล้องกับความเห็นของ Mitchell Hashimoto ที่ว่า Fable ทำได้ดีเป็นพิเศษกับ งานยากที่ต้องโฟกัสและมี reward function ชัดเจน
- หากทำด้วยมือ คาดว่าจะต้องใช้วิศวกร 3 คนที่รู้จักโค้ดเบสทั้งหมดอย่างสมบูรณ์เป็นเวลาประมาณ 1 ปี
- ในช่วงนั้นจะเดินหน้าปรับปรุงความเข้ากันได้กับ Node.js, แก้บั๊กและปัญหาความปลอดภัย, และทำฟีเจอร์ใหม่ได้ยาก
- ทางเลือกที่สมจริงกว่าคือไม่เขียนใหม่ แล้วเดินหน้าแก้บั๊กด้านหน่วยความจำในระบบเดิมต่อไป
เงื่อนไขสำหรับการนำไปใช้กับโปรเจ็กต์อื่น
- หาก AI สามารถย่นการเขียนใหม่หรือ migration ที่ปกติใช้ 1 ปีให้เหลือระดับ 1 สัปดาห์ได้ ก็จะทำให้โปรเจ็กต์ที่ก่อนหน้านี้พิจารณาได้ยากกลายเป็นสิ่งที่ทำได้จริง
- หากจะนำ workflow ของ Bun ไปใช้ซ้ำ ต้องมี 3 เงื่อนไข
- วิศวกร ที่รู้จักโค้ดเบสอย่างลึกมากและมีแรงขับในการลงมือทำ
- ชุดทดสอบ ที่แข็งแรงพอให้เชื่อถือว่าการผ่านเทสต์สะท้อนการทำงานจริง
- ความพร้อมจะลงทุนกับต้นทุนโทเคนจำนวนมาก แม้ยังไม่รู้ล่วงหน้าว่าจะสำเร็จหรือไม่
- งานซ้ำ ๆ อย่างการย้ายโค้ดเป็นสิ่งที่ LLM รับมือได้ค่อนข้างดี ดังนั้นหากมีเทสต์ที่ดีและมีวิศวกรที่สามารถจัดระเบียบปัญหาได้ โอกาสสำเร็จก็สูง
- ไม่ใช่ว่าทุกโปรเจ็กต์จะต้องใช้เงิน 165,000 ดอลลาร์
- ถ้าเป็นโปรเจ็กต์ที่ง่ายกว่า ต้นทุนก็อาจต่ำลง
- อาจใช้โมเดลที่แพงที่สุดกับการวางแผนระดับสูง และใช้โมเดลที่ถูกกว่าสำหรับการเขียนโค้ดและรีวิว
- แม้ migration ที่ขับเคลื่อนด้วย AI จะเร็วขึ้นเรื่อย ๆ แต่ความเร็วระดับนี้จะเกิดขึ้นได้เฉพาะใน โปรเจ็กต์ที่วิศวกรรมวางมาดี แบบ Bun เท่านั้น
ยังไม่มีความคิดเห็น