Flawless - เอนจินการรันที่ทนทานสำหรับ Rust
(flawless.dev)- Flawless คือ เอนจินการรันการคำนวณที่ทนทาน ซึ่งช่วยให้โค้ดรันจนจบได้แม้เกิดความขัดข้อง
- เวิร์กโฟลว์เขียนเป็น ฟังก์ชัน Rust ปกติ แต่จะไม่คอมไพล์เป็นโค้ดเนทีฟ แต่คอมไพล์เป็น WebAssembly เพื่อรันในสภาพแวดล้อมที่กำหนดผลลัพธ์ได้แน่นอน
- ผลข้างเคียงแบบไม่กำหนดแน่นอน จะเกิดขึ้นเฉพาะจุดที่โค้ดติดต่อกับโลกภายนอก เช่น การส่งคำขอ HTTP หรือการสร้างเลขสุ่ม และ Flawless จะบันทึกสิ่งเหล่านี้ไว้ในล็อก
- หากการรันถูกขัดจังหวะ จะใช้ล็อกที่บันทึกไว้เพื่อไล่รันกลับไปให้ถึงสถานะเดิม และจะไม่ทำซ้ำผลข้างเคียงที่เคยทำไปแล้ว
- นักพัฒนาสามารถแสดงสถานะถาวรผ่านโค้ดและตัวแปรภายในได้ โดยไม่ต้องจำลองสถานะทั้งหมดลงในฐานข้อมูลเอง ทำให้หลังรีสตาร์ตเซิร์ฟเวอร์ก็สามารถรันต่อจากจุดที่หยุดไว้ได้
โมเดลการรันที่แสดงสถานะถาวรด้วยโค้ด
- Flawless คือเอนจิน การคำนวณที่ทนทาน ซึ่งทำให้โค้ดรันไปจนเสร็จแม้เกิดความขัดข้องของฮาร์ดแวร์หรือซอฟต์แวร์
- ประสบการณ์ผู้ใช้ที่ซับซ้อนต้องการ UI และสถานะที่ซับซ้อน แต่การจำลองสถานะทั้งหมดไว้ในฐานข้อมูลโดยตรงเป็นเรื่องยาก
- แอปพลิเคชันสมัยใหม่จำเป็นต้องมี การจัดเก็บแบบถาวร เพื่อไม่ให้ผู้ใช้สูญเสียความคืบหน้าแม้เผลอรีเฟรชหน้าเว็บ
- Flawless มีเป้าหมายให้สามารถจำลองสถานะถาวรด้วยโค้ดและตัวแปรภายใน เพื่อช่วยแสดงพฤติกรรมที่ซับซ้อนของแอปพลิเคชันได้ง่ายขึ้น
การรันบน WebAssembly และการกู้คืนจากความขัดข้อง
- เวิร์กโฟลว์เขียนเป็น ฟังก์ชัน Rust ปกติ และสามารถมีตรรกะใด ๆ ก็ได้
- ฟังก์ชันจะถูกคอมไพล์เป็น WebAssembly แทนโค้ดเนทีฟ และรันในสภาพแวดล้อมที่กำหนดผลลัพธ์ได้แน่นอนอย่างสมบูรณ์
- ความไม่กำหนดแน่นอนจะถูกนำเข้ามาเฉพาะเมื่อโต้ตอบกับโลกภายนอก เช่น การส่งคำขอ HTTP หรือการสร้างเลขสุ่ม
- Flawless จะบันทึก ล็อกผลข้างเคียงแบบไม่กำหนดแน่นอน เหล่านี้ไว้เพื่อใช้ในการกู้คืน
- หากการรันเวิร์กโฟลว์ถูกขัดจังหวะ จะรันใหม่เพื่อไล่ตามไปจนถึงสถานะเดียวกัน
- ผลข้างเคียงที่ทำไปแล้วจะไม่ถูกทำซ้ำ
- ลดปริมาณข้อมูลที่ต้องจัดเก็บให้เหลือน้อยที่สุด และส่วนที่เหลือจะคำนวณใหม่ตามความจำเป็นเมื่อเกิดความขัดข้อง
- โมเดลการรันนี้ช่วยให้สังเกตพฤติกรรมของทั้งระบบได้ดียิ่งขึ้น
- สามารถวิเคราะห์เส้นทางการรันที่แม่นยำของเวิร์กโฟลว์ที่เสร็จแล้วหรือที่กำลังรันอยู่ได้
- สภาพแวดล้อมการรันแบบกำหนดผลลัพธ์แน่นอนช่วยให้จัดการบั๊กที่ทำซ้ำได้ยากได้ง่ายขึ้น
- นักพัฒนาสามารถลดภาระเรื่องการจัดเก็บสถานะ และมุ่งเน้นการเขียน ตรรกะทางธุรกิจ ได้มากขึ้น
- แม้ต้องรีสตาร์ตเซิร์ฟเวอร์เพื่อบำรุงรักษา เมื่อเริ่มเอนจิน Flawless ใหม่ เวิร์กโฟลว์ก็จะรันต่อจากจุดที่หยุดไว้
- ณ วันที่ 9 ธันวาคม 2024 มี Flawless Beta 3 ให้ใช้งานแล้ว
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
สงสัยว่าจะจัดการ การจัดการเวอร์ชันของเวิร์กโฟลว์ อย่างไร มองว่านี่เป็นปัญหาที่ยากที่สุดในระบบอย่าง Temporal/Cadence
การอัปเกรดจะสำเร็จก็ต่อเมื่อโค้ดใหม่สามารถ replay บันทึก side effect เดิมได้อย่างถูกต้องเท่านั้น คือให้โค้ดใหม่ไล่ตามจากประวัติเดิม พอไล่ทันแล้วก็รันต่อไปตามนั้น
ถ้าโค้ดใหม่เบี่ยงเบนจากประวัติเดิม การอัปเกรดจะล้มเหลวและย้อนกลับไปใช้โค้ดก่อนหน้า ในกรณีนี้ต้องให้คนเข้ามาตรวจสอบว่าเกิดอะไรผิดพลาด
มีแนวทางอื่นด้วย แต่ผมมองว่าวิธีนี้เรียบง่ายที่สุดสำหรับการทำความเข้าใจและใช้งานจริง ระหว่างพัฒนาก็สามารถใช้ log เดิมมาทดสอบได้ว่าโค้ดแยกทางหรือไม่
และสงสัยด้วยว่าอยากคงหลายเวอร์ชันไว้พร้อมกัน หรือว่าต้องการวิธี migrate งานที่กำลังดำเนินอยู่ไปยัง นิยามเวิร์กโฟลว์ ล่าสุด
รู้สึกว่าสายงานของเรากำลังค่อย ๆ เข้าใกล้สถาปัตยกรรมหรือการแพทย์มากขึ้น เทคโนโลยีแบบนี้ช่วยให้เราพ้นจากขั้นลองเล่นไปสู่ วัฒนธรรมวิศวกรรม ที่จริงจังได้
ถามสั้น ๆ คือ ในระบบแบบนี้จะป้องกันไม่ให้ ผลของการโจมตี DoS ถูกทำให้คงอยู่ถาวร ได้อย่างไร
สถาปัตยกรรมใกล้เคียงกับการจัดการความเห็นเชิงอัตวิสัยภายใต้ข้อจำกัดด้านกฎระเบียบ ส่วนการแพทย์คือความรู้เชิงประจักษ์ที่ตรวจสอบด้วยการทดลอง ทั้งสองอย่างดูไม่ค่อยคล้ายวิศวกรรม
สถาปัตยกรรมใกล้เคียงกับรูปแบบหนึ่งของศิลปะ และภาควิชาสถาปัตยกรรมในมหาวิทยาลัยก็มักอยู่ในคณะศิลปะ
การแพทย์เป็นวิทยาศาสตร์ประยุกต์เหมือนวิศวกรรม แต่ก็ไม่ใช่สาขาวิศวกรรมโดยตัวมันเอง
สงสัยว่า ความเป็น deterministic นี้ขยายไปถึงการคำนวณแบบ floating-point ด้วยหรือไม่
ในเกมมัลติเพลเยอร์ สถานะฝั่งไคลเอนต์จะค่อย ๆ drift จากการคำนวณ floating-point จนต้องซิงก์กับสถานะเซิร์ฟเวอร์ใหม่เป็นระยะ ๆ และในอดีตนี่เป็นจุดที่ค่อนข้างปวดหัว
ดู https://webassembly.org/docs/faq/#why-is-there-no-fast-math-... และ https://github.com/WebAssembly/design/blob/main/Nondetermini...
https://asawicki.info/news_1741_myths_about_floating-point_n...
เมื่อก่อนผลลัพธ์จาก CPU ก็อาจต่างกันได้ตาม execution path ดูทวีตในบทความ
f64จะเป็นตัวเลือกได้ไหม หรือว่าไม่สมจริงในแง่ความซับซ้อนและขนาดสงสัยว่าสถานะของ side effect ถูกเก็บไว้ที่ไหน เช่น ถ้ามี AWS Lambda ที่อยากทำให้เป็น idempotent, Lambda ไม่มี local storage ที่คงอยู่ระหว่างการรัน
ถ้าไม่ได้ mount อะไรอย่าง EBS volume สถานะก็จะไม่คงอยู่ ดังนั้นมองได้ไหมว่าสถานะจะเก็บใน DB
ชอบ แอนิเมชัน ที่แสดงหลักการสำคัญและวิธีทำงาน ทำออกมาดีมากจริง ๆ
โค้ดอาจไม่สวยมาก แต่ implementation ค่อนข้างตรงไปตรงมา ถ้าอยากดู อยู่ที่นี่: https://flawless.dev/js/how-does-it-work-animation.js
ดูน่าสนใจ แต่สงสัยว่าวิธีทำเครื่องหมายว่าฟังก์ชัน มี side effect จะทำให้ใช้งานโดยไม่พลาดได้ง่ายแค่ไหน
ในตัวอย่าง สมมติว่าการสร้างเลขสุ่มเป็น side effect เพราะมาจากตัวสร้างเลขสุ่มที่ flawless ให้มา แล้วถ้าเป็นฟังก์ชัน Rust ปกติจะทำได้ไหม
คิดว่าน่าจะมี test harness ให้ developer ตรวจสอบเวิร์กโฟลว์ด้วย
WebAssembly บังคับให้ประกาศ host call ภายในโมดูลอย่างชัดเจน ถ้าพยายามใช้ host call อื่นที่ flawless ไม่ได้ให้ไว้ ก็จะ instantiate โมดูลไม่ได้
ใน ecosystem ของ WebAssembly ก็มีงานมาตรฐานหลายอย่างกำลังดำเนินอยู่ เช่น ถ้าใช้ crate
randของ Rust แล้วคอมไพล์เป็น WebAssembly การสร้างเลขสุ่มจะใช้ฟังก์ชัน host ของ WASIระหว่างรอให้
wasi,wasi-httpฯลฯ เป็นมาตรฐาน ตอนนี้จึง expose interface ของตัวเองไปก่อนแน่นอนว่ามีข้อเสียใหญ่ด้วย ไม่ใช่โค้ด Rust ทั้งหมดจะคอมไพล์เป็น WebAssembly ได้ แต่ผมมองว่า แนวทางปฏิเสธเป็นค่าเริ่มต้น ที่รับประกันว่า side effect ที่ไม่ได้ตั้งใจจะไม่เกิดขึ้นเลยนั้นดีกว่า
flawlessแล้ว น่าจะเป็นการให้ใช้สิ่งคล้ายstd::flawlessแทนการเปิดให้เข้าถึง ecosystem Rust ทั้งหมดharness น่าจะแก้ปัญหาส่วนใหญ่ได้ แต่ก็สงสัยว่าจะ map ฟีเจอร์ได้มากแค่ไหน
เท่าที่เห็นตอนนี้ดูใกล้เคียงกับ scripting runtime ที่ใช้ Rust มากกว่า
ดูเหมือนเป็น ทางเลือกแทน Temporal สำหรับ Rust ที่ใช้ WASM เป็นรันไทม์ ชอบนะ
ผมเป็นผู้ก่อตั้ง windmill.dev และเราก็ทำเอนจินแบบ durable ที่เขียนด้วย Rust เหมือนกัน เพียงแต่มันสง่างามน้อยกว่ามาก เราแบ่งเวิร์กโฟลว์ออกเป็นขั้นตอนที่ชัดเจนใน Python/TypeScript/Go/Bash และถ้าต้องการกลับมาทำต่อจากขั้นตอนที่ยังไม่เสร็จ ก็เริ่มใหม่จากขั้นตอนสุดท้าย พร้อมบันทึกผลลัพธ์ของแต่ละขั้นอย่างถาวรลงใน
jsonbของ Postgres DBกรณีใช้งานต่างกันชัดเจน และ flawless ดูเบามากตามที่เว็บไซต์บอก จนน่าจะใช้โมเดลสถานะของโฟลว์ UI และสเกลได้ถึงหลักล้านบนเซิร์ฟเวอร์ขนาดเล็ก
ยอดเยี่ยมมาก หวังว่าสักวัน Rust จะขับเคลื่อน ระบบแบบกระจาย ทั้งหมด
“โปรแกรม concurrent ที่ซับซ้อนพอซึ่งเขียนด้วยภาษาอื่น จะมี การอิมพลีเมนต์ Erlang ครึ่งใบที่บั๊กเยอะและช้า ตามสเปกไม่เป็นทางการแบบแก้ขัดอยู่ข้างใน” — กฎข้อแรกของการเขียนโปรแกรมของ Virding
สิ่งนี้ค่อนข้างต่างจาก Erlang มาก Erlang ถูกสร้างขึ้นหลักๆ เพื่อพัฒนาซอฟต์แวร์ได้แม้ในช่วงที่มีแค่อุปกรณ์ต้นแบบที่มีปัญหาฮาร์ดแวร์มากมาย
สำหรับผม สองแนวทางนี้ดูเหมือนอยู่คนละขั้ว Flawless อาจติดอยู่ในลูปเพื่อพยายามทำเวิร์กโฟลว์ที่แครชกลางทางให้จบ แต่ Erlang พร้อมจะทิ้งทราฟฟิก 50% ที่ไปเหยียบบั๊กฮาร์ดแวร์อย่างเต็มใจ
Erlang แก้ปัญหาโดยแทบกำจัดสถานะถาวรออกไป สถานะแทบทั้งหมดอยู่ในที่อย่างคิวข้อความหรือ DB ภายนอก
Flawless ดูเหมือนแก้ปัญหาด้วยเทคนิคที่คล้ายกับ filesystem journaling แต่ก็ไม่เหมือนกันทั้งหมด คือเป็นการบันทึกไว้ตอนทำ side effect
filesystem journaling มีไว้เพื่อรันซ้ำหลังแครช แต่กรณีนี้มีไว้เพื่อทำให้ไม่จำเป็นต้องรันซ้ำ
ยังไม่ชัดเจนว่าจะเหมาะกับโดเมนไหนแค่ไหน แต่ดูเหมือนจะไม่ได้ทับซ้อนกับโดเมนที่ Erlang เหมาะสมอย่างสมบูรณ์
เจ๋งมาก ใน Ambient ซึ่งเป็นรันไทม์เกมแบบ WASM ก็มีปัญหาคล้ายกัน มีโปรเซสที่แข่งขันกันและอาจต้อง retry การโต้ตอบกัน แนวทางที่แสดงตรงนี้เลยน่าสนใจ
แต่สงสัยว่ามีความสัมพันธ์กับ Lunatic อย่างไร Lunatic ยังพัฒนาต่ออยู่ไหม อันนี้เป็นโปรเจกต์ข้างๆ หรือว่าแยกกันโดยสิ้นเชิง?
https://lunatic.solutions/
https://kolobara.com
“ลองจินตนาการว่า เมื่อเริ่มการคำนวณใดๆ แล้ว ระบบรับประกันว่าจะรันจนเสร็จ และงานทุกอย่างจะถูกทำอย่างถูกต้องหนึ่งครั้งพอดี”
สงสัยว่ารับประกันได้อย่างไร การส่งแบบ exactly-once ในระบบแบบกระจายเป็นไปไม่ได้ไม่ใช่หรือ?
ถ้าระบบยังเดินหน้าต่อได้ ข้อความจะถูกส่ง exactly once
ถ้าต้องการหลักฐานว่ามีอยู่จริง NFSv3 ก็ทำให้สิ่งนี้ทำงานได้ตั้งแต่ทศวรรษ 1980 แล้ว ไม่รู้ว่าเป็นรายแรกหรือเปล่า