เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

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

ครั้งหนึ่งเราค้นพบว่าแอป Dodo Pizza ใช้เวลาเปิดตัวโดยเฉลี่ย 3 วินาที และสำหรับ “ผู้โชคดี” บางคนอาจใช้เวลา 15-20 วินาที

ด้านล่างของการตัดเป็นเรื่องราวที่จบลงอย่างมีความสุข: เกี่ยวกับการเติบโตของฐานข้อมูล Realm, หน่วยความจำรั่ว, การที่เราสะสมวัตถุที่ซ้อนกันได้อย่างไร จากนั้นจึงรวมตัวกันและแก้ไขทุกอย่าง

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน
ผู้เขียนบทความ: แม็กซิม คาชินคิน — นักพัฒนา Android ที่ Dodo Pizza

สามวินาทีจากการคลิกที่ไอคอนแอปพลิเคชันไปจนถึง onResume() ของกิจกรรมแรกนั้นไม่มีที่สิ้นสุด และสำหรับผู้ใช้บางราย เวลาเริ่มต้นถึง 15-20 วินาที สิ่งนี้เป็นไปได้อย่างไร?

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

ค้นหาและวิเคราะห์ปัญหา

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

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

นานแค่ไหน? ตาม เอกสารของ Googleหากการเริ่มแอปพลิเคชันโดยไม่ใช้เครื่องใช้เวลาน้อยกว่า 5 วินาที จะถือว่า "เป็นเรื่องปกติ" เปิดตัวแอป Dodo Pizza สำหรับ Android (ตามเมตริก Firebase _app_start) ที่ เริ่มเย็น โดยเฉลี่ยใน 3 วินาที - “ไม่ดี ไม่แย่” อย่างที่พวกเขาพูด

แต่แล้วการร้องเรียนก็เริ่มปรากฏว่าแอปพลิเคชันใช้เวลานานมากในการเปิดตัว! ประการแรก เราตัดสินใจวัดว่า "ยาวมาก ยาวมาก" คืออะไร และเราใช้การติดตาม Firebase สำหรับสิ่งนี้ การติดตามการเริ่มต้นแอป.

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

การติดตามมาตรฐานนี้จะวัดเวลาระหว่างช่วงเวลาที่ผู้ใช้เปิดแอปพลิเคชันและช่วงเวลาที่ onResume() ของกิจกรรมแรกถูกดำเนินการ ในคอนโซล Firebase เมตริกนี้เรียกว่า _app_start ปรากฎว่า:

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

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

ความคิดสองประการเข้ามาในใจ:

  1. มีบางอย่างกำลังรั่วไหล
  2. “บางสิ่ง” นี้จะถูกรีเซ็ตหลังจากปล่อยแล้วรั่วไหลอีกครั้ง

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

เกิดอะไรขึ้นกับฐานข้อมูล Realm

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

adb exec-out run-as ${PACKAGE_NAME} cat files/${DB_NAME}

เมื่อดูเนื้อหาของฐานข้อมูลในเวลาที่ต่างกัน เราพบว่าจำนวนออบเจ็กต์บางประเภทเพิ่มขึ้นอย่างต่อเนื่อง

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

ความสัมพันธ์ระหว่างการเติบโตของฐานข้อมูลและเวลาเริ่มต้น

การเติบโตของฐานข้อมูลที่ไม่สามารถควบคุมได้นั้นแย่มาก แต่สิ่งนี้ส่งผลต่อเวลาเริ่มต้นแอปพลิเคชันอย่างไร การวัดสิ่งนี้ผ่าน ActivityManager ค่อนข้างง่าย ตั้งแต่ Android 4.4 logcat จะแสดงบันทึกพร้อมสตริงที่แสดงและเวลา เวลานี้เท่ากับช่วงเวลาตั้งแต่ช่วงเวลาที่เปิดแอปพลิเคชันจนถึงสิ้นสุดการเรนเดอร์กิจกรรม ในช่วงเวลานี้มีเหตุการณ์ต่อไปนี้เกิดขึ้น:

  • เริ่มกระบวนการ
  • การเริ่มต้นของวัตถุ
  • การสร้างและการเริ่มต้นกิจกรรม
  • การสร้างเค้าโครง
  • การแสดงผลแอปพลิเคชัน

เหมาะกับเรา. หากคุณรัน ADB ด้วยแฟล็ก -S และ -W คุณจะได้รับเอาต์พุตเพิ่มเติมพร้อมเวลาเริ่มต้นระบบ:

adb shell am start -S -W ru.dodopizza.app/.MainActivity -c android.intent.category.LAUNCHER -a android.intent.action.MAIN

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

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

