اس کی کہانی کہ کس طرح دائرے میں جھرنوں کو حذف کرنا ایک طویل لانچ پر جیت گیا۔

تمام صارفین موبائل ایپلیکیشنز میں تیز رفتار لانچ اور ریسپانسیو UI کو قدر کی نگاہ سے دیکھتے ہیں۔ اگر ایپلی کیشن کو لانچ ہونے میں زیادہ وقت لگتا ہے تو صارف اداس اور غصے میں آنے لگتا ہے۔ آپ صارف کے تجربے کو آسانی سے خراب کر سکتے ہیں یا صارف کے ایپلیکیشن کا استعمال شروع کرنے سے پہلے ہی اسے مکمل طور پر کھو سکتے ہیں۔

ہم نے ایک بار دریافت کیا کہ ڈوڈو پیزا ایپ کو لانچ ہونے میں اوسطاً 3 سیکنڈ لگتے ہیں، اور کچھ "خوش نصیبوں" کے لیے اس میں 15-20 سیکنڈ لگتے ہیں۔

کٹ کے نیچے ایک خوش کن اختتام کے ساتھ ایک کہانی ہے: Realm ڈیٹا بیس کی ترقی کے بارے میں، ایک میموری لیک، کس طرح ہم نے نیسٹڈ اشیاء کو جمع کیا، اور پھر خود کو اکٹھا کیا اور سب کچھ ٹھیک کیا۔

اس کی کہانی کہ کس طرح دائرے میں جھرنوں کو حذف کرنا ایک طویل لانچ پر جیت گیا۔

اس کی کہانی کہ کس طرح دائرے میں جھرنوں کو حذف کرنا ایک طویل لانچ پر جیت گیا۔
مضمون کا مصنف: میکسم کاچینکن - ڈوڈو پیزا میں اینڈرائیڈ ڈویلپر۔

ایپلیکیشن آئیکون پر کلک کرنے سے لے کر پہلی سرگرمی کے onResume() تک تین سیکنڈ انفینٹی ہے۔ اور کچھ صارفین کے لیے، آغاز کا وقت 15-20 سیکنڈ تک پہنچ گیا۔ یہ بھی کیسے ممکن ہے؟

جن کے پاس پڑھنے کا وقت نہیں ہے ان کے لیے ایک بہت ہی مختصر خلاصہ
ہمارا دائرہ ڈیٹا بیس لامتناہی طور پر بڑھا۔ کچھ گھریلو اشیاء کو حذف نہیں کیا گیا تھا، لیکن مسلسل جمع کیا گیا تھا. ایپلیکیشن شروع ہونے کا وقت آہستہ آہستہ بڑھتا گیا۔ پھر ہم نے اسے ٹھیک کر دیا، اور سٹارٹ اپ کا وقت ہدف پر آ گیا - یہ 1 سیکنڈ سے کم ہو گیا اور مزید اضافہ نہیں ہوا۔ مضمون میں صورت حال کا تجزیہ اور دو حل ہیں - ایک فوری اور ایک عام۔

مسئلہ کی تلاش اور تجزیہ

آج، کسی بھی موبائل ایپلیکیشن کو تیزی سے لانچ کرنا اور جوابدہ ہونا چاہیے۔ لیکن یہ صرف موبائل ایپ کے بارے میں نہیں ہے۔ کسی سروس اور کمپنی کے ساتھ تعامل کا صارف کا تجربہ ایک پیچیدہ چیز ہے۔ مثال کے طور پر، ہمارے معاملے میں، ڈیلیوری کی رفتار پیزا سروس کے کلیدی اشارے میں سے ایک ہے۔ اگر ڈیلیوری تیز ہو تو پیزا گرم ہو جائے گا، اور جو گاہک ابھی کھانا چاہتا ہے اسے زیادہ انتظار نہیں کرنا پڑے گا۔ ایپلی کیشن کے لیے، بدلے میں، تیز سروس کا احساس پیدا کرنا ضروری ہے، کیونکہ اگر ایپلی کیشن کو لانچ ہونے میں صرف 20 سیکنڈ لگتے ہیں، تو آپ کو پیزا کے لیے کتنا انتظار کرنا پڑے گا؟

پہلے تو ہمیں خود اس حقیقت کا سامنا کرنا پڑا کہ بعض اوقات ایپلیکیشن کو لانچ ہونے میں چند سیکنڈ لگتے تھے، اور پھر ہمیں دوسرے ساتھیوں سے شکایات سننے لگیں کہ اس میں کتنا وقت لگتا ہے۔ لیکن ہم اس صورت حال کو مسلسل دہرانے سے قاصر تھے۔

