ในส่วนแรกของซีรีส์สองส่วนเกี่ยวกับหน่วยความจำรั่ว เราได้ดูว่า Ruby จัดการหน่วยความจำอย่างไรและ Garbage Collection (GC) ทำงานอย่างไร
คุณอาจสามารถซื้อเครื่องที่มีประสิทธิภาพพร้อมหน่วยความจำได้มากขึ้น และแอปของคุณอาจรีสตาร์ทบ่อยพอที่จะทำให้ผู้ใช้ไม่สังเกตเห็น แต่การใช้หน่วยความจำก็มีความสำคัญ
การจัดสรรและการรวบรวมขยะไม่ฟรี หากคุณมีการรั่วไหล คุณจะใช้เวลากับ Garbage Collection มากขึ้นเรื่อยๆ แทนที่จะทำในสิ่งที่คุณสร้างแอปให้ทำ
ในโพสต์นี้ เราจะเจาะลึกลงไปในเครื่องมือที่คุณสามารถใช้เพื่อค้นหาและวินิจฉัยหน่วยความจำรั่ว
ลุยต่อ!
การค้นหารอยรั่วใน Ruby
การตรวจจับรอยรั่วนั้นง่ายพอ คุณสามารถใช้ 02 , 18รหัส> และกราฟ RSS ในเครื่องมือ APM ของคุณเพื่อดูการใช้หน่วยความจำที่เพิ่มขึ้น แต่การรู้ว่าคุณมีรอยรั่วนั้นไม่เพียงพอที่จะแก้ไขได้ คุณต้องรู้ว่ามันมาจากไหน ตัวเลขดิบไม่สามารถบอกคุณได้
โชคดีที่ระบบนิเวศของ Ruby มีเครื่องมือที่ยอดเยี่ยมในการแนบบริบทกับตัวเลขเหล่านั้น สองคือ 22 และ 39รหัส> .
40รหัส> ใน Ruby
55รหัส> gem นำเสนอ API ที่เรียบง่ายมากและรายงานการจัดสรรและเก็บรักษาหน่วยความจำที่มีรายละเอียด (แม้ว่าจะมีมากเกินไปก็ตาม) ซึ่งรวมถึงคลาสของอ็อบเจ็กต์ที่ได้รับการจัดสรร ขนาด และตำแหน่งที่ได้รับการจัดสรร คุณสามารถเพิ่มลงในโปรแกรมที่รั่วไหลของเราได้อย่างง่ายดาย
แสดงผลรายงานที่มีลักษณะคล้ายกันนี้
มีข้อมูลมากมายที่นี่ แต่โดยทั่วไปคือ62 และ 72รหัส> การสแกนส่วนจะมีประโยชน์มากที่สุดเมื่อค้นหารอยรั่ว เหล่านี้คือตำแหน่งไฟล์ที่จัดสรรอ็อบเจ็กต์ โดยเรียงลำดับตามจำนวนอ็อบเจ็กต์ที่ได้รับการจัดสรร
81รหัส>วัตถุคือวัตถุทั้งหมดที่ได้รับการจัดสรร (สร้าง) ภายใน94บล็อก104รหัส>วัตถุคือวัตถุที่ไม่ได้รับการรวบรวมขยะในตอนท้ายของ118บล็อก เราบังคับให้ GC วิ่งก่อนสิ้นสุดบล็อกเพื่อให้เรามองเห็นวัตถุที่รั่วไหลได้ชัดเจนยิ่งขึ้น
โปรดใช้ความระมัดระวังในการเชื่อถือ 122 การนับวัตถุ ขึ้นอยู่กับว่าส่วนใดของโค้ดที่รั่วไหลอยู่ภายใน 137 บล็อก.
ตัวอย่างเช่น ถ้าเราย้ายการประกาศของ 148 ลงใน 157 บล็อก เราอาจหลงคิดว่าโค้ดไม่รั่วไหล
ด้านบนของรายงานผลลัพธ์จะไม่รายงานออบเจ็กต์ที่เก็บไว้จำนวนมาก (เฉพาะรายงานเท่านั้น)
160รหัส> ใน Ruby
170รหัส> gem เป็นชุดเครื่องมือที่มีประโยชน์มากสำหรับงานด้านประสิทธิภาพทุกประเภท โดยมุ่งเป้าไปที่แอป Rails เป็นหลัก สำหรับการค้นหาการรั่วไหล เราต้องการดูที่ 183 , 197รหัส> และ207 .
งานเหล่านี้ทำงานโดยการส่ง 219 คำขอไปยังแอปที่ทำงานอยู่ ดังนั้นเราจึงไม่สามารถเพิ่มลงในโปรแกรมรั่วเล็กๆ ของเราได้ แต่เราจะต้องตั้งค่าแอป SmallRails ด้วยจุดสิ้นสุดที่ทำให้หน่วยความจำรั่วไหลแทน จากนั้นจึงติดตั้ง221 บนแอปนั้น
ตอนนี้คุณควรจะสามารถบูตแอปด้วย 230 ได้ . คุณจะสามารถ 246 ได้ จุดสิ้นสุดที่รั่วไหลในแต่ละคำขอ
ตอนนี้เราสามารถใช้ 259 ได้แล้ว เพื่อดูการรั่วไหลของเรา
262รหัส>
สิ่งนี้จะแสดงให้เราเห็นการใช้หน่วยความจำเมื่อเวลาผ่านไป (คล้ายกับวิธีที่เราดูการเติบโตของธีมของสคริปต์ที่รั่วไหลของเราด้วย 276 และ 280รหัส> ).
Derailed จะบูตแอปในโหมดใช้งานจริง และแตะจุดปลายทางซ้ำๆ (299 ตามค่าเริ่มต้น) และรายงานการใช้หน่วยความจำ ถ้ามันไม่หยุดเติบโต เราก็มีรอยรั่ว!
หมายเหตุ :Derailed จะบูตแอป Rails ในโหมดใช้งานจริงเพื่อทำการทดสอบ ตามค่าเริ่มต้น ก็จะเป็น 307 ด้วย ก่อน เนื่องจากเราไม่มีฐานข้อมูลในแอปนี้ เราจึงต้องแทนที่พฤติกรรมนี้ด้วย 313 .
เราสามารถเรียกใช้การวัดประสิทธิภาพนี้กับปลายทางที่แตกต่างกันเพื่อดูว่าจุดใด (ถ้ามี) รั่วไหล
325รหัส>
335รหัส> งานใช้ 345 ภายใต้ประทุนเพื่อให้รายงานที่ผลิตออกมาดูคุ้นเคย
รายงานนี้สามารถช่วยจำกัดขอบเขตการจัดสรรหน่วยความจำที่รั่วไหลของคุณให้แคบลงได้ ในตัวอย่างของเรา ส่วนสุดท้ายของรายงาน — 355 — บอกเราอย่างชัดเจนว่าปัญหาของเราคืออะไร
เรารั่วไหล 10,000 สายที่มี "ABC" จาก 363 ออนไลน์ 3. ในแอปที่ไม่ซับซ้อน รายงานนี้จะมีขนาดใหญ่กว่ามากและมีสตริงที่เก็บไว้ซึ่งคุณต้องการเก็บไว้ เช่น แคชการค้นหา ฯลฯ แต่ส่วนนี้และส่วน 'ตามตำแหน่ง' อื่นๆ จะช่วยคุณจำกัดการรั่วไหลให้แคบลงได้
371รหัส>
383รหัส> การวัดประสิทธิภาพสามารถช่วยได้หากรายงานจาก 398 ซับซ้อนเกินกว่าจะรู้ว่ารอยรั่วของคุณมาจากไหน
ตามชื่อที่แนะนำ 401 สร้างการดัมพ์สามฮีปและคำนวณความแตกต่างระหว่างพวกมัน โดยจะสร้างรายงานซึ่งรวมถึงประเภทของออบเจ็กต์ที่เก็บไว้ระหว่างดัมพ์และตำแหน่งที่จัดสรรไว้
คุณยังอ่านการติดตามหน่วยความจำ Ruby รั่วไหลในปี 2021 ได้อีกด้วย เพื่อทำความเข้าใจให้ดีขึ้นว่าเกิดอะไรขึ้น
รายงานระบุให้เราทราบอย่างชัดเจนว่าเราต้องไปที่ไหนสำหรับแอปสำหรับเด็กที่รั่วไหล ที่ด้านบนของส่วนต่าง เราเห็นวัตถุสตริงที่เก็บไว้ 999991 ซึ่งจัดสรรจาก 411 ในบรรทัดที่ 3
การรั่วไหลในแอป Real Ruby และ Rails
หวังว่าตัวอย่างที่เราใช้จนถึงตอนนี้ไม่เคยถูกนำไปใช้กับแอปในชีวิตจริง ฉันหวังว่าจะไม่มีใครตั้งใจจะทำให้หน่วยความจำรั่วไหล!
ในแอปที่ไม่ซับซ้อน การรั่วไหลของหน่วยความจำอาจติดตามได้ยากกว่ามาก อ็อบเจ็กต์ที่เก็บรักษาไว้ไม่ได้แย่เสมอไป แคชที่มีรายการขยะที่รวบรวมไว้จะไม่มีประโยชน์มากนัก
มีบางสิ่งที่เหมือนกันระหว่างการรั่วไหลทั้งหมด ที่ไหนสักแห่ง วัตถุระดับราก (คลาส/โกลบอล ฯลฯ) เก็บการอ้างอิงไปยังวัตถุ
ตัวอย่างหนึ่งที่พบบ่อยคือแคชที่ไม่มีขีดจำกัดหรือนโยบายการไล่ออก ตามคำจำกัดความ สิ่งนี้จะทำให้หน่วยความจำรั่วไหลเนื่องจากทุกวัตถุที่ใส่ลงในแคชจะคงอยู่ตลอดไป เมื่อเวลาผ่านไป แคชนี้จะครอบครองหน่วยความจำของแอปมากขึ้นเรื่อยๆ โดยมีเปอร์เซ็นต์การใช้งานจริงน้อยลงเรื่อยๆ
พิจารณาโค้ดต่อไปนี้ที่ดึงคะแนนสูงสำหรับเกม มันคล้ายกับสิ่งที่ฉันเคยเห็นในอดีต นี่เป็นคำขอที่มีราคาแพง และเราสามารถหยุดแคชได้อย่างง่ายดายเมื่อมีการเปลี่ยนแปลง ดังนั้นเราจึงต้องการแคช
424รหัส> แฮชไม่ได้ถูกตรวจสอบโดยสมบูรณ์ มันจะเติบโตจนกลายเป็นคะแนนสูงสุดสำหรับผู้ใช้ทุกคน — ไม่เหมาะหากคุณมีคะแนนอย่างใดอย่างหนึ่งมาก
ในแอป Rails เราอาจต้องการใช้ 435 ด้วยการหมดอายุที่สมเหตุสมผล (หน่วยความจำรั่วใน Redis ยังคงเป็นหน่วยความจำรั่ว!) แทน
ในแอปที่ไม่ใช่ Rails เราต้องการจำกัดขนาดแฮช โดยกำจัดรายการที่เก่าแก่ที่สุดหรือใช้งานน้อยที่สุด 445รหัส> เป็นการดำเนินการที่ดี
การรั่วไหลเวอร์ชันที่ละเอียดอ่อนกว่านี้คือแคชที่มีขีด จำกัด แต่คีย์ที่มีขนาดที่กำหนดเอง หากคีย์เติบโต แคชก็จะเติบโตเช่นกัน ปกติคุณจะไม่โดนแบบนี้ แต่หากคุณกำลังซีเรียลไลซ์ออบเจ็กต์เป็น JSON และใช้เป็นคีย์ ให้ตรวจสอบอีกครั้งว่าคุณไม่ได้ซีเรียลไลซ์สิ่งต่าง ๆ ที่เพิ่มขึ้นตามการใช้งานเช่นกัน เช่น รายการข้อความที่อ่านแล้วของผู้ใช้
การอ้างอิงแบบวงกลม
การอ้างอิงแบบวงกลม สามารถ จะถูกเก็บขยะ Garbage Collection ใน Ruby ใช้อัลกอริทึม "Mark and Sweep" ในระหว่างการนำเสนอแนะนำการจัดสรรความกว้างตัวแปร Peter Zhu และ Matt Valentine-House ได้ให้คำอธิบายที่ยอดเยี่ยมเกี่ยวกับวิธีการทำงานของอัลกอริทึมนี้
โดยพื้นฐานแล้ว มีสองขั้นตอน:การทำเครื่องหมายและการกวาด
-
ในเครื่องหมาย เฟส ตัวรวบรวมขยะเริ่มต้นที่ออบเจ็กต์รูท (คลาส, โกลบอล ฯลฯ) ทำเครื่องหมายแล้วดูที่ออบเจ็กต์ที่อ้างอิง
จากนั้นจะทำเครื่องหมายออบเจ็กต์ที่อ้างอิงทั้งหมด วัตถุอ้างอิงที่ถูกทำเครื่องหมายไว้แล้วจะไม่ถูกมองอีกครั้ง สิ่งนี้จะดำเนินต่อไปจนกว่าจะไม่มีวัตถุให้ดูอีกต่อไป — กล่าวคือ มีการทำเครื่องหมายวัตถุที่อ้างอิงทั้งหมดแล้ว
-
จากนั้นคนเก็บขยะจะเคลื่อนไปยังการกวาด เฟส วัตถุใดๆ ที่ไม่ได้ทำเครื่องหมายจะถูกล้างข้อมูล
ดังนั้น วัตถุที่มีการอ้างอิงแบบสดยังสามารถล้างข้อมูลได้ ตราบใดที่ออบเจ็กต์รูทไม่ได้อ้างอิงถึงออบเจ็กต์ในที่สุด มันก็จะถูกรวบรวม ด้วยวิธีนี้ กลุ่มของวัตถุที่มีการอ้างอิงแบบวงกลมยังสามารถถูกรวบรวมขยะได้
การตรวจสอบประสิทธิภาพของแอปพลิเคชัน:ไทม์ไลน์ของเหตุการณ์และกราฟออบเจ็กต์ที่จัดสรร
ตามที่กล่าวไว้ในส่วนแรกของชุดนี้ แอประดับการผลิตใดๆ ควรใช้ Application PerformanceMonitoring (APM) บางรูปแบบ
มีตัวเลือกมากมายให้เลือก รวมถึงการกลิ้งของคุณเอง (แนะนำสำหรับทีมขนาดใหญ่เท่านั้น) คุณลักษณะสำคัญประการหนึ่งที่คุณควรได้รับจาก APM คือความสามารถในการดูจำนวนการจัดสรรที่การดำเนินการ (หรืองานเบื้องหลัง) ทำ APMtools ที่ดีจะแจกแจงรายละเอียดนี้ โดยให้ข้อมูลเชิงลึกว่าการจัดสรรมาจากไหน — ผู้ควบคุม มุมมอง ฯลฯ
ซึ่งมักเรียกกันว่า "ไทม์ไลน์ของกิจกรรม" คะแนนโบนัสหาก APM ของคุณอนุญาตให้คุณเขียนโค้ดที่กำหนดเองซึ่งจะแจกแจงไทม์ไลน์เพิ่มเติม
พิจารณาโค้ดต่อไปนี้สำหรับตัวควบคุม Rails
เมื่อรายงานโดย APM 'ไทม์ไลน์ของเหตุการณ์' อาจมีลักษณะคล้ายกับภาพหน้าจอต่อไปนี้จาก AppSignal

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

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