ในขณะเดียวกัน ก็มีความสัมพันธ์ในลักษณะเดียวกันระหว่างขนาดและการเติบโตของฐานข้อมูล ซึ่งเพิ่มขึ้นจาก 4 MB เป็น 15 MB โดยรวมแล้วปรากฎว่าเมื่อเวลาผ่านไป (ด้วยการเติบโตของ Cold Start) ทั้งเวลาเปิดตัวแอปพลิเคชันและขนาดของฐานข้อมูลก็เพิ่มขึ้น เรามีสมมติฐานอยู่ในมือของเรา ตอนนี้สิ่งที่เหลืออยู่คือการยืนยันการพึ่งพาอาศัยกัน ดังนั้นเราจึงตัดสินใจลบ "รอยรั่ว" และดูว่าจะทำให้การเปิดตัวเร็วขึ้นหรือไม่

เหตุผลในการเติบโตของฐานข้อมูลอย่างไม่มีที่สิ้นสุด

ก่อนที่จะลบ "รอยรั่ว" ออก ควรทำความเข้าใจว่าทำไมจึงปรากฏตั้งแต่แรก เพื่อที่จะทำสิ่งนี้ เรามาจำไว้ว่า Realm คืออะไร

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

(สำหรับวัตถุประสงค์ของบทความนี้ คำอธิบายนี้จะเพียงพอสำหรับเรา คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ Realm ได้อย่างยอดเยี่ยม เอกสาร หรือในพวกเขา สถาบันการศึกษา).

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

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

ข้อมูลรั่วไหลโดยไม่มีการลบแบบเรียงซ้อน

ข้อมูลจะรั่วไหลได้อย่างไรหากคุณอาศัยการลบแบบเรียงซ้อนที่ไม่มีอยู่จริง หากคุณมีอ็อบเจ็กต์ Realm ที่ซ้อนกัน จะต้องถูกลบออก
ลองดูตัวอย่างจริง (เกือบ) กัน เรามีวัตถุ CartItemEntity:

@RealmClass
class CartItemEntity(
 @PrimaryKey
 override var id: String? = null,
 ...
 var name: String = "",
 var description: String = "",
 var image: ImageEntity? = null,
 var category: String = MENU_CATEGORY_UNKNOWN_ID,
 var customizationEntity: CustomizationEntity? = null,
 var cartComboProducts: RealmList<CartProductEntity> = RealmList(),
 ...
) : RealmObject()

สินค้าในตะกร้ามีหลายช่องรวมทั้งรูปภาพด้วย ImageEntity, ส่วนผสมที่ปรับแต่งเอง CustomizationEntity. นอกจากนี้ สินค้าในรถเข็นสามารถเป็นสินค้ารวมกับชุดสินค้าของตัวเองได้ RealmList (CartProductEntity). ช่องที่แสดงทั้งหมดเป็นอ็อบเจ็กต์ Realm หากเราแทรกวัตถุใหม่ (copyToRealm() / copyToRealmOrUpdate()) ด้วยรหัสเดียวกัน วัตถุนี้จะถูกเขียนทับทั้งหมด แต่ออบเจ็กต์ภายในทั้งหมด (รูปภาพ, customsCustomizationEntity และ cartComboProducts) จะสูญเสียการเชื่อมต่อกับพาเรนต์และยังคงอยู่ในฐานข้อมูล

เนื่องจากการเชื่อมต่อกับพวกเขาขาดหายไป เราจึงไม่อ่านหรือลบพวกเขาอีกต่อไป (เว้นแต่เราจะเข้าถึงพวกเขาอย่างชัดเจนหรือล้าง "ตาราง") ทั้งหมด เราเรียกสิ่งนี้ว่า "หน่วยความจำรั่ว"

เมื่อเราทำงานร่วมกับ Realm เราต้องผ่านองค์ประกอบทั้งหมดอย่างชัดเจนและลบทุกอย่างก่อนการดำเนินการดังกล่าวอย่างชัดเจน ซึ่งสามารถทำได้เช่นนี้:

val entity = realm.where(CartItemEntity::class.java).equalTo("id", id).findFirst()
if (first != null) {
 deleteFromRealm(first.image)
 deleteFromRealm(first.customizationEntity)
 for(cartProductEntity in first.cartComboProducts) {
   deleteFromRealm(cartProductEntity)
 }
 first.deleteFromRealm()
}
// и потом уже сохраняем

หากคุณทำเช่นนี้ทุกอย่างจะทำงานได้ตามที่ควร ในตัวอย่างนี้ เราถือว่าไม่มีอ็อบเจ็กต์ Realm ที่ซ้อนกันอยู่ภายในรูปภาพ, customsCustomizationEntity และ cartComboProducts ดังนั้นจึงไม่มีการวนซ้ำและการลบแบบซ้อนอื่นๆ

