ʻO ka moʻolelo o ka lanakila ʻana o ka cascade holoi ma Realm ma kahi hoʻomaka lōʻihi
Lawe nā mea hoʻohana a pau i ka hoʻomaka wikiwiki ʻana a me ka pane ʻana i ka UI ma nā polokalamu kelepona. Inā lōʻihi ka hoʻomaka ʻana o ka noi, hoʻomaka ka mea hoʻohana e kaumaha a huhū. Hiki iā ʻoe ke hao wale i ka ʻike o ka mea kūʻai aku a nalowale loa i ka mea hoʻohana ma mua o kona hoʻomaka ʻana e hoʻohana i ka noi.
Ua ʻike mua mākou ʻo ka Dodo Pizza app he 3 kekona e hoʻomaka ai ma ka awelika, a no kekahi mau "laki" he 15-20 kekona.
Aia ma lalo o ka ʻokiʻoki kahi moʻolelo me ka hopena hauʻoli: e pili ana i ka ulu ʻana o ka waihona Realm, kahi leak hoʻomanaʻo, pehea mākou i hōʻiliʻili ai i nā mea nested, a laila huki iā mākou iho a hoʻoponopono i nā mea āpau.
Mea kākau ʻatikala: Maxim Kachinkin — Mea hoʻomohala Android ma Dodo Pizza.
ʻEkolu kekona mai ke kaomi ʻana i ka ikona noi a i onResume() o ka hana mua he pau ʻole. A no kekahi mau mea hoʻohana, hiki i ka manawa hoʻomaka i 15-20 kekona. Pehea e hiki ai kēia?
He hōʻuluʻulu pōkole loa no ka poʻe ʻaʻohe manawa e heluhelu ai
Ua ulu ʻole kā mākou waihona waihona Realm. ʻAʻole i holoi ʻia kekahi mau mea pūnana, akā ua hōʻiliʻili mau ʻia. Ua piʻi mālie ka manawa hoʻomaka o ka noi. A laila hoʻoponopono mākou, a hiki i ka manawa hoʻomaka i ka pahuhopu - ua emi iho ma lalo o 1 kekona a ʻaʻole i hoʻonui hou. Aia ka ʻatikala i kahi loiloi o ke kūlana a me ʻelua mau hoʻonā - kahi wikiwiki a maʻamau.
Huli a nānā i ka pilikia
I kēia lā, pono e hoʻomaka koke nā polokalamu kelepona a pane. Akā ʻaʻole ia e pili ana i ka polokalamu kelepona. ʻO ka ʻike mea hoʻohana o ka launa pū ʻana me kahi lawelawe a me kahi hui he mea paʻakikī. No ka laʻana, i kā mākou hihia, ʻo ka wikiwiki ka lawe ʻana i kekahi o nā hōʻailona koʻikoʻi no ka lawelawe pizza. Inā wikiwiki ka lawe ʻana, wela ka pizza, a ʻo ka mea kūʻai makemake e ʻai i kēia manawa ʻaʻole e kali lōʻihi. No ka noi, he mea nui ia e hana i ka manaʻo o ka lawelawe wikiwiki, no ka mea inā he 20 kekona wale nō ka noi e hoʻomaka ai, a laila pehea ka lōʻihi e kali ai ʻoe i ka pizza?
I ka wā mua, ua ʻike mākou iā mākou iho i kekahi manawa ua lawe ka noi i ʻelua mau kekona e hoʻomaka ai, a laila hoʻomaka mākou e lohe i nā hoʻopiʻi mai nā hoa hana ʻē aʻe e pili ana i ka lōʻihi. Akā ʻaʻole hiki iā mākou ke hana mau i kēia kūlana.
Pehea ka lōʻihi? Wahi a Palapala Google, inā emi ka hoʻomaka anuanu o kahi noi ma mua o 5 kekona, a laila manaʻo ʻia kēia "me he mea maʻamau". Ua hoʻokuʻu ʻia ʻo Dodo Pizza Android app (e like me nā metric Firebase _app_start) ma hoʻomaka anu ma ka awelika i 3 kekona - "ʻAʻole maikaʻi, ʻaʻole weliweli," e like me kā lākou e ʻōlelo nei.
Akā, ua hoʻomaka ka hoʻopiʻi ʻana no ka lawe ʻana o ka noi i kahi manawa lōʻihi loa e hoʻomaka ai! No ka hoʻomaka ʻana, ua hoʻoholo mākou e ana i ke ʻano o ka "loa, loa, lōʻihi loa". A ua hoʻohana mākou i ka trace Firebase no kēia Ka hoʻomaka ʻana o ka polokalamu.
ʻO kēia kaʻina maʻamau e ana i ka manawa ma waena o ka manawa e wehe ai ka mea hoʻohana i ka noi a me ka manawa e hoʻokō ʻia ai ka onResume() o ka hana mua. Ma ka Firebase Console ua kapa ʻia kēia metric _app_start. Ua ʻike ʻia:
ʻO nā manawa hoʻomaka no nā mea hoʻohana ma luna o ka 95th percentile he aneane 20 kekona (ʻoi aku ka lōʻihi o kekahi), ʻoiai ʻo ka manawa hoʻomaka anuanu ma lalo o 5 kekona.
ʻO ka manawa hoʻomakaʻaʻole ia he waiwai mau, akā e ulu ana i ka manawa. Akā i kekahi manawa aia nā hāʻule. Ua ʻike mākou i kēia ʻano i ka wā i hoʻonui ai mākou i ka nui o ka nānā ʻana i 90 mau lā.
ʻElua mau manaʻo i hiki mai i ka manaʻo:
Ke lele nei kekahi mea.
Hoʻonohonoho hou ʻia kēia "mea" ma hope o ka hoʻokuʻu ʻana a laila lele hou.
"He mea paha me ka waihona," manaʻo mākou, a ua pololei mākou. ʻO ka mea mua, hoʻohana mākou i ka waihona ma ke ʻano he huna; i ka wā o ka neʻe ʻana mākou e hoʻomaʻemaʻe iā ia. ʻO ka lua, hoʻouka ʻia ka waihona i ka wā e hoʻomaka ai ka noi. Hoʻohui like nā mea a pau.
He aha ka hewa i ka waihona Realm
Hoʻomaka mākou e nānā i ka loli ʻana o nā ʻikepili o ka waihona i ke ola o ka noi, mai ka hoʻokomo mua ʻana a i ka wā e hoʻohana ikaika ai. Hiki iā ʻoe ke nānā i nā ʻike o ka waihona Realm ma o stetho a i ʻole i nā kikoʻī hou aʻe a maopopo ma ka wehe ʻana i ka faila ma o Halekula Aupuni. No ka ʻike ʻana i nā mea o ka waihona ma o ADB, e kope i ka waihona waihona Realm:
Ma hope o ka nānā ʻana i nā mea o ka waihona i nā manawa like ʻole, ua ʻike mākou e piʻi mau ana ka nui o nā mea o kekahi ʻano.
Hōʻike ke kiʻi i kahi ʻāpana o Realm Studio no nā faila ʻelua: ma ka ʻaoʻao hema - ka waihona noi i kekahi manawa ma hope o ke kau ʻana, ma ka ʻākau - ma hope o ka hoʻohana ikaika. Hiki ke ʻike ʻia ka nui o nā mea ImageEntity и MoneyType ua ulu nui loa (hōʻike ke kiʻi kiʻi i ka helu o nā mea o kēlā me kēia ʻano).
Ka pilina ma waena o ka ulu ʻana o ka waihona a me ka manawa hoʻomaka
He ino loa ka ulu ana o ka waihona i hooponopono ole ia. Akā pehea e pili ai kēia i ka manawa hoʻomaka noi? He mea maʻalahi ke ana i kēia ma o ka ActivityManager. Mai ka Android 4.4, hōʻike ka logcat i ka log me ke kaula i hōʻike ʻia a me ka manawa. Ua like kēia manawa me ka manawa mai ka manawa i hoʻokuʻu ʻia ai ka noi a hiki i ka pau ʻana o ka hana hana. I loko o kēia manawa, hiki mai nā hanana penei:
E hoʻomaka i ka hana.
Hoʻomaka mua o nā mea.
Ka hana ʻana a me ka hoʻomaka ʻana o nā hana.
Ke hana ʻana i kahi hoʻolālā.
Hoʻopuka noi.
Kūpono iā mākou. Inā holo ʻoe i ka ADB me nā hae -S a me -W, hiki iā ʻoe ke loaʻa i ka hoʻopuka hoʻonui me ka manawa hoʻomaka:
adb shell am start -S -W ru.dodopizza.app/.MainActivity -c android.intent.category.LAUNCHER -a android.intent.action.MAIN
Если сгрепать оттуда grep -i WaitTime manawa, hiki iā ʻoe ke hoʻokaʻawale i ka hōʻiliʻili ʻana o kēia metric a nānā pono i nā hopena. Hōʻike ka pakuhi ma lalo i ka hilinaʻi o ka manawa hoʻomaka o ka noi ma ka helu o ka hoʻomaka anu o ka noi.
I ka manawa like, ua like ke ʻano o ka pilina ma waena o ka nui a me ka ulu ʻana o ka waihona, i ulu mai 4 MB a 15 MB. I ka huina, ua hoʻololi ʻia i ka manawa (me ka ulu ʻana o ke anuanu), ua hoʻonui ʻia ka manawa hoʻomaka o ka noi a me ka nui o ka waihona. He kuhiakau ko makou ma ko makou mau lima. ʻO nā mea a pau i koe e hōʻoia i ka hilinaʻi. No laila, ua hoʻoholo mākou e wehe i nā "leaks" a ʻike inā e wikiwiki kēia i ka hoʻomaka.
Nā kumu o ka ulu ʻana o ka waihona ʻikepili pau ʻole
Ma mua o ka wehe ʻana i nā "leaks", pono e hoʻomaopopo i ke kumu i ʻike ʻia ai lākou ma kahi mua. No ka hana ʻana i kēia, e hoʻomanaʻo kākou i ke ʻano o Realm.
ʻO Realm kahi waihona pili ʻole. Hiki iā ʻoe ke wehewehe i nā pilina ma waena o nā mea ma ke ʻano like me ka nui o nā ʻikepili pili ORM ma Android i wehewehe ʻia. I ka manawa like, mālama pono ʻo Realm i nā mea i ka hoʻomanaʻo me ka liʻiliʻi o nā loli a me nā palapala palapala. ʻAe kēia iā ʻoe e heluhelu wikiwiki i ka ʻikepili mai ka disk, ʻo ia ka ikaika o Realm a me ke kumu i aloha ʻia ai.
(No ke kumu o kēia ʻatikala, lawa kēia wehewehe ʻana iā mākou. Hiki iā ʻoe ke heluhelu hou aʻe e pili ana i Realm i ke anuanu palapala aiʻole ma kā lākou hale kula).
Nui nā mea hoʻomohala i maʻa i ka hana hou aku me nā ʻikepili pili (e laʻa, nā waihona ORM me SQL ma lalo o ka puʻupuʻu). A ʻo nā mea e like me ka holoi ʻana i ka ʻikepili e like me ka hāʻawi ʻia. Akā ʻaʻole ma Realm.
Ma ke ala, ua noi ʻia ka hiʻohiʻona hoʻopau cascade no ka manawa lōʻihi. ʻO kēia hoʻoponopono и kekahi, pili pū me ia, ua kūkākūkā ikaika ʻia. Aia ka manaʻo e hana koke ʻia. Akā ua unuhi ʻia nā mea a pau i ka hoʻokomo ʻana i nā loulou ikaika a nāwaliwali, kahi e hoʻopau koke ai i kēia pilikia. Ua ola a ʻeleu ma kēia hana noi huki, i hoʻomaha ʻia i kēia manawa no nā pilikia kūloko.
Leak ʻikepili me ka holoi ʻole ʻana
Pehea ka lele ʻana o ka ʻikepili inā hilinaʻi ʻoe i ka holoi ʻana i ka cascading ʻole? Inā ua nested ʻoe i nā mea Realm, pono e holoi ʻia.
E nānā kākou i kahi laʻana (kokoke) maoli. He mea kā mākou 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()
ʻO ka huahana i loko o ke kaʻa he mau māla like ʻole, me kahi kiʻi ImageEntity, настроенные ингредиенты CustomizationEntity. Eia kekahi, hiki i ka huahana i loko o ke kaʻa ke hui pū me kāna mau huahana ponoʻī RealmList (CartProductEntity). ʻO nā kahua āpau i helu ʻia he mau mea Realm. Inā mākou e hoʻokomo i kahi mea hou (copyToRealm() / copyToRealmOrUpdate()) me ka id like, a laila e hoʻopau piha ʻia kēia mea. Akā ʻo nā mea kūloko āpau (kiʻi, customizationEntity a me cartComboProducts) e nalowale ka pilina me ka makua a noho i ka waihona.
No ka nalowale ʻana o ka pilina me lākou, ʻaʻole mākou e heluhelu hou a holoi paha iā lākou (inā ʻaʻole mākou e komo pono iā lākou a hoʻomaʻemaʻe paha i ka "papaʻaina" holoʻokoʻa). Ua kapa mākou i kēia "leaks hoʻomanaʻo".
Ke hana nei mākou me Realm, pono mākou e hele i nā mea āpau a hoʻopau i nā mea āpau ma mua o ia mau hana. Hiki ke hana i kēia, no ka laʻana, e like me kēia:
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()
}
// и потом уже сохраняем
Inā hana ʻoe i kēia, a laila e hana nā mea a pau e like me ka mea e pono ai. Ma kēia laʻana, manaʻo mākou ʻaʻohe mea ʻē aʻe i nested Realm i loko o ke kiʻi, customizationEntity, a me cartComboProducts, no laila ʻaʻohe puka puka a me nā holoi ʻē aʻe.
Hoʻoholo "wikiwiki".
ʻO ka mea mua a mākou i hoʻoholo ai e hoʻomaʻemaʻe i nā mea ulu wikiwiki a nānā i nā hopena e ʻike inā e hoʻoponopono kēia i kā mākou pilikia kumu. ʻO ka mea mua, ua hana ʻia ka hopena maʻalahi a me ka intuitive, ʻo ia hoʻi: pono i kēlā me kēia mea ke kuleana no ka wehe ʻana i kāna mau keiki. No ka hana ʻana i kēia, ua hoʻolauna mākou i kahi interface i hoʻihoʻi i kahi papa inoa o kāna mau mea Realm pūnana:
interface NestedEntityAware {
fun getNestedEntities(): Collection<RealmObject?>
}
A ua hoʻokō mākou i kā mākou mau mea Realm:
@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 Hoʻihoʻi mākou i nā keiki āpau ma ke ʻano he papa inoa. A hiki i kēlā me kēia mea keiki ke hoʻokō i ka nestedEntityAware interface, e hōʻike ana aia nā mea Realm kūloko e holoi ai, no ka laʻana. 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
)
}
}
A pēlā aku, hiki ke hana hou i ka pūnana o nā mea.
A laila kākau mākou i kahi ala e hoʻopau hou i nā mea nested. Hana (hana ʻia ma ke ʻano he hoʻonui) deleteAllNestedEntities loaʻa nā mea a me ke ʻano kiʻekiʻe a pau deleteNestedRecursively Hoʻopau hou i nā mea pūnana me ka hoʻohana ʻana i ka interface 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()
}
}
}
Мы проделали это с самыми быстрорастущими объектами и проверили, что получилось.
ʻO ka hopena, ua pau ka ulu ʻana o kēlā mau mea a mākou i uhi ai me kēia hoʻonā. A ua lohi ka ulu holoʻokoʻa o ke kumu, akā ʻaʻole i pau.
ʻO ka hopena "maʻamau".
База хоть и стала расти медленнее, но все равно росла. Поэтому мы стали искать дальше. В нашем проекте очень активно используется кеширование данных в Realm. Поэтому писать для каждого объекта все вложенные объекты трудозатратно, плюс повышается риск ошибки, ведь можно забыть указать объекты при изменении кода.
Хотелось сделать так, чтобы не использовать интерфейсы, а чтобы всё работало само.
Ke makemake mākou i kahi mea e hana ponoʻī, pono mākou e hoʻohana i ka noʻonoʻo. No ka hana ʻana i kēia, hiki iā mākou ke hele i kēlā me kēia kahua papa a nānā inā he mea Realm a i ʻole ka papa inoa o nā mea:
Inā he RealmModel a i ʻole RealmList ke kahua, a laila e hoʻohui i ka mea o kēia kahua i kahi papa inoa o nā mea pūnana. Ua like loa nā mea a pau e like me kā mākou i hana ai ma luna, aia wale nō e hana ʻia iā ia iho. He maʻalahi loa ke ʻano o ka holoi ʻana i ka cascade a e like me kēia:
Hoʻonui filterRealmObject kānana a hāʻawi i nā mea Realm wale nō. ʻano hana getNestedRealmObjects ma ka noʻonoʻo ʻana, ʻike ʻo ia i nā mea Realm nested āpau a hoʻokomo iā lākou i kahi papa inoa laina. A laila mākou e hana i ka mea like recursively. I ka holoiʻana, ponoʻoe e nānā i ka mea no ka pono isValid, no ka mea, hiki i nā mea makua ʻokoʻa ke loaʻa i nā mea like. ʻOi aku ka maikaʻi o ka pale ʻana i kēia a hoʻohana wale i ka auto-generation of id i ka wā e hana ai i nā mea hou.
Hoʻokō piha i ke ʻano 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)
}
ʻO ka hopena, ma kā mākou mea kūʻai aku hoʻohana mākou i ka "cascading delete" no kēlā me kēia hana hoʻololi ʻikepili. No ka laʻana, no ka hana hoʻokomo e like me kēia:
ʻO ka hana mua getManagedEntities loaʻa nā mea i hoʻohui ʻia, a laila ke ʻano cascadeDelete Holoi hou i nā mea i hōʻiliʻili ʻia ma mua o ke kākau ʻana i nā mea hou. Hoʻopau mākou i ka hoʻohana ʻana i kēia ala i loko o ka noi. Ua pau loa nā leaka hoʻomanaʻo ma Realm. Ke hoʻokō nei i ke ana like o ka hilinaʻi o ka manawa hoʻomaka i ka helu o ka hoʻomaka anu o ka noi, ʻike mākou i ka hopena.
Hōʻike ka laina ʻōmaʻomaʻo i ka hilinaʻi o ka manawa hoʻomaka o ka noi ma ka helu o ka hoʻomaka ʻana o ke anuanu i ka wā o ka holoi ʻana i nā mea pūnana.
Nā hopena a me nā hopena
ʻO ka ʻikepili Realm e ulu mau nei ka mea e hoʻomaka mālie ai ka noi. Ua hoʻokuʻu mākou i kahi mea hou me kā mākou "cascading delete" o nā mea nested. A ke nānā nei mākou a loiloi i ka hopena o kā mākou hoʻoholo i ka manawa hoʻomaka noi ma o ka _app_start metric.
Для анализа берём промежуток времени 90 дней и видим: время запуска приложения, как медианное, так и то, что приходится на 95 процентиль пользователей, начало уменьшаться и больше не поднимается.
Inā ʻoe e nānā i ka pakuhi ʻehiku lā, ʻike pono ʻia ka _app_start metric a emi iho i ka 1 kekona.
Pono nō hoʻi e hoʻohui ʻia ma ka paʻamau, hoʻouna ʻo Firebase i nā leka hoʻomaopopo inā ʻoi aku ka waiwai waena o _app_start ma mua o 5 kekona. Eia naʻe, e like me kā mākou e ʻike ai, ʻaʻole pono ʻoe e hilinaʻi i kēia, akā e komo i loko a nānā pono.
ʻO ka mea kūikawā e pili ana i ka waihona Realm he waihona pili ʻole ia. ʻOiai ka maʻalahi o ka hoʻohana ʻana, ka like me nā hoʻonā ORM a me ka hoʻopili ʻana i nā mea, ʻaʻohe ia o ka holoi ʻana.
Inā ʻaʻole i noʻonoʻo ʻia kēia, a laila e hōʻiliʻili nā mea pūnana a "lele." E ulu mau ana ka waihona, a e hoʻopili i ka lohi a i ʻole ka hoʻomaka ʻana o ka noi.
Ua haʻi wau i kā mākou ʻike pehea e hana wikiwiki ai i ka holoi ʻana i nā mea i Realm, ʻaʻole i waho o ka pahu, akā ua kamaʻilio ʻia no ka manawa lōʻihi. e'ōlelo lākou и e'ōlelo lākou. I kā mākou hihia, ua wikiwiki kēia i ka manawa hoʻomaka o ka noi.
ʻOiai ke kūkākūkā e pili ana i ke ʻano kokoke o kēia hiʻohiʻona, ʻo ka nele o ka holoi ʻana i ka cascade i Realm e hana ʻia e ka hoʻolālā. Inā ʻoe e hoʻolālā ana i kahi noi hou, a laila e noʻonoʻo i kēia. A inā ʻoe e hoʻohana nei iā Realm, e nānā inā loaʻa iā ʻoe nā pilikia.