ผู้ใช้ทุกคนยอมรับการเปิดใช้ UI ที่รวดเร็วและตอบสนองในแอปพลิเคชันมือถือ หากแอปพลิเคชันใช้เวลานานในการเปิดใช้งาน ผู้ใช้จะเริ่มรู้สึกเศร้าและโกรธ คุณสามารถทำให้ประสบการณ์ของลูกค้าเสียไปได้อย่างง่ายดายหรือสูญเสียผู้ใช้ไปโดยสิ้นเชิงแม้กระทั่งก่อนที่เขาจะเริ่มใช้แอปพลิเคชัน
ครั้งหนึ่งเราค้นพบว่าแอป Dodo Pizza ใช้เวลาเปิดตัวโดยเฉลี่ย 3 วินาที และสำหรับ “ผู้โชคดี” บางคนอาจใช้เวลา 15-20 วินาที
ด้านล่างของการตัดเป็นเรื่องราวที่จบลงอย่างมีความสุข: เกี่ยวกับการเติบโตของฐานข้อมูล Realm, หน่วยความจำรั่ว, การที่เราสะสมวัตถุที่ซ้อนกันได้อย่างไร จากนั้นจึงรวมตัวกันและแก้ไขทุกอย่าง
ผู้เขียนบทความ:แม็กซิม คาชินคิน — นักพัฒนา Android ที่ Dodo Pizza
สามวินาทีจากการคลิกที่ไอคอนแอปพลิเคชันไปจนถึง onResume() ของกิจกรรมแรกนั้นไม่มีที่สิ้นสุด และสำหรับผู้ใช้บางราย เวลาเริ่มต้นถึง 15-20 วินาที สิ่งนี้เป็นไปได้อย่างไร?
สรุปสั้นๆ สำหรับคนไม่มีเวลาอ่าน
ฐานข้อมูลอาณาจักรของเราเติบโตขึ้นอย่างไม่มีที่สิ้นสุด วัตถุที่ซ้อนกันบางรายการไม่ได้ถูกลบ แต่ถูกสะสมอย่างต่อเนื่อง เวลาเริ่มต้นแอปพลิเคชันค่อยๆ เพิ่มขึ้น จากนั้นเราแก้ไขมัน และเวลาเริ่มต้นก็มาถึงเป้าหมาย - มันเหลือน้อยกว่า 1 วินาทีและไม่เพิ่มขึ้นอีกต่อไป บทความนี้ประกอบด้วยการวิเคราะห์สถานการณ์และวิธีแก้ปัญหาสองประการ - วิธีที่รวดเร็วและวิธีปกติ
ค้นหาและวิเคราะห์ปัญหา
ในปัจจุบัน แอปพลิเคชันบนมือถือใดๆ ก็ตามจะต้องเปิดตัวอย่างรวดเร็วและตอบสนองได้ดี แต่ไม่ใช่แค่แอปบนอุปกรณ์เคลื่อนที่เท่านั้น ประสบการณ์ผู้ใช้ในการโต้ตอบกับบริการและบริษัทเป็นสิ่งที่ซับซ้อน ตัวอย่างเช่น ในกรณีของเรา ความเร็วในการส่งเป็นหนึ่งในตัวบ่งชี้สำคัญสำหรับบริการพิซซ่า ถ้าส่งเร็วพิซซ่าจะร้อน ลูกค้าที่อยากกินตอนนี้ไม่ต้องรอนาน ในทางกลับกัน แอปพลิเคชันต้องสร้างความรู้สึกถึงการบริการที่รวดเร็ว เพราะหากแอปพลิเคชันใช้เวลาเปิดเพียง 20 วินาที คุณจะต้องรอพิซซ่านานเท่าใด
ในตอนแรกเราเองต้องเผชิญกับความจริงที่ว่าบางครั้งแอปพลิเคชันใช้เวลาสองสามวินาทีในการเปิดตัว จากนั้นเราก็เริ่มได้ยินคำร้องเรียนจากเพื่อนร่วมงานคนอื่นเกี่ยวกับระยะเวลาที่มันใช้เวลานาน แต่เราไม่สามารถทำซ้ำสถานการณ์นี้อย่างต่อเนื่องได้
นานแค่ไหน? ตาม
แต่แล้วการร้องเรียนก็เริ่มปรากฏว่าแอปพลิเคชันใช้เวลานานมากในการเปิดตัว! ประการแรก เราตัดสินใจวัดว่า "ยาวมาก ยาวมาก" คืออะไร และเราใช้การติดตาม Firebase สำหรับสิ่งนี้
การติดตามมาตรฐานนี้จะวัดเวลาระหว่างช่วงเวลาที่ผู้ใช้เปิดแอปพลิเคชันและช่วงเวลาที่ onResume() ของกิจกรรมแรกถูกดำเนินการ ในคอนโซล Firebase เมตริกนี้เรียกว่า _app_start ปรากฎว่า:
- เวลาเริ่มต้นสำหรับผู้ใช้ที่อยู่เหนือเปอร์เซ็นไทล์ที่ 95 คือเกือบ 20 วินาที (บางครั้งอาจนานกว่านั้น) แม้ว่าค่ามัธยฐานของเวลาเริ่มต้นขณะเย็นจะน้อยกว่า 5 วินาทีก็ตาม
- เวลาเริ่มต้นไม่ใช่ค่าคงที่ แต่เพิ่มขึ้นเมื่อเวลาผ่านไป แต่บางครั้งก็มีหยด เราพบรูปแบบนี้เมื่อเราเพิ่มระดับการวิเคราะห์เป็น 90 วัน
ความคิดสองประการเข้ามาในใจ:
- มีบางอย่างกำลังรั่วไหล
- “บางสิ่ง” นี้จะถูกรีเซ็ตหลังจากปล่อยแล้วรั่วไหลอีกครั้ง
“อาจมีบางอย่างในฐานข้อมูล” เราคิด และเราคิดถูก ประการแรก เราใช้ฐานข้อมูลเป็นแคช ในระหว่างการย้ายข้อมูล เราจะล้างมัน ประการที่สอง ฐานข้อมูลจะถูกโหลดเมื่อแอปพลิเคชันเริ่มทำงาน ทุกอย่างเข้ากัน
เกิดอะไรขึ้นกับฐานข้อมูล Realm
เราเริ่มตรวจสอบว่าเนื้อหาของฐานข้อมูลเปลี่ยนแปลงไปอย่างไรตลอดอายุของแอปพลิเคชัน ตั้งแต่การติดตั้งครั้งแรกและต่อไปในระหว่างการใช้งาน คุณสามารถดูเนื้อหาของฐานข้อมูล Realm ผ่านทาง
adb exec-out run-as ${PACKAGE_NAME} cat files/${DB_NAME}
เมื่อดูเนื้อหาของฐานข้อมูลในเวลาที่ต่างกัน เราพบว่าจำนวนออบเจ็กต์บางประเภทเพิ่มขึ้นอย่างต่อเนื่อง
รูปภาพแสดงส่วนของ 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
คุณสามารถทำให้การรวบรวมเมตริกนี้เป็นแบบอัตโนมัติและดูผลลัพธ์ด้วยสายตาได้ กราฟด้านล่างแสดงการขึ้นต่อกันของเวลาเริ่มต้นแอปพลิเคชันกับจำนวนการเริ่มเย็นของแอปพลิเคชัน
ในขณะเดียวกัน ก็มีความสัมพันธ์ในลักษณะเดียวกันระหว่างขนาดและการเติบโตของฐานข้อมูล ซึ่งเพิ่มขึ้นจาก 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 หรือรายการของอ็อบเจ็กต์:
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
เนื่องจากอาจเป็นได้ว่าออบเจ็กต์พาเรนต์ที่แตกต่างกันสามารถมีออบเจ็กต์ที่เหมือนกันซ้อนกันได้ เป็นการดีกว่าที่จะหลีกเลี่ยงสิ่งนี้และใช้การสร้างรหัสอัตโนมัติเมื่อสร้างวัตถุใหม่
การใช้งานเมธอด 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 ที่เติบโตอย่างต่อเนื่องทำให้แอปพลิเคชันเปิดช้ามาก เราเผยแพร่การอัปเดตด้วย "การลบแบบเรียงซ้อน" ของออบเจ็กต์ที่ซ้อนกันของเราเอง และตอนนี้เราติดตามและประเมินว่าการตัดสินใจของเราส่งผลต่อเวลาเริ่มต้นแอปพลิเคชันอย่างไรผ่านตัววัด _app_start
สำหรับการวิเคราะห์ เราใช้เวลา 90 วันและดูว่าเวลาเปิดตัวแอปพลิเคชัน ทั้งค่ามัธยฐานและที่ตรงกับเปอร์เซ็นไทล์ที่ 95 ของผู้ใช้ เริ่มลดลงและไม่เพิ่มขึ้นอีกต่อไป
หากคุณดูแผนภูมิเจ็ดวัน เมตริก _app_start ดูเพียงพอโดยสมบูรณ์และใช้เวลาน้อยกว่า 1 วินาที
นอกจากนี้ยังควรเพิ่มว่าตามค่าเริ่มต้น Firebase จะส่งการแจ้งเตือนหากค่ามัธยฐานของ _app_start เกิน 5 วินาที อย่างไรก็ตาม ตามที่เราเห็น คุณไม่ควรพึ่งพาสิ่งนี้ แต่ควรเข้าไปตรวจสอบอย่างชัดเจน
สิ่งพิเศษเกี่ยวกับฐานข้อมูล Realm คือเป็นฐานข้อมูลที่ไม่เกี่ยวข้อง แม้จะใช้งานง่าย แต่ก็คล้ายคลึงกับโซลูชัน ORM และการลิงก์อ็อบเจ็กต์ แต่ก็ไม่มีการลบแบบเรียงซ้อน
หากไม่คำนึงถึงสิ่งนี้ วัตถุที่ซ้อนกันจะสะสมและ "รั่วไหลออกไป" ฐานข้อมูลจะเติบโตอย่างต่อเนื่อง ซึ่งจะส่งผลต่อการชะลอตัวหรือการเริ่มทำงานของแอปพลิเคชัน
ฉันแบ่งปันประสบการณ์ของเราเกี่ยวกับวิธีลบออบเจ็กต์แบบเรียงซ้อนใน Realm อย่างรวดเร็วซึ่งยังไม่ได้แกะกล่อง แต่ได้รับการพูดคุยกันมานานแล้ว
แม้จะมีการอภิปรายเกี่ยวกับรูปลักษณ์ที่ใกล้จะเกิดขึ้นของคุณลักษณะนี้ แต่การลบแบบเรียงซ้อนใน Realm ก็ทำได้โดยการออกแบบ หากคุณกำลังออกแบบแอปพลิเคชันใหม่ ให้คำนึงถึงสิ่งนี้ด้วย และหากคุณใช้ Realm อยู่แล้ว ให้ตรวจสอบว่าคุณประสบปัญหาดังกล่าวหรือไม่
ที่มา: will.com