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

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

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

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

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

การใช้แผนสำรองในคอมพิวเตอร์เครื่องเดียว

พิจารณาโค้ดย่อยในภาษา C ต่อไปนี้ที่แสดงให้เห็นรูปแบบร่วมกันสำหรับการจัดการความล้มเหลวในการจัดสรรหน่วยความจำในหลายๆ แอปพลิเคชัน โค้ดนี้จัดสรรหน่วยความจำโดยใช้ฟังก์ชัน malloc() แล้วคัดลอกบัฟเฟอร์อิมเมจลงในหน่วยความจำนั้น ในขณะที่ดำเนินการเปลี่ยนแปลงบางอย่าง:
pixel_ranges = malloc(image_size); // allocates memory
if (pixel_ranges == NULL) {
  // On error, malloc returns NULL
  exit(1);
}
for (i = 0; i < image_size; i++) {
  pixel_ranges[i] = xform(original_image[i]);
}

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

pixel_ranges = malloc(image_size);
if (pixel_ranges == NULL) {
  pixel_ranges = malloc2(image_size);
}

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

อีกปัญหาหนึ่งก็คือการใช้แผนสำรองก็อาจล้มเหลวได้เอง โค้ดที่ใช้แผนสำรองก่อนหน้าไม่ได้จัดการความล้มเหลวของ malloc2 ดังนั้น โปรแกรมไม่ได้ให้ประโยชน์มากเท่าที่คุณอาจคิด กลยุทธ์การใช้แผนสำรองทำให้การล้มเหลวทั้งหมดมีแนวโน้มที่จะเกิดขึ้นน้อยลง แต่ก็ไม่ถึงกับเป็นไปไม่ได้ ที่ Amazon เราพบว่าการใช้ทรัพยากรด้านวิศวกรรมในการทำให้โค้ดหลัก (ไม่ใช่การใช้แผนสำรอง) มีความน่าเชื่อถือมากขึ้นมักจะมีโอกาสสำเร็จมากกว่าการลงทุนในกลยุทธ์การใช้แผนสำรองที่ใช้ไม่บ่อยครั้ง

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

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

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

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

สำหรับแอปพลิเคชันที่ทำงานในคอมพิวเตอร์เครื่องเดียวที่มีความสำคัญสูงที่ต้องทำงานในกรณีที่การจัดสรรหน่วยความจำล้มเหลว วิธีหนึ่งในการแก้ปัญหาก็คือ การจัดสรรหน่วยความจำแบบฮีปเมื่อเริ่มต้นระบบ และไม่ต้องพึ่ง malloc อีกต่อไป แม้จะอยู่ภายใต้เงื่อนไขข้อผิดพลาดก็ตาม Amazon ได้นำกลยุทธ์นี้มาใช้หลายครั้งแล้ว ตัวอย่างเช่น ในเดมอนติดตามที่ทำงานบนเซิร์ฟเวอร์การทำงานจริง และเดมอนของ Amazon Elastic Compute Cloud (Amazon EC2) ที่ติดตามการส่งชุดข้อมูลของ CPU ของลูกค้า

การใช้แผนสำรองแบบกระจาย

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

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

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

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

กลยุทธ์การใช้แผนสำรองแบบกระจายมักจะไม่คุ้มกับความเสี่ยง เช่นเดียวกับ malloc2 กลยุทธ์การใช้แผนสำรองมักจะมีข้อเสียบางประการ มิฉะนั้น เราจะใช้มันตลอดเวลา การมีสิ่งที่ผิดปกติก็แย่อยู่แล้ว แล้วเหตุใดจึงใช้แผนสำรองที่แย่เข้าไปอีก

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

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

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

if (cache_healthy) {
  shipping_speed = get_speed_via_cache(sku);
} else {
  shipping_speed = get_speed_from_database(sku);
}

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

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

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

Amazon หลีกเลี่ยงการใช้แผนสำรองอย่างไร

เมื่อพิจารณาจุดอ่อนที่เราได้พบในการใช้แผนสำรองแบบกระจายแล้ว ตอนนี้ เราแทบจะเลือกทางเลือกอื่นที่ไม่ใช่การใช้แผนสำรองเสมอ ซึ่งแสดงอยู่ที่นี่

ปรับปรุงความน่าเชื่อถือของกรณีการใช้แผนสำรอง

