Back home

ความน่าเชื่อถือรันไทม์ของ Rust/Wasm จำเป็นต้องจัดการกับทั้งความตื่นตระหนกและยกเลิกการกู้คืน

เมื่ออินสแตนซ์ Wasm ที่ใช้ร่วมกันเริ่มรับสายเป็นเวลานาน ข้อขัดข้องจะเพิ่มขึ้นจากความล้มเหลวเพียงครั้งเดียวไปสู่การกู้คืนสถานะและปัญหาการแยกข้อบกพร่อง

Wasm ถือได้ว่าเป็นเลเยอร์การย้ายอย่างง่ายดายในตอนแรก: สามารถตั้งโปรแกรมโค้ดได้, เพจสามารถทำงานได้, ประสิทธิภาพก็โอเค และสิ่งต่าง ๆ ดูเหมือนจะใกล้เคียงกัน มันเริ่มยากขึ้นจริงๆ โดยปกติหลังจากผ่านการสาธิตไปแล้ว เมื่อโมดูล เช่น ตัวแก้ไข ตัวเรนเดอร์ และตัวแยกวิเคราะห์เอกสารย้ายจากการทดสอบหน้าเดียวไปเป็นรันไทม์แบบอาศัยระยะยาว โมเดลข้อบกพร่องจะเปลี่ยนไปทันที

ในเวลานี้ ความตื่นตระหนกและการยกเลิกไม่ใช่ข้อยกเว้นในเลเยอร์ภาษาอีกต่อไป สิ่งที่พวกเขาตัดสินใจคือ อินสแตนซ์นี้จะสามารถรับงานต่อๆ ไปได้หรือไม่ สถานะในหน่วยความจำมีการปนเปื้อนหรือไม่ เลเยอร์โฮสต์ควรละทิ้งอินสแตนซ์ทันทีหรือไม่ และจำเป็นต้องเติมพูลอินสแตนซ์หรือไม่ เมื่อทีมงานเคลื่อนที่ย้ายเคอร์เนลที่ทำงานในคอนเทนเนอร์ดั้งเดิมมาเป็นเวลานานไปยังเว็บ การเปลี่ยนแปลงระดับนี้เองที่ประเมินต่ำไปได้ง่ายที่สุด

หลังจากผ่านการสาธิตแล้ว Fault Model เพิ่งเริ่มต้นขึ้น

การขัดข้องในการโทรเพียงครั้งเดียวไม่ใช่เรื่องยากที่จะเข้าใจ การคลิกปุ่มจะทำให้เกิดการโทร Wasm หากล้มเหลว จะมีการรายงานข้อผิดพลาดสำหรับการดำเนินการ รีเฟรชหน้านี้แล้วลองอีกครั้ง ต้นทุนยังคงควบคุมได้

ปัญหาเกิดขึ้นหลังจากที่รันไทม์เริ่มนำอินสแตนซ์กลับมาใช้ใหม่ เมื่ออินสแตนซ์ Wasm เดียวกันเปิดเอกสารหลายชุดอย่างต่อเนื่อง รับเหตุการณ์อินพุตหลายรอบ และผ่านการเรียกบริดจ์ JS หลายครั้ง ขอบเขตของอิทธิพลของความตื่นตระหนกและการยกเลิกจะไม่หยุดอยู่ที่การดำเนินการปัจจุบันอีกต่อไป ความล้มเหลวที่ไม่สมบูรณ์อาจลากคำขอที่ตามมาลงมา