โซลูชัน "ด่วน"

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

interface NestedEntityAware {
 fun getNestedEntities(): Collection<RealmObject?>
}

และเราได้นำไปใช้ในวัตถุอาณาจักรของเรา:

@RealmClass
class DataPizzeriaEntity(
 @PrimaryKey
 var id: String? = null,
 var name: String? = null,
 var coordinates: CoordinatesEntity? = null,
 var deliverySchedule: ScheduleEntity? = null,
 var restaurantSchedule: ScheduleEntity? = null,
 ...
) : RealmObject(), NestedEntityAware {

 override fun getNestedEntities(): Collection<RealmObject?> {
   return listOf(
       coordinates,
       deliverySchedule,
       restaurantSchedule
   )
 }
}

В getNestedEntities เราจะส่งคืนรายการย่อยทั้งหมดเป็นรายการแบบแบน และแต่ละอ็อบเจ็กต์ลูกยังสามารถใช้อินเทอร์เฟซ NestedEntityAware ได้ ซึ่งบ่งชี้ว่ามีอ็อบเจ็กต์ Realm ภายในที่จะลบ เป็นต้น ScheduleEntity:

@RealmClass
class ScheduleEntity(
 var monday: DayOfWeekEntity? = null,
 var tuesday: DayOfWeekEntity? = null,
 var wednesday: DayOfWeekEntity? = null,
 var thursday: DayOfWeekEntity? = null,
 var friday: DayOfWeekEntity? = null,
 var saturday: DayOfWeekEntity? = null,
 var sunday: DayOfWeekEntity? = null
) : RealmObject(), NestedEntityAware {

 override fun getNestedEntities(): Collection<RealmObject?> {
   return listOf(
       monday, tuesday, wednesday, thursday, friday, saturday, sunday
   )
 }
}

และอื่น ๆ การวางซ้อนของวัตถุสามารถทำซ้ำได้

จากนั้นเราจะเขียนวิธีการที่จะลบวัตถุที่ซ้อนกันทั้งหมดแบบวนซ้ำ วิธีการ (ทำเป็นส่วนขยาย) deleteAllNestedEntities รับวัตถุและวิธีการระดับบนสุดทั้งหมด deleteNestedRecursively ลบวัตถุที่ซ้อนกันทั้งหมดซ้ำโดยใช้อินเทอร์เฟซ NestedEntityAware:

fun <T> Realm.deleteAllNestedEntities(entities: Collection<T>,
 entityClass: Class<out RealmObject>,
 idMapper: (T) -> String,
 idFieldName : String = "id"
 ) {

 val existedObjects = where(entityClass)
     .`in`(idFieldName, entities.map(idMapper).toTypedArray())
     .findAll()

 deleteNestedRecursively(existedObjects)
}

private fun Realm.deleteNestedRecursively(entities: Collection<RealmObject?>) {
 for(entity in entities) {
   entity?.let { realmObject ->
     if (realmObject is NestedEntityAware) {
       deleteNestedRecursively((realmObject as NestedEntityAware).getNestedEntities())
     }
     realmObject.deleteFromRealm()
   }
 }
}

เราทำสิ่งนี้กับวัตถุที่เติบโตเร็วที่สุดและตรวจสอบว่าเกิดอะไรขึ้น

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

เป็นผลให้วัตถุเหล่านั้นที่เรากล่าวถึงในโซลูชันนี้หยุดการเติบโต และการเติบโตโดยรวมของฐานก็ชะลอตัวลงแต่ก็ไม่หยุด

วิธีแก้ปัญหา "ปกติ"

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

ฉันต้องการให้แน่ใจว่าฉันไม่ได้ใช้อินเทอร์เฟซ แต่ทุกอย่างทำงานได้ด้วยตัวเอง

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

RealmModel::class.java.isAssignableFrom(field.type)

RealmList::class.java.isAssignableFrom(field.type)

หากฟิลด์เป็น RealmModel หรือ RealmList ให้เพิ่มออบเจ็กต์ของฟิลด์นี้ไปยังรายการออบเจ็กต์ที่ซ้อนกัน ทุกอย่างเหมือนกับที่เราทำข้างต้นทุกประการ เฉพาะที่นี่เท่านั้นที่จะดำเนินการได้ด้วยตัวเอง วิธีการลบแบบเรียงซ้อนนั้นง่ายมากและมีลักษณะดังนี้:

