เรื่องดีๆ และหายนะจากแคช

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

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

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

เราจะใช้การแคชเมื่อใด

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

แคชในเครื่อง

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

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

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

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

แคชจากภายนอก

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

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

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

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

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

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

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

เปรียบเทียบแคชแบบอินไลน์และแคชที่อยู่ข้างเคียง

การตัดสินใจอีกประการหนึ่งที่เราต้องทำเมื่อประเมินแนวทางการแคชต่างๆ ก็คือตัวเลือกระหว่างแคชแบบอินไลน์และแคชที่อยู่ข้างเคียง แคชแบบอินไลน์หรือแคชแบบอ่านทั้งหมด/เขียนทั้งหมดจะฝังการจัดการแคชลงใน API การเข้าถึงข้อมูลหลัก ซึ่งทำให้ API ดังกล่าวมีรายละเอียดข้อมูลการปรับใช้การจัดการแคช ตัวอย่างจะรวมถึงการปรับใช้งานเฉพาะแอปพลิเคชันเป็นหลักอย่าง Amazon DynamoDB Accelerator (DAX) และการปรับใช้งานที่อิงตามมาตรฐานอย่าง การแคช HTTP (ไม่ว่าจะด้วยไคลเอ็นต์การแคชในเครื่องหรือเซิร์ฟเวอร์แคชภายนอกอย่าง Nginx หรือ Varnish) ในทางตรงกันข้าม แคชที่อยู่ข้างเคียงคือพื้นที่จัดเก็บอ็อบเจ็กต์ทั่วไปเช่น ที่จัดเก็บที่ Amazon ElastiCache ให้บริการ (Memcached และ Redis) หรือไลบรารีอย่าง Ehcache และ Google Guava สำหรับแคชในหน่วยความจำ ด้วยแคชที่อยู่ข้างเคียง โค้ดแอปพลิเคชันจะจัดการแคชโดยตรงก่อนและหลังการเรียกใช้ไปยังแหล่งข้อมูล การตรวจหาอ็อบเจ็กต์ที่ถูกแคชก่อนการเรียกใช้แบบดาวน์สตรีม และการใส่อ็อบเจ็กต์ลงในแคชหลังจากที่การเรียกใช้เหล่านั้นเสร็จสมบูรณ์

ประโยชน์หลักของแคชแบบอินไลน์คือโมเดล API ที่เป็นแบบเดียวกันสำหรับไคลเอ็นต์ สามารถเพิ่ม ลบ หรือปรับแต่งการแคชได้โดยไม่ต้องเปลี่ยนลอจิกไคลเอ็นต์ แคชแบบอินไลน์จะดึงลอจิกการจัดการแคชออกมาจากโค้ดของแอปพลิเคชัน ดังนั้นจึงเป็นการลบที่มาของบั๊กที่ก่อให้เกิดปัญหา แคช HTTP นั้นจะน่าสนใจเป็นพิเศษเนื่องจากมีตัวเลือกพร้อมใช้งานมากมายให้เลือก เช่นไลบรารีในหน่วยความจำ พร็อกซี HTTP แบบสแตนด์อโลนอย่างที่ได้กล่าวไว้ข้างต้น และบริการที่ได้รับการจัดการอย่างเครือข่ายการจัดส่งเนื้อหา (CDN)

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

การหมดอายุของแคช

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

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

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

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

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

ข้อพิจารณาอื่นๆ

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

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