ความเสี่ยงดังกล่าวมักไม่เปิดเผยในวันแรก ในระยะแรก คุณมักจะเห็นเฉพาะรายงานข้อผิดพลาดที่กระจัดกระจาย เช่น การแสดงผลล้มเหลวเป็นครั้งคราว การส่งออกบางอย่างค้าง และเอกสารบางอย่างอยู่ในสถานะที่ไม่ถูกต้องหลังจากถูกปิดและเปิดใหม่ หากคุณตรวจสอบเพิ่มเติม เบาะแสจะค่อยๆ มาบรรจบกันเป็นปรากฏการณ์เดียวกัน แม้ว่าความล้มเหลวจะเกิดขึ้นในสายโซ่การโทร แต่ความเสียหายยังคงอยู่ในอินสแตนซ์ที่ใช้ร่วมกัน

ณ จุดนี้ จุดเน้นของการสนทนาไม่ได้อยู่ที่ “ว่าโค้ด Rust จะตื่นตระหนกหรือไม่” อีกต่อไป แต่เป็น “ว่ารันไทม์นี้มีคุณสมบัติเหมาะสมหรือไม่ที่จะให้บริการการโทรครั้งถัดไปหลังจากการตื่นตระหนก”

จับความตื่นตระหนกได้ ยกเลิกได้เพียงเปลี่ยนอินสแตนซ์เท่านั้น

สิ่งที่สำคัญที่สุดที่ต้องแยกออกจากกันใน Rust/Wasm คือความหมายของความล้มเหลวสองประการของการตื่นตระหนกและการยกเลิก

ความตื่นตระหนกยังมีโอกาสที่จะผ่อนคลายกลับไปตามขอบเขตที่กำหนดไว้ ตราบใดที่ Binding Layer และ Host Layer ตกลงเกี่ยวกับวิธีการกู้คืนล่วงหน้า การเรียกปัจจุบันอาจล้มเหลว และสถานะอื่นๆ ในอินสแตนซ์ก็สามารถรักษาไว้ได้ การทำแท้งไม่ใช่วิธีที่จะไปเลย หมายความว่าการดำเนินการปัจจุบันถึงสถานะที่ไม่สามารถกู้คืนได้ หากคุณยังคงใช้อินสแตนซ์เดิมเพื่อรับคำขอ คุณจะต้องเดิมพันว่าหน่วยความจำและทรัพยากรจะไม่ได้รับความเสียหายครึ่งทาง

เมื่อทั้งสองผสมกันระหว่างรันไทม์ ปัญหาจะเกิดขึ้นอย่างแน่นอนในการประมวลผลครั้งต่อไป:

  • Swallow ยกเลิกตามข้อยกเว้นปกติ และกลุ่มอินสแตนซ์จะยังคงใช้ออบเจ็กต์ที่สูญเสียความน่าเชื่อถือต่อไป
  • ปฏิบัติต่อความตื่นตระหนกทั้งหมดราวกับว่าอินสแตนซ์จะต้องถูกทำลาย และปริมาณงานจะลดลงโดยไม่จำเป็น
  • โฮสต์ JS รู้เพียงว่า “การโทรล้มเหลว” แต่ไม่รู้ว่าจะลองใหม่ สูญเสียอินสแตนซ์ หรือตัดเซสชันปัจจุบันออก

นี่เป็นสิ่งที่สมจริงที่สุดเกี่ยวกับความน่าเชื่อถือรันไทม์ของ Wasm: ต้องกำหนดซีแมนทิกส์การกู้คืนก่อนจึงจะสามารถดำเนินการแยกและกำหนดเวลาในภายหลังได้

หากเลเยอร์การรวมไม่มีซีแมนทิกส์การกู้คืน เลเยอร์โฮสต์จะเข้าสู่สถานะที่ไม่ดีและยอมรับงานต่อไป

สถานที่ที่อันตรายที่สุดสำหรับปัญหาประเภทนี้ไม่ได้อยู่ในรหัสธุรกิจ แต่อยู่ในชั้นที่มีผลผูกพันซึ่งดูเหมือนว่าจะ “ได้รับการดูแลแล้ว” เลเยอร์โฮสต์มักจะเห็นเฉพาะออบเจ็กต์ข้อผิดพลาดที่ส่งออกมา และบันทึกว่าเป็นความล้มเหลวในการโทรตามปกติ บันทึกอยู่ที่นั่นและเพจไม่ขัดข้องทันที แต่ระบบอาจทิ้งสถานะที่ไม่ถูกต้องไว้ภายในอินสแตนซ์