یہ کتنا لمبا ہے؟ کے مطابق گوگل دستاویزات، اگر کسی ایپلیکیشن کے کولڈ اسٹارٹ میں 5 سیکنڈ سے کم وقت لگتا ہے، تو اسے "گویا نارمل" سمجھا جاتا ہے۔ ڈوڈو پیزا اینڈرائیڈ ایپ لانچ کی گئی (فائر بیس میٹرکس کے مطابق _app_start) پر سرد آغاز اوسطا 3 سیکنڈ میں - "بہت اچھا نہیں، خوفناک نہیں،" جیسا کہ وہ کہتے ہیں۔

لیکن پھر شکایات ظاہر ہونے لگیں کہ ایپلی کیشن کو لانچ ہونے میں بہت، بہت، بہت طویل وقت لگا! شروع کرنے کے لیے، ہم نے فیصلہ کیا کہ "بہت، بہت، بہت لمبا" کیا ہے۔ اور ہم نے اس کے لیے Firebase ٹریس کا استعمال کیا۔ ایپ اسٹارٹ ٹریس.

اس کی کہانی کہ کس طرح دائرے میں جھرنوں کو حذف کرنا ایک طویل لانچ پر جیت گیا۔

یہ معیاری ٹریس صارف کے ایپلیکیشن کو کھولنے اور پہلی سرگرمی کے onResume() کے عمل میں آنے کے درمیان کے وقت کی پیمائش کرتا ہے۔ Firebase کنسول میں اس میٹرک کو _app_start کہا جاتا ہے۔ معلوم ہوا کہ:

  • 95 ویں فیصد سے اوپر کے صارفین کے لیے آغاز کا وقت تقریباً 20 سیکنڈ (کچھ اس سے بھی زیادہ) ہے، اس کے باوجود کہ اوسط کولڈ اسٹارٹ اپ کا وقت 5 سیکنڈ سے کم ہے۔
  • آغاز کا وقت ایک مستقل قدر نہیں ہے، لیکن وقت کے ساتھ بڑھتا ہے۔ لیکن کبھی کبھی قطرے ہوتے ہیں۔ ہمیں یہ نمونہ اس وقت ملا جب ہم نے تجزیہ کے پیمانے کو 90 دن تک بڑھا دیا۔

اس کی کہانی کہ کس طرح دائرے میں جھرنوں کو حذف کرنا ایک طویل لانچ پر جیت گیا۔

ذہن میں دو خیالات آئے:

  1. کچھ لیک ہو رہا ہے۔
  2. یہ "کچھ" ریلیز کے بعد دوبارہ ترتیب دیا جاتا ہے اور پھر دوبارہ لیک ہوجاتا ہے۔

"شاید ڈیٹا بیس کے ساتھ کچھ،" ہم نے سوچا، اور ہم صحیح تھے۔ سب سے پہلے، ہم ڈیٹا بیس کو کیش کے طور پر استعمال کرتے ہیں؛ منتقلی کے دوران ہم اسے صاف کرتے ہیں۔ دوم، ایپلیکیشن شروع ہونے پر ڈیٹا بیس لوڈ ہو جاتا ہے۔ سب کچھ ایک ساتھ فٹ بیٹھتا ہے۔

Realm ڈیٹا بیس میں کیا خرابی ہے۔

ہم نے جانچنا شروع کیا کہ ڈیٹا بیس کے مواد پہلی تنصیب سے لے کر اور پھر فعال استعمال کے دوران ایپلیکیشن کی زندگی میں کس طرح تبدیل ہوتے ہیں۔ آپ Realm ڈیٹا بیس کے مواد کو بذریعہ دیکھ سکتے ہیں۔ اسٹیتھو یا مزید تفصیل سے اور واضح طور پر فائل کو کھول کر دائرہ اسٹوڈیو. ADB کے ذریعے ڈیٹا بیس کے مواد کو دیکھنے کے لیے، Realm ڈیٹا بیس فائل کو کاپی کریں:

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

مختلف اوقات میں ڈیٹا بیس کے مواد کو دیکھنے کے بعد، ہمیں پتہ چلا کہ ایک خاص قسم کی اشیاء کی تعداد میں مسلسل اضافہ ہو رہا ہے۔

اس کی کہانی کہ کس طرح دائرے میں جھرنوں کو حذف کرنا ایک طویل لانچ پر جیت گیا۔
تصویر میں دو فائلوں کے لیے Realm سٹوڈیو کا ایک ٹکڑا دکھایا گیا ہے: بائیں جانب - ایپلیکیشن کی بنیاد انسٹالیشن کے کچھ دیر بعد، دائیں جانب - فعال استعمال کے بعد۔ یہ دیکھا جا سکتا ہے کہ اشیاء کی تعداد ImageEntity и MoneyType نمایاں طور پر اضافہ ہوا ہے (اسکرین شاٹ ہر قسم کی اشیاء کی تعداد کو ظاہر کرتا ہے)۔