ข้อพิจารณาสุดท้ายก็คือ สถานการณ์ “Thundering Herd” ซึ่งหมายความว่าไคลเอ็นต์ได้ส่งคำขอที่ต้องการทรัพยากรดาวน์สตรีมที่ไม่ถูกแคชเหมือนกันในเกือบจะเวลาเดียวกัน ซึ่งอาจเกิดขึ้นเมื่อเซิร์ฟเวอร์ทำงานและเข้าร่วมฟลีตด้วยแคชในตัวที่ว่างเปล่า ซึ่งทำให้คำขอจำนวนมากจากแต่ละเซิร์ฟเวอร์ที่ส่งไปถึงสิ่งที่ขึ้นต่อกันในดาวน์สตรีม ซึ่งสามารถนำไปสู่การควบคุม/การหยุดทำงาน ในการแก้ไขปัญหานี้ เราใช้การรวมคำขอ ซึ่งเซิร์ฟเวอร์หรือแคชภายนอกจะทำให้มั่นใจว่าจะมีคำขอเดียวที่อยู่ในระหว่างการดำเนินการเท่านั้นที่ถูกส่งด้วยทรัพยาการที่ไม่แคช บางไลบรารีการแคชจะมอบการสนับสนุนสำหรับการรวมคำขอ การแคชแบบอินไลน์ภายนอก (เช่น Nginx หรือ Varnish) ก็เช่นกัน นอกจากนี้ ยังสามารถปรับใช้การรวมคำขอเพิ่มเติมให้กับแคชที่มีอยู่อีกด้วย 

แนวปฏิบัติที่ดีที่สุดและข้อพิจารณาเกี่ยวกับ Amazon

บทความนี้ได้กล่าวถึงแนวปฏิบัติที่ดีที่สุดของ Amazon และการตัดสินใจเลือกและความเสี่ยงที่เกี่ยวกับแคช ต่อไปนี้คือสรุปของแนวปฏิบัติที่ดีที่สุดของ Amazon และข้อพิจารณาที่ทีมของเราใช้เมื่อต้องเริ่มใช้งานแคช:

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

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


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

Matt เป็นวิศวกรใหญ่ในฝ่าย Emerging Devices ที่ Amazon ซึ่งเขาทำงานเกี่ยวกับซอฟต์แวร์และบริการสำหรับอุปกรณ์เพื่อผู้บริโภคที่กำลังจะเปิดตัว ก่อนหน้านี้เขาทำงานที่ AWS Elemental โดยเป็นผู้นำทีมที่เปิดใช้งาน MediaTailor ซึ่งเป็นเซิร์ฟเวอร์บริการแทรกโฆษณาที่ปรับตามเฉพาะบุคคลเพื่อวิดีโอแบบสดและวิดีโอตามความต้องการ โดยในระหว่างนั้นเขาได้ช่วยเปิดใช้งานการสตรีม NFL Thursday Night Football ฤดูกาลแรกบน PrimeVideo ก่อนจะมาทำงานที่ Amazon นั้น Matt ได้ใช้เวลา 15 ปีไปกับอุตสาหกรรมการรักษาความปลอดภัย ซึ่งรวมถึงขณะที่ทำงานที่ McAfee, Intel และบริษัทสตาร์ทอัพบางแห่ง โดยเขาได้สร้างการจัดการความปลอดภัยขององค์กร เทคโนโลยีป้องกันมัลแวร์และป้องกันการโจมตีข้อบกพร่อง มาตรการรักษาความปลอดภัยที่มาจากฮาร์ดแวร์ และ DRM

Jas Chhabra เป็นวิศวกรใหญ่ที่ AWS เขาได้เข้าร่วม AWS ในปี 2016 และทำงานกับ AWS IAM เป็นเวลาไม่กี่ปีก่อนที่จะเปลี่ยนแปลงบทบาทมาทำงานกับ AWS Machine Learning ก่อนทำงานที่ AWS เขาทำงานที่ Intel โดยมีบทบาทด้านเทคนิคที่หลากหลายทางด้าน IoT, ข้อมูลประจำตัว และการรักษาความปลอดภัย ความสนใจปัจจุบันคือแมชชีนเลิร์นนิ่ง การรักษาความปลอดภัย และระบบแบบกระจายขนาดใหญ่ ความสนใจในอดีตคือ IoT, Bitcoin, ข้อมูลประจำตัว และการเข้ารหัส เขาสำเร็จการศึกษาระดับปริญญาโททางด้านวิทยาการคอมพิวเตอร์

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