สิ่งที่ต้องแก้ไขจริงๆ ไม่ใช่แค่ลอง/จับ แต่รวมถึงการดำเนินการจัดการหลังจากเกิดความล้มเหลว ลอจิกที่คล้ายกับต่อไปนี้เพิ่งเริ่มเข้าสู่การออกแบบความน่าเชื่อถือ:

async function runWithRecovery(instance, input) {
  try {
    return await instance.exports.handle(input)
  } catch (error) {
    if (isAbort(error)) {
      pool.replace(instance.id)
    }
    throw error
  }
}

จุดเน้นของโค้ดนี้ไม่ได้อยู่ที่ไวยากรณ์ แต่เป็นการตัดสินง่ายๆ ว่าความล้มเหลวในปัจจุบันได้ทำเครื่องหมายอินสแตนซ์นี้เป็นวัตถุที่ไม่น่าเชื่อถือหรือไม่ หากคำตอบคือใช่ การดำเนินการกู้คืนไม่ควรหยุดอยู่ที่การทิ้งข้อผิดพลาด แต่ควรดำเนินต่อไปจนถึงการกำจัดอินสแตนซ์ การสร้างทรัพยากรขึ้นใหม่ และการร้องขอการตัดโฟลว์

ตราบใดที่เลเยอร์นี้ไม่ได้กำหนดไว้อย่างชัดเจน ระบบจะดูเหมือนว่าจะจัดการกับข้อผิดพลาด แต่จริงๆ แล้วสิ่งที่กำลังทำอยู่คือการนำรันไทม์ที่อาจเสียหายกลับเข้าไปในเส้นทางที่ใช้งานจริง

อินสแตนซ์ที่ใช้ร่วมกันจะขยายปัญหาการกู้คืนให้กลายเป็นปัญหากลยุทธ์การรวมกลุ่ม

หลังจากที่ Wasm ใส่ลงในสินค้าจริงแล้ว แทบจะไม่มี “อินสแตนซ์เดียวจนกว่าเพจจะถูกปิด” โดยทั่วไปคือพูลอินสแตนซ์ พูลผู้ปฏิบัติงาน หรือเอกสารเบื้องหน้าและงานเบื้องหลังที่แชร์ชุดทรัพยากรรันไทม์ ในขั้นตอนนี้ ค่าใช้จ่ายในการฟื้นตัวจากความตื่นตระหนกและการยกเลิกจะเขียนกลยุทธ์การรวมกลุ่มโดยตรง

หากการเริ่มต้นอินสแตนซ์มีราคาแพง ระบบมักจะนำอินสแตนซ์นั้นกลับมาใช้ใหม่ให้ได้มากที่สุด แต่เมื่อมีการนำกลับมาใช้ใหม่แล้ว จะต้องอัปเกรดการแยกข้อผิดพลาดพร้อมกัน:

  • สถานะใดที่สามารถหยุดการทำงานได้ในการโทรเพียงครั้งเดียว และจะถูกละทิ้งพร้อมกับการโทรหลังจากเกิดความล้มเหลว
  • แคชใดที่ได้รับอนุญาตให้เก็บรักษาไว้ระหว่างการโทร และแคชใดจะต้องใช้ไม่ได้อย่างสมบูรณ์เมื่อพบการยกเลิก
  • หลังจากแทนที่อินสแตนซ์แล้ว งานที่อยู่ในคิวจะถูกย้ายอย่างไร การลองใหม่จะทำให้เกิดผลข้างเคียงสองครั้งหรือไม่?

นี่ไม่ใช่คำตอบที่เลเยอร์ภาษาจะส่งโดยอัตโนมัติ เป็นการออกแบบรันไทม์