ตามที่กล่าวถึงก่อนหน้านี้ กลยุทธ์การใช้แผนสำรองเพียงแค่ลดแนวโน้มของความล้มเหลวทั้งหมด บริการสามารถใช้งานได้มากขึ้นมาก หากทำให้โค้ดหลัก (ไม่ใช่การใช้แผนสำรอง) มีประสิทธิภาพมากขึ้น ตัวอย่างเช่น แทนที่จะนำลอจิกการใช้แผนสำรองมาใช้งานระหว่างที่เก็บข้อมูลสองแห่งที่แตกต่างกัน ทีมสามารถลงทุนในการใช้ฐานข้อมูลที่มีศักยภาพความพร้อมใช้งานสูงขึ้น เช่น Amazon DynamoDB กลยุทธ์นี้ถูกนำไปใช้เป็นผลสำเร็จบ่อยๆ ใน Amazon ตัวอย่างเช่น การบรรยายครั้งนี้อธิบายการใช้ DynamoDB เพื่อสนับสนุน amazon.com ในวัน Prime Day 2017

ปล่อยให้ผู้เรียกใช้จัดการข้อผิดพลาด

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

พุชข้อมูลในเชิงรุก

อีกวิธีหนึ่งที่เราใช้ในการหลีกเลี่ยงความจำเป็นในการใช้งานแทนคือ ลดจำนวนชิ้นส่วนที่ทำงานในขณะตอบสนองต่อคำขอ ตัวอย่างเช่น หากบริการจำเป็นต้องใช้ข้อมูลในการตอบสนองคำขอ และข้อมูลนั้นก็มีอยู่แล้วในเครื่อง (ไม่จำเป็นต้องดึงข้อมูล) ก็ไม่จำเป็นต้องใช้กลยุทธ์การย้ายเมื่อเกิดข้อผิดพลาด ตัวอย่างที่ประสบความสำเร็จของกรณีนี้อยู่ที่การนำบทบาทของ AWS Identity and Access Management (IAM) มาใช้งานสำหรับ Amazon EC2 บริการ IAM จำเป็นต้องส่งค่าข้อมูลประจำตัวที่ลงนามและหมุนเวียนให้แก่โค้ดที่ทำงานบน EC2 instance เพื่อหลีกเลี่ยงไม่ให้ต้องกลับไปใช้งานแทน ข้อมูลประจำตัวจะถูกส่งในเชิงรุกให้แก่ทุกๆ อินสแตนซ์ และยังคงใช้งานได้เป็นเวลาหลายชั่วโมง ซึ่งหมายความว่าคำขอที่เกี่ยวกับบทบาท IAM ยังคงทำงานต่อไปแม้ในกรณีหยุดชะงักในกลไกการพุชที่ไม่ค่อยได้เกิดขึ้น 

เปลี่ยนการใช้แผนสำรองเป็นการย้ายเมื่อเกิดข้อผิดพลาด

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

ตรวจสอบให้แน่ใจว่าการลองใหม่และการหมดเวลาไม่กลายเป็นการแผนสำรองแทน

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

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

สรุป

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

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

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


เกี่ยวกับผู้เขียน

Jacob Gabrielson เป็นวิศวกรหลักอาวุโสที่ Amazon Web Services โดยรับผิดชอบด้านแพลตฟอร์มไมโครเซอร์วิสภายในเป็นหลักที่ Amazon มาเป็นเวลา 17 ปี ในช่วงเวลา 8 ปีที่ผ่านมา เขาได้ทำงานบน EC2 และ ECS รวมทั้งระบบการติดตั้งซอฟต์แวร์เพื่อใช้งานจริง, บริการของ Control Plane, Spot Market, Lightsail และล่าสุดคือ ซอฟต์แวร์คอนเทนเนอร์ Jacob มีความสนใจในด้านการเขียนโปรแกรมระบบ ภาษาในการเขียนโปรแกรม และการประมวลผลแบบกระจาย เขาไม่ชอบลักษณะการทำงานของระบบที่มีสองรูปแบบการทำงาน โดยอย่างยิ่งภายใต้เงื่อนไขความล้มเหลว เขาได้รับวุฒิปริญญาตรีสาขาวิทยาการคอมพิวเตอร์จากมหาวิทยาลัยวอชิงตันในเมืองซีแอตเทิล

การหมดเวลา การลองใหม่ และการถอยกลับที่มีความผันแปร ความท้าทายและกลยุทธ์ในการแคช