เมื่อดูการจัดสรรด้วยวิธีนี้ เราสามารถจำกัดการค้นหาของเราให้แคบลงเมื่อพิจารณาปัญหาหน่วยความจำ ทำให้ใช้เครื่องมือเช่น454ได้ง่ายขึ้นมาก และ 468รหัส> อย่างมีประสิทธิภาพ
อ่านเกี่ยวกับส่วนเสริมล่าสุดใน Ruby gem ของ AppSignal เช่น การจัดสรรและการติดตามสถิติ GC
สรุป
ในโพสต์นี้ เราได้เจาะลึกเครื่องมือบางอย่างที่สามารถช่วยค้นหาและแก้ไขหน่วยความจำรั่วได้ รวมถึง 471 , 480รหัส> , 497รหัส> , 503รหัส> , 514รหัส> ไทม์ไลน์ของเหตุการณ์และกราฟออบเจ็กต์ที่จัดสรรใน AppSignal
ฉันหวังว่าคุณจะพบโพสต์นี้พร้อมกับส่วนที่หนึ่ง ซึ่งมีประโยชน์ในการวินิจฉัยและแยกแยะหน่วยความจำรั่วในแอป Ruby ของคุณ
อ่านเพิ่มเติมเกี่ยวกับเครื่องมือที่เราใช้:
528รหัส>533รหัส>- แอป Rails ที่รั่ว
อ่านรายละเอียดเพิ่มเติม:
544รหัส> เอกสารประกอบโมดูล557รหัส> เอกสารประกอบโมดูล- เจาะลึกการเก็บขยะ
- การจัดสรรความกว้างของตัวแปร
ขอให้สนุกกับการเขียนโค้ด!
ปล. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่เผยแพร่ สมัครรับจดหมายข่าว Ruby Magic ของเราและไม่พลาดแม้แต่โพสต์เดียว! ป>