ด้วยเหตุนี้ หากการถกเถียงเรื่องความน่าเชื่อถือของ Rust/Wasm หยุดแค่เพียง “สามารถจับความตื่นตระหนกได้หรือไม่” ก็เป็นเรื่องง่ายที่จะประเมินปัญหาต่ำไป สิ่งที่ทำให้ช่องว่างต้นทุนการบำรุงรักษากว้างขึ้นจริงๆ คือกลุ่มอินสแตนซ์สามารถรักษาขอบเขตความน่าเชื่อถือที่ชัดเจนหลังจากเกิดความล้มเหลวได้หรือไม่

ขอบเขตที่ใช้บังคับมีความเกี่ยวข้องอย่างมากกับวงจรชีวิต

การออกแบบการบูรณะชุดนี้ไม่จำเป็นสำหรับทุกโครงการ Wasm

หากโมดูลเป็นเพียงเครื่องมือออฟไลน์ที่ทำเพียงครั้งเดียว หรืออินสแตนซ์ทั้งหมดถูกนำกลับมาใช้ใหม่เมื่อเพจถูกทำลาย ความแตกต่างระหว่างความตื่นตระหนกและการยกเลิกจะยังคงอยู่ แต่ประโยชน์ในการกู้คืนจะมีน้อยกว่ามาก บ่อยครั้งเพียงพอที่จะรีเฟรชเพจโดยตรงและรันงานใหม่โดยตรง

เมื่อระบบมีคุณสมบัติดังต่อไปนี้ ความหมายของการกู้คืนจะเปลี่ยนจาก “รายการเพิ่มประสิทธิภาพ” เป็น “รายการโครงสร้างพื้นฐาน” อย่างรวดเร็ว:

  • อินสแตนซ์จะคงอยู่เป็นเวลานานและไม่ถูกทำลายพร้อมกับวงจรชีวิตหน้าเดียว
  • อินสแตนซ์เดียวกันจะรับสายหลายรอบอย่างต่อเนื่อง
  • เลเยอร์โฮสติ้งจำเป็นต้องใช้การรวมกลุ่มเพื่อแลกกับเวลาเริ่มต้นและปริมาณงาน
  • ปกป้องสถานะเซสชัน สถานะแคช และงานที่อยู่ในคิวหลังจากเกิดความล้มเหลว

เมื่อทีมเคลื่อนที่ย้ายความสามารถดั้งเดิมไปยังเว็บ ขอบเขตนี้มีแนวโน้มที่จะเกิดขึ้นมากที่สุด ความสัมพันธ์แบบแยกเดี่ยวที่แต่เดิมสร้างไว้ตามค่าเริ่มต้นในกระบวนการแอป มักจะต้องกรอกอีกครั้งหลังจากถึงขอบเขตโฮสต์ JS/Wasm

Wasm ช่วยให้โค้ดเนทีฟเข้าสู่เบราว์เซอร์ได้ง่ายขึ้น แต่ไม่ได้นำซีแมนทิกส์การกู้คืนรันไทม์มาด้วย ทันทีที่ระบบเริ่มแชร์อินสแตนซ์ สถานะใช้ซ้ำ และรับสายระยะยาว ความตื่นตระหนกและการยกเลิกจะต้องถือเป็นเหตุการณ์รันไทม์ที่แตกต่างกันสองเหตุการณ์ แบบแรกสนใจว่าจะวางสายปัจจุบันอย่างไร และแบบหลังสนใจว่าอินสแตนซ์นี้สามารถอยู่ในพูลต่อไปได้หรือไม่ หากไม่ทำการตัดสินก่อน ยิ่งการปลูกถ่ายรหัสประสบความสำเร็จมากเท่าใด การจัดการกับความล้มเหลวที่ตามมาก็จะยิ่งยากขึ้นเท่านั้น