fun <T : Any> Realm.cascadeDelete(entities: Collection<T?>) {
 if(entities.isEmpty()) {
   return
 }

 entities.filterNotNull().let { notNullEntities ->
   notNullEntities
       .filterRealmObject()
       .flatMap { realmObject -> getNestedRealmObjects(realmObject) }
       .also { realmObjects -> cascadeDelete(realmObjects) }

   notNullEntities
       .forEach { entity ->
         if((entity is RealmObject) && entity.isValid) {
           entity.deleteFromRealm()
         }
       }
 }
}

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

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

การใช้งานเมธอด getNestedRealmObjects อย่างสมบูรณ์

private fun getNestedRealmObjects(realmObject: RealmObject) : List<RealmObject> {
 val nestedObjects = mutableListOf<RealmObject>()
 val fields = realmObject.javaClass.superclass.declaredFields

// Проверяем каждое поле, не является ли оно RealmModel или списком RealmList
 fields.forEach { field ->
   when {
     RealmModel::class.java.isAssignableFrom(field.type) -> {
       try {
         val child = getChildObjectByField(realmObject, field)
         child?.let {
           if (isInstanceOfRealmObject(it)) {
             nestedObjects.add(child as RealmObject)
           }
         }
       } catch (e: Exception) { ... }
     }

     RealmList::class.java.isAssignableFrom(field.type) -> {
       try {
         val childList = getChildObjectByField(realmObject, field)
         childList?.let { list ->
           (list as RealmList<*>).forEach {
             if (isInstanceOfRealmObject(it)) {
               nestedObjects.add(it as RealmObject)
             }
           }
         }
       } catch (e: Exception) { ... }
     }
   }
 }

 return nestedObjects
}

private fun getChildObjectByField(realmObject: RealmObject, field: Field): Any? {
 val methodName = "get${field.name.capitalize()}"
 val method = realmObject.javaClass.getMethod(methodName)
 return method.invoke(realmObject)
}

ด้วยเหตุนี้ ในโค้ดลูกค้าของเรา เราใช้ "การลบแบบเรียงซ้อน" สำหรับการดำเนินการแก้ไขข้อมูลแต่ละครั้ง ตัวอย่างเช่น สำหรับการดำเนินการแทรก จะมีลักษณะดังนี้:

override fun <T : Entity> insert(
 entityInformation: EntityInformation,
 entities: Collection<T>): Collection<T> = entities.apply {
 realmInstance.cascadeDelete(getManagedEntities(entityInformation, this))
 realmInstance.copyFromRealm(
     realmInstance
         .copyToRealmOrUpdate(this.map { entity -> entity as RealmModel }
 ))
}

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

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

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

ผลลัพธ์และข้อสรุป

ฐานข้อมูล Realm ที่เติบโตอย่างต่อเนื่องทำให้แอปพลิเคชันเปิดช้ามาก เราเผยแพร่การอัปเดตด้วย "การลบแบบเรียงซ้อน" ของออบเจ็กต์ที่ซ้อนกันของเราเอง และตอนนี้เราติดตามและประเมินว่าการตัดสินใจของเราส่งผลต่อเวลาเริ่มต้นแอปพลิเคชันอย่างไรผ่านตัววัด _app_start

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

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

เรื่องราวของการที่การลบแบบเรียงซ้อนใน Realm ได้รับชัยชนะเหนือการเปิดตัวอันยาวนาน

หากคุณดูแผนภูมิเจ็ดวัน เมตริก _app_start ดูเพียงพอโดยสมบูรณ์และใช้เวลาน้อยกว่า 1 วินาที

นอกจากนี้ยังควรเพิ่มว่าตามค่าเริ่มต้น Firebase จะส่งการแจ้งเตือนหากค่ามัธยฐานของ _app_start เกิน 5 วินาที อย่างไรก็ตาม ตามที่เราเห็น คุณไม่ควรพึ่งพาสิ่งนี้ แต่ควรเข้าไปตรวจสอบอย่างชัดเจน

สิ่งพิเศษเกี่ยวกับฐานข้อมูล Realm คือเป็นฐานข้อมูลที่ไม่เกี่ยวข้อง แม้จะใช้งานง่าย แต่ก็คล้ายคลึงกับโซลูชัน ORM และการลิงก์อ็อบเจ็กต์ แต่ก็ไม่มีการลบแบบเรียงซ้อน

หากไม่คำนึงถึงสิ่งนี้ วัตถุที่ซ้อนกันจะสะสมและ "รั่วไหลออกไป" ฐานข้อมูลจะเติบโตอย่างต่อเนื่อง ซึ่งจะส่งผลต่อการชะลอตัวหรือการเริ่มทำงานของแอปพลิเคชัน

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

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

ที่มา: will.com

เพิ่มความคิดเห็น