- ในอดีต การใช้ CPU ของระบบของฉันพุ่งไปถึง 3,200% จนคอร์ทั้ง 32 คอร์ทำงานเต็มทั้งหมด
- ขณะนั้นใช้ Java 17 runtime และเมื่อตรวจดูเวลา CPU จาก thread dump แล้วจัดเรียงตามเวลา CPU ก็พบเธรดลักษณะคล้ายกันจำนวนมาก
- การวิเคราะห์โค้ดที่เป็นปัญหา
- ตรวจสอบบรรทัดที่ 29 ของคลาส
BusinessLogic ผ่าน stack trace
- โค้ดดังกล่าวมีลักษณะเป็นการวนลูป
unrelatedObjects แล้วแทรกค่าของ relatedObject ลงใน treeMap
- นี่เป็นโค้ดที่ไม่มีประสิทธิภาพ เพราะไม่ได้ใช้
unrelatedObject ภายในลูป
การแก้ไขโค้ดและการทดสอบ
- ลบลูปที่ไม่จำเป็นออก แล้วแก้เป็นบรรทัดเดียว
treeMap.put(relatedObject.a(), relatedObject.b());
- มีการรัน unit test ก่อนและหลังแก้ไข แต่ไม่สามารถทำให้ปัญหาเกิดขึ้นซ้ำได้
- แม้
treeMap และ unrelatedObjects จะมีขนาดมากกว่า 1,000,000 รายการ ก็ยังไม่เกิดปัญหา
ค้นพบสาเหตุของปัญหา
treeMap ถูกเข้าถึงพร้อมกันจากหลายเธรด และไม่มีการซิงโครไนซ์
- ปัญหานี้เกิดจากหลายเธรดแก้ไข
TreeMap พร้อมกัน
การทำให้ปัญหาเกิดซ้ำผ่านการทดลอง
- ทำการทดลองให้หลายเธรดอัปเดต
TreeMap ที่แชร์ร่วมกันแบบสุ่ม
- ตั้งค่าให้ใช้บล็อก
try-catch เพื่อเพิกเฉยต่อ NullPointerException
- ผลการทดลองพบว่าการใช้ CPU สามารถเพิ่มขึ้นได้ถึง 500%
บทสรุป
- การแก้ไข
TreeMap พร้อมกันโดยไม่มีการซิงโครไนซ์ อาจก่อให้เกิดปัญหาด้านประสิทธิภาพอย่างรุนแรง
- เพื่อป้องกันปัญหานี้ แนะนำให้ซิงโครไนซ์
TreeMap หรือใช้คอลเลกชันที่ปลอดภัยต่อเธรด เช่น ConcurrentMap
1 ความคิดเห็น
ความเห็นจาก Hacker News
เคยคิดว่า race condition ทำให้ข้อมูลเสียหายหรือเกิด deadlock เท่านั้น แต่ไม่เคยนึกมาก่อนว่ามันจะก่อปัญหาด้านประสิทธิภาพได้ด้วย ข้อมูลอาจเสียหายในลักษณะที่สร้างลูปไม่สิ้นสุดได้
ในโค้ดที่มีหลาย thread ทำงานอยู่ กลยุทธ์เดียวที่แน่นอนจริง ๆ คือทำให้อ็อบเจ็กต์ทั้งหมด immutable และสำหรับอ็อบเจ็กต์ที่ทำให้ immutable ไม่ได้ ก็ต้องจำกัดให้อยู่ในส่วนที่เล็ก พึ่งพาตัวเองได้ และควบคุมอย่างเข้มงวด
ประโยคที่ว่า "แทบจะ ssh เข้าไปไม่ได้" ทำให้นึกถึงตอนเรียนบัณฑิตศึกษาที่ใช้ Sun UltraSparc 170
โค้ดสามารถย่อให้เหลือเพียงประมาณนี้ได้
อีกวิธีหนึ่งที่จะเกิดลูปไม่สิ้นสุดได้ คือใช้ implementation ของ <i>Comparator</i> หรือ <i>Comparable</i> ที่ไม่ได้สร้าง total order ที่สอดคล้องกัน
อาจพิจารณาใช้ตัวนับที่เพิ่มขึ้นเพื่อตรวจจับ cycle และโยน exception หากเกินความลึกของต้นไม้หรือขนาดของ collection
ใน Java การทำงานพร้อมกันกับอ็อบเจ็กต์ที่ไม่ thread-safe มักสร้างบั๊กที่น่าสนใจที่สุด
มีคำถามว่า TreeMap ที่ไม่ได้ป้องกันไว้สามารถทำให้เกิดการใช้งาน 3,200% ได้จริงหรือไม่
ผู้เขียนได้ค้นพบ Poison Pill รูปแบบหนึ่ง ซึ่งพบได้บ่อยกว่าในระบบ event sourcing และเป็นข้อความที่ฆ่าทุกสิ่งที่มันไปเจอ
exception ใน thread เป็นปัญหาร้ายแรงอย่างแท้จริง