ڈیٹا بیس کی ترقی اور آغاز کے وقت کے درمیان تعلق

ڈیٹا بیس کی بے قابو ترقی بہت خراب ہے۔ لیکن یہ ایپلی کیشن کے آغاز کے وقت کو کیسے متاثر کرتا ہے؟ ActivityManager کے ذریعے اس کی پیمائش کرنا کافی آسان ہے۔ اینڈرائیڈ 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 تک پہنچ گئی۔ مجموعی طور پر، یہ پتہ چلتا ہے کہ وقت کے ساتھ (سردی شروع ہونے کی ترقی کے ساتھ)، ایپلی کیشن کے آغاز کا وقت اور ڈیٹا بیس کا سائز دونوں میں اضافہ ہوا. ہمارے ہاتھ پر ایک مفروضہ ہے۔ اب صرف انحصار کی تصدیق کرنا باقی تھا۔ لہذا، ہم نے "لیکس" کو ہٹانے کا فیصلہ کیا اور دیکھیں کہ آیا اس سے لانچ کی رفتار تیز ہو جائے گی۔

لامتناہی ڈیٹا بیس کی ترقی کی وجوہات

"لیک" کو ہٹانے سے پہلے، یہ سمجھنے کے قابل ہے کہ وہ پہلی جگہ میں کیوں ظاہر ہوئے. ایسا کرنے کے لیے، آئیے یاد رکھیں کہ دائرہ کیا ہے۔

دائرہ ایک غیر متعلقہ ڈیٹا بیس ہے۔ یہ آپ کو اشیاء کے درمیان تعلقات کو اسی طرح بیان کرنے کی اجازت دیتا ہے جس طرح Android پر کتنے ORM رشتہ دار ڈیٹا بیس بیان کیے گئے ہیں۔ ایک ہی وقت میں، Realm کم سے کم تبدیلیوں اور نقشوں کے ساتھ اشیاء کو براہ راست میموری میں اسٹور کرتا ہے۔ یہ آپ کو ڈسک سے ڈیٹا کو بہت تیزی سے پڑھنے کی اجازت دیتا ہے، جو کہ Realm کی طاقت ہے اور اسے کیوں پسند کیا جاتا ہے۔

(اس مضمون کے مقاصد کے لیے یہ تفصیل ہمارے لیے کافی ہوگی۔ آپ ریئلم کے بارے میں مزید پڑھ سکتے ہیں دستاویزات یا ان میں اکیڈمی).

بہت سے ڈویلپرز رشتہ دار ڈیٹا بیس کے ساتھ زیادہ کام کرنے کے عادی ہیں (مثال کے طور پر، ہڈ کے نیچے SQL کے ساتھ ORM ڈیٹا بیس)۔ اور کاسکیڈنگ ڈیٹا ڈیلیٹ کرنے جیسی چیزیں اکثر دی گئی لگتی ہیں۔ لیکن دائرے میں نہیں۔

ویسے، کاسکیڈ ڈیلیٹ کرنے کا فیچر کافی عرصے سے پوچھا جا رہا ہے۔ یہ نظر ثانی и دوسرااس کے ساتھ منسلک، فعال طور پر بات چیت کی گئی تھی. ایک احساس تھا کہ یہ جلد ہی ہو جائے گا۔ لیکن پھر سب کچھ مضبوط اور کمزور روابط کے تعارف میں ترجمہ ہوا، جو خود بخود اس مسئلہ کو حل کرے گا. اس کام میں کافی جاندار اور سرگرم تھے۔ کھینچنے کی درخواستجسے اندرونی مشکلات کی وجہ سے فی الحال روک دیا گیا ہے۔

بغیر جھڑپوں کو حذف کیے ڈیٹا کا لیک ہونا

اگر آپ غیر موجود جھرن کو حذف کرنے پر بھروسہ کرتے ہیں تو ڈیٹا کیسے لیک ہوتا ہے؟ اگر آپ نے ریئلم آبجیکٹ کو نیسٹ کیا ہے تو انہیں ڈیلیٹ کرنا ہوگا۔
آئیے ایک (تقریبا) حقیقی مثال دیکھیں۔ ہمارے پاس ایک اعتراض ہے۔ 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()) داخل کرتے ہیں تو یہ آبجیکٹ مکمل طور پر اوور رائٹ ہوجائے گا۔ لیکن تمام اندرونی اشیاء (تصویر، کسٹمائزیشن اینٹیٹی اور کارٹ کامبو پروڈکٹس) والدین سے تعلق کھو دیں گے اور ڈیٹا بیس میں رہیں گے۔

چونکہ ان کے ساتھ رابطہ منقطع ہو گیا ہے، اس لیے ہم انہیں مزید نہیں پڑھتے اور نہ ہی حذف کرتے ہیں (جب تک کہ ہم واضح طور پر ان تک رسائی حاصل نہ کر لیں یا پوری "ٹیبل" کو صاف نہ کر دیں)۔ ہم نے اسے "میموری لیک" کہا۔

جب ہم 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 اشیاء کی فہرست واپس کی:

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 انٹرفیس کو بھی نافذ کر سکتا ہے، جس سے یہ ظاہر ہوتا ہے کہ اس میں حذف کرنے کے لیے اندرونی دائرے کی اشیاء موجود ہیں، مثال کے طور پر 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 آبجیکٹ ہے یا اشیاء کی فہرست:

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 ڈیٹا بیس ایپلی کیشن کو بہت آہستہ سے لانچ کرنے کا سبب بن رہا تھا۔ ہم نے نیسٹڈ آبجیکٹ کے اپنے "کاسکیڈنگ ڈیلیٹ" کے ساتھ ایک اپ ڈیٹ جاری کیا۔ اور اب ہم نگرانی اور جائزہ لیتے ہیں کہ ہمارے فیصلے نے _app_start میٹرک کے ذریعے ایپلیکیشن لانچ کے وقت کو کیسے متاثر کیا۔

اس کی کہانی کہ کس طرح دائرے میں جھرنوں کو حذف کرنا ایک طویل لانچ پر جیت گیا۔

تجزیہ کے لیے، ہم 90 دن کا وقت لیتے ہیں اور دیکھتے ہیں: ایپلیکیشن لانچ ہونے کا وقت، درمیانی اور وہ جو صارفین کے 95ویں فیصد پر آتا ہے، کم ہونا شروع ہوا اور اب بڑھتا نہیں ہے۔

اس کی کہانی کہ کس طرح دائرے میں جھرنوں کو حذف کرنا ایک طویل لانچ پر جیت گیا۔

اگر آپ سات دن کے چارٹ کو دیکھیں تو _app_start میٹرک مکمل طور پر کافی ہے اور 1 سیکنڈ سے بھی کم ہے۔

یہ بھی شامل کرنے کے قابل ہے کہ ڈیفالٹ کے طور پر، اگر _app_start کی اوسط قدر 5 سیکنڈ سے زیادہ ہو تو Firebase اطلاعات بھیجتا ہے۔ تاہم، جیسا کہ ہم دیکھ سکتے ہیں، آپ کو اس پر بھروسہ نہیں کرنا چاہیے، بلکہ اندر جاکر اسے واضح طور پر چیک کریں۔

ریئلم ڈیٹا بیس کی خاص بات یہ ہے کہ یہ ایک غیر متعلقہ ڈیٹا بیس ہے۔ اس کے استعمال میں آسانی، ORM حل اور آبجیکٹ لنکنگ سے مماثلت کے باوجود، اس میں جھرنوں کو حذف نہیں کیا گیا ہے۔

اگر اس کو مدنظر نہیں رکھا جاتا ہے، تو گھریلو اشیاء جمع ہو جائیں گی اور "لیک ہو جائیں گی۔" ڈیٹا بیس مسلسل بڑھتا جائے گا، جس کے نتیجے میں ایپلیکیشن کی سست روی یا آغاز متاثر ہوگا۔

میں نے اپنے تجربے کا اشتراک کیا کہ کس طرح دائرے میں اشیاء کو فوری طور پر حذف کرنا ہے، جو ابھی تک باکس سے باہر نہیں ہے، لیکن اس کے بارے میں کافی عرصے سے بات کی جا رہی ہے۔ کہنا и کہنا. ہمارے معاملے میں، اس نے ایپلیکیشن کے آغاز کے وقت کو بہت تیز کر دیا۔

اس خصوصیت کے آنے والے ظہور کے بارے میں بحث کے باوجود، دائرے میں جھرن کو حذف کرنے کی غیر موجودگی ڈیزائن کے ذریعہ کی جاتی ہے۔ اگر آپ ایک نئی ایپلی کیشن ڈیزائن کر رہے ہیں، تو اس کو مدنظر رکھیں۔ اور اگر آپ پہلے سے ہی Realm استعمال کر رہے ہیں، تو چیک کریں کہ آیا آپ کو اس طرح کے مسائل درپیش ہیں۔

ماخذ: www.habr.com

نیا تبصرہ شامل کریں