สวัสดีครับ ผมเจย์ออน
วันนี้ผมจะมาอธิบายเกณฑ์การตั้งค่าแคชนะครับ เนื่องจากเป็นโพสต์ที่เขียนขึ้นจากประสบการณ์จริงในการทำงาน ดังนั้นขอให้ทุกคนอ่านเป็นข้อมูลประกอบก็พอครับ ㅎㅎ
แคชคืออะไร?
แคชหมายถึงการเก็บผลลัพธ์ที่คาดว่าจะถูกเรียกใช้ในอนาคตไว้ล่วงหน้า เพื่อให้บริการได้อย่างรวดเร็ว กล่าวคือ เก็บผลลัพธ์ไว้ล่วงหน้า และเมื่อมีการร้องขอในภายหลัง ก็จะเข้าถึงแคชแทนการอ้างอิง DB หรือ API เพื่อประมวลผลคำขอ เทคนิคแคชนี้เกิดขึ้นจากกฎของปาเรโต
กฎของปาเรโตระบุว่า 80% ของผลลัพธ์เกิดจากสาเหตุเพียง 20% ลองดูภาพประกอบด้านล่างนี้ดูนะครับ!
กล่าวคือ แคชไม่จำเป็นต้องแคชผลลัพธ์ทั้งหมด เพียงแคชข้อมูล 20% ที่ใช้บ่อยที่สุดในระหว่างการให้บริการ ก็สามารถเพิ่มประสิทธิภาพโดยรวมได้แล้ว
ควรแคชข้อมูลประเภทใดบ้าง?
ตามกฎของปาเรโต เราไม่ควรแคชข้อมูลใดๆ ก็ได้ แต่ควรแคชเฉพาะข้อมูลที่จำเป็นเท่านั้น แล้วเราควรแคชข้อมูลประเภทใดบ้าง?
ข้อมูลที่ต้องอ่านบ่อยแต่เขียนน้อย
ตามทฤษฎีแล้ว มักกล่าวกันว่า “ควรแคชข้อมูลที่ต้องอ่านบ่อยแต่เขียนน้อย” แต่เกณฑ์ “การอ่านบ่อย” และ “การเขียนน้อย” นั้นค่อนข้างคลุมเครือ
ดังนั้น ผมจึงตรวจสอบข้อมูลที่จะแคชโดยใช้ขั้นตอนต่อไปนี้
- ตรวจสอบประวัติการเรียกใช้คิวรี RDB 5 อันดับแรกโดยใช้ APM เช่น DataDog
- จากนั้นค้นหาคิวรีการสืบค้น และตรวจสอบว่าสืบค้นข้อมูลจากตารางใด
- ตรวจสอบจำนวนครั้งที่คิวรีการอัปเดตตารางนั้นถูกเรียกใช้
กระบวนการข้างต้นคือการตรวจสอบว่ามีการสืบค้นข้อมูลบ่อยแต่มีการอัปเดตข้อมูลน้อยหรือไม่ ตารางที่ผมตรวจสอบในระหว่างการทำงานจริงนั้น มีการสืบค้นข้อมูล 1.74 ล้านครั้งต่อวัน แต่การอัปเดตข้อมูลมีเพียงประมาณ 500 ครั้งเท่านั้น นี่ถือเป็นเงื่อนไขที่เหมาะสมสำหรับการใช้แคชอย่างเห็นได้ชัด ㅎㅎ
ข้อมูลที่ไวต่อการอัปเดต
ข้อมูลที่ไวต่อการอัปเดตหมายถึงความแตกต่างระหว่าง RDB กับแคชต้องน้อยที่สุด ตัวอย่างเช่น ข้อมูลที่เกี่ยวข้องกับการชำระเงินนั้นไวต่อการอัปเดตมาก ดังนั้นแม้ว่าจะตรงตามเงื่อนไขแคชข้างต้น ก็ควรพิจารณาการใช้งานด้วย
ผมจำเป็นต้องแคชตารางที่เกี่ยวข้องกับการชำระเงินที่ตรงตามลักษณะทั้งสองข้างต้น ดังนั้นผมจึงไม่ได้ใช้แคชกับทุกโลจิกที่ใช้ตารางที่เกี่ยวข้องกับการชำระเงินนี้ แต่ได้ตัดสินใจแคชเฉพาะบางส่วนในโลจิกที่ค่อนข้างปลอดภัยและไม่เกิดการชำระเงินจริง
แคชแบบโลคอลกับแคชแบบโกลบอล
ตอนนี้เรากำหนดข้อมูลที่จะแคชและขอบเขตของการแคชได้ระดับหนึ่งแล้ว ต่อไปก็ต้องพิจารณาว่าจะ “เก็บ” ข้อมูลแคชไว้ที่ใด โดยทั่วไปสามารถเก็บไว้ในหน่วยความจำโลคอลหรือเก็บไว้ในเซิร์ฟเวอร์แยกต่างหาก เช่น Redis
แคชแบบโลคอล
แคชแบบโลคอลคือวิธีการเก็บข้อมูลที่จะแคชไว้ในหน่วยความจำของเซิร์ฟเวอร์แอปพลิเคชัน โดยทั่วไปมักใช้ Guava cache หรือ Caffeine cache
ข้อดี
- เนื่องจากสืบค้นแคชจากหน่วยความจำภายในเซิร์ฟเวอร์เดียวกันในขณะที่ดำเนินการโลจิกของแอปพลิเคชัน จึงมีความเร็วสูง
- ใช้งานง่าย
ข้อเสีย
- หากมีอินสแตนซ์หลายตัว จะเกิดปัญหาต่างๆ มากมาย
- ไม่สามารถเผยแพร่แคชที่แก้ไขในอินสแตนซ์หนึ่งไปยังอินสแตนซ์อื่นได้ แต่ก็มีไลบรารีแคชแบบโลคอลที่สามารถเผยแพร่การแก้ไขไปยังอินสแตนซ์อื่นได้
- เนื่องจากแต่ละอินสแตนซ์จะเก็บแคชไว้ ดังนั้นเมื่อมีอินสแตนซ์ใหม่เกิดขึ้น ก็จะต้องใส่แคชใหม่ ทำให้เกิดแคชมิสมากขึ้น ส่งผลให้ไม่สามารถรับมือกับปริมาณการใช้งานได้ และอินสแตนซ์อาจหยุดทำงาน
แคชแบบโกลบอล
แคชแบบโกลบอลเป็นวิธีการใช้เซิร์ฟเวอร์แยกต่างหากเพื่อเก็บข้อมูลแคช เช่น Redis
ข้อดี
- เนื่องจากอินสแตนซ์ต่างๆ สามารถใช้แคชร่วมกันได้ ดังนั้นแม้ว่าแคชจะถูกแก้ไขในอินสแตนซ์หนึ่ง อินสแตนซ์อื่นๆ ก็จะได้รับค่าแคชเดียวกัน
- เมื่อมีอินสแตนซ์ใหม่เกิดขึ้น ก็สามารถดูที่ที่เก็บแคชที่มีอยู่แล้วได้ จึงไม่จำเป็นต้องใส่แคชใหม่
ข้อเสีย
- เนื่องจากต้องผ่านการรับส่งข้อมูลผ่านเครือข่าย จึงมีความเร็วช้ากว่าแคชแบบโลคอล
- ต้องมีเซิร์ฟเวอร์แคชแยกต่างหาก จึงมีค่าใช้จ่ายในการดูแลรักษาโครงสร้างพื้นฐานเพิ่มขึ้น
- ค่าใช้จ่ายในการดูแลรักษาโครงสร้างพื้นฐาน? → ค่าใช้จ่ายของเซิร์ฟเวอร์ การตั้งค่าและบำรุงรักษาโครงสร้างพื้นฐาน และการวางแผนรับมือกับเหตุขัดข้องต่างๆ
ผู้เขียนเลือกใช้แบบใด?
ปัจจุบันเซิร์ฟเวอร์แอปพลิเคชันของบริษัทมีโครงสร้างที่เปิดใช้งานอินสแตนซ์หลายตัว แต่ผมเลือกใช้แคชแบบโลคอล
โดยหลักๆ มีเหตุผล 3 ข้อ
- ข้อมูลที่จะแคชที่เก็บไว้ใน RDB มีประมาณ 40,000 รายการ ซึ่งหากนำข้อมูลทั้งหมดขึ้นหน่วยความจำ ก็จะใช้พื้นที่ไม่เกิน 4 MB
- ต้องให้ประสิทธิภาพการสืบค้นข้อมูลที่เกี่ยวข้องกับการชำระเงินดีขึ้น
- แม้ว่าจะมี Redis อยู่แล้ว แต่การเก็บแคชใหม่ใน Redis จะทำให้เกิดค่าใช้จ่ายด้านโครงสร้างพื้นฐาน
จะอัปเดตแคชอย่างไร?
หากมีเซิร์ฟเวอร์แอปพลิเคชันหลายตัวและใช้แคชแบบโลคอล ค่าแคชที่เก็บไว้ในแต่ละเซิร์ฟเวอร์แอปพลิเคชันอาจแตกต่างกัน ตัวอย่างเช่น ค่าข้อมูลแคชที่เก็บไว้ในเซิร์ฟเวอร์ A คือ “1” แต่ค่าข้อมูลแคชที่เก็บไว้ในเซิร์ฟเวอร์ B อาจถูกแก้ไขเป็น “2” ในเซิร์ฟเวอร์ B ในสถานการณ์นี้ หากผู้ใช้ส่งคำขอไปยังโหลดบาลานเซอร์ ก็จะได้รับค่าที่แตกต่างกันจากเซิร์ฟเวอร์ A และเซิร์ฟเวอร์ B
ดังนั้นจึงจำเป็นต้องกำหนดค่าให้แคชในแต่ละอินสแตนซ์ล้างข้อมูลโดยอัตโนมัติและสืบค้นข้อมูลจาก RDB โดยใช้ TTL
ควรตั้งค่า TTL เป็นเท่าใด
TTL ย่อมาจาก Time To Live ซึ่งเป็นการตั้งค่าที่จะล้างแคชหลังจากเวลาที่กำหนด ตัวอย่างเช่น หากตั้งค่า TTL เป็น 5 วินาที ข้อมูลแคชจะถูกล้างโดยอัตโนมัติหลังจาก 5 วินาที จากนั้นหากเกิดแคชมิส ก็จะสืบค้นข้อมูลจาก RDB และเก็บไว้
แล้วควรตั้งค่า TTL เป็นเท่าใด
การอ่าน/เขียนเกิดขึ้นในเซิร์ฟเวอร์แคชเดียว
หากการอ่าน/เขียนเกิดขึ้นในเซิร์ฟเวอร์แคชแบบโกลบอล เช่น Redis หรือเกิดขึ้นในเซิร์ฟเวอร์แอปพลิเคชันเดียวที่ใช้แคชแบบโลคอล ก็สามารถตั้งค่า TTL เป็นหน่วยชั่วโมงขึ้นไปได้ เนื่องจากเมื่อเขียนข้อมูล ก็จะแก้ไขแคชเดิม และเซิร์ฟเวอร์ที่ดึงข้อมูลจากแคชนั้นจะเห็นข้อมูลล่าสุดเสมอ
ในกรณีนี้ เราอาจไม่ตั้งค่า TTL และให้เซิร์ฟเวอร์แคชล้างแคชทีละน้อยโดยอัตโนมัติเมื่อพื้นที่แคชเต็มโดยใช้ Algorithm LRU
การอ่าน/เขียนเกิดขึ้นในเซิร์ฟเวอร์แคชหลายตัว
หากการอ่าน/เขียนเกิดขึ้นในเซิร์ฟเวอร์แคชแบบโกลบอลหลายตัว หรือเกิดขึ้นในเซิร์ฟเวอร์แอปพลิเคชันหลายตัวที่ใช้แคชแบบโลคอล ควรตั้งค่า TTL เป็นหน่วยวินาทีถึงนาที เนื่องจากอาจมีโอกาสอ่านข้อมูลเก่าจากเซิร์ฟเวอร์แคชที่ยังไม่ได้อัปเดตข้อมูลที่แก้ไข
ในกรณีนี้ TTL จะถูกกำหนดตามบริบทต่างๆ โดยยิ่งมีความสำคัญของการอัปเดตและความน่าจะเป็นของการเปลี่ยนแปลงค่าสูงเท่าใด ก็ควรตั้งค่า TTL ให้สั้นลงเท่านั้น และยิ่งมีความสำคัญของการอัปเดตน้อยและความน่าจะเป็นของการเปลี่ยนแปลงค่าต่ำเท่าใด ก็สามารถตั้งค่า TTL ให้ยาวขึ้นได้
ผู้เขียนตั้งค่า TTL อย่างไร
ข้อมูลที่จะแคชของผมคือข้อมูลที่เกี่ยวข้องกับการชำระเงิน และแม้ว่าจะไม่ได้ใช้แคชกับโลจิกที่เกิดการชำระเงินจริง แต่ก็ยังมีความสำคัญของการอัปเดตเนื่องจากเป็นข้อมูลที่เกี่ยวข้องกับการชำระเงิน อย่างไรก็ตาม ความเป็นไปได้ในการอัปเดตค่อนข้างต่ำ ดังนั้นจึงตั้งค่า TTL เป็น 5 วินาทีเพื่อความปลอดภัย
สรุป
สรุปแล้ว วิธีการแคชที่ผมเลือกใช้มีดังนี้
- ข้อมูลที่เกี่ยวข้องกับการชำระเงิน
- การสืบค้นข้อมูลเกิดขึ้นบ่อยมาก แต่การแก้ไขข้อมูลเกิดขึ้นน้อยมาก
- ใช้แคชเฉพาะโลจิกที่ไม่เกิดการชำระเงินจริง แต่มีการสืบค้นข้อมูล
- ใช้แคชแบบโลคอล และตั้งค่า TTL เป็น 5 วินาที
สิ่งที่ต้องทำต่อไปคือการทดสอบประสิทธิภาพของวิธีการแคชที่ใช้จริง โดยตอนนี้ยังกำลังพิจารณาว่าจะทดสอบประสิทธิภาพอย่างไร จึงจะเขียนอธิบายในโพสต์ถัดไปนะครับ!
ความคิดเห็น0