Optimasi startup asinkron dan inisialisasi fenomena yang tidak disengaja
Biasanya tidak ada gunanya menukar keuntungan 200 ms dengan kondisi balapan yang tidak dapat diulang dan biaya pemecahan masalah.
Indikator layar pertama turun, tetapi salah satu gangguan yang paling mengganggu mulai muncul secara online: muncul sesekali, sulit untuk direproduksi, dan tampak bersifat metafisik.
Tumpukan kerusakan tidak stabil, semua log terlihat “normal”, dan terkadang dapat pulih sendiri. Melihat kembali catatan perubahan, semua orang melakukan hal yang sama: menghentikan inisialisasi fase startup, menundanya, menjadikannya asinkron, dan menjadikannya bersamaan untuk membuat cold start lebih cepat.
Masalahnya bukan pada “kelambatannya hilang”, tetapi “ketergantungannya hilang”, atau lebih tepatnya, ketergantungannya disembunyikan.
Dalam artikel ini, saya ingin menjelaskan penilaian paling kritis dalam penyelidikan nyata: jebakan optimasi startup sering kali menempatkan interaksi bisnis pertama ke dalam keadaan semi-inisialisasi. **Penghematan 200 md mungkin akan terbuang pada error yang sesekali terjadi, status yang salah, cakupan bersama, dan waktu pemecahan masalah tim.
Latar belakang masalah: Layar pertama lebih cepat, dan klik pertama terkadang macet
Deskripsi kesalahannya sangat umum:
- Android cold start lebih cepat, dan waktu layar putih pertama berkurang
- Sebagian kecil pengguna online terkadang mengalami crash atau kesalahan pada “klik pertama setelah layar pertama”
- Tumpukan kerusakan terkadang ada di modul bisnis, terkadang di lapisan jaringan, dan terkadang di SDK.
- Hampir tidak mungkin untuk mereproduksi di lingkungan lokal dan pengujian, dan reproduksi skala abu-abu juga tidak stabil.
Jenis masalah ini paling mudah disalahartikan sebagai “perbedaan dalam lingkungan online”, “kompatibilitas model”, dan “kejang SDK pihak ketiga”. Namun jika ini sangat relevan dengan perubahan pengoptimalan startup, pertama-tama saya akan memperlakukannya sebagai sesuatu yang lebih sederhana: **kondisi balapan. **
Penilaian inti: Asinkronisasi bukanlah metode pengoptimalan, melainkan mengubah semantik kesiapan sistem.
Banyak intuisi untuk optimasi startup adalah:
- Barang-barang IO yang berat dipindahkan ke thread latar belakang
- Hal-hal berat CPU secara paralel
- Tunda inisialisasi kritis dari layar non-pertama hingga setelah layar pertama
Ini hampir selalu “valid” pada metrik.
Namun mereka juga melakukan sesuatu yang lebih berbahaya: **Hapus ketergantungan yang awalnya tersirat dalam “eksekusi berurutan”. **
Sebelumnya di Application#onCreate() ini diinisialisasi secara berurutan: A -> B -> C. Bahkan jika tidak ada yang menulis dokumen, sistem defaultnya adalah fakta ini:
- Ketika
onCreate()berakhir, A/B/C setidaknya telah berjalan
Kemudian mereka dipecah menjadi:
-A segera jalankan
- B menyerahkan tugas asinkron -C serahkan ke tugas asinkron lainnya
Saat ini, akhiran onCreate() tidak lagi berarti “sistem sudah siap”, melainkan hanya berarti “Saya membuang tugas”.
Klik pertama online sering kali terjadi pada waktu yang tidak terduga: rendering layar pertama selesai, pengguna langsung mengklik, atau perilaku otomatis memicu navigasi.
Jadi interaksi bisnis pertama berada dalam rentang yang canggung:
- Beberapa dependensi telah diinisialisasi
- Beberapa masih berjalan
- Ada yang gagal namun dirahasiakan secara diam-diam
- Ada yang belum dimulai karena tertunda
Itu bukan “lambat”, ini keadaan tidak lengkap.
Proses demonstrasi: Bagaimana masalah menyatu ke “semi-inisialisasi” langkah demi langkah?
Untuk memecahkan masalah yang sesekali terjadi, saya tidak akan fokus pada crash stack terlebih dahulu. Saya akan melakukan tiga hal terlebih dahulu untuk mengubah “tidak dapat direproduksi” menjadi “dapat dijelaskan”.
1) Gambarkan diagram ketergantungan startup terlebih dahulu, jangan gambar diagram modul
Diagram modul menjawab “siapa bergantung pada siapa”, tetapi pertanyaan awal menjawab:
- Inisialisasi apa yang harus diselesaikan sebelum interaksi pertama
- Kegagalan inisialisasi mana yang akan memengaruhi semantik bisnis
- Inisialisasi mana yang hanyalah lapisan gula pada kue
Saya akan membagi dependensi startup menjadi tiga kategori sesuai dengan batasan “interaksi pertama”:
- Harus siap (Hard Ready): Jika belum siap, tidak boleh masuk ke jalur kritis, seperti status login, token autentikasi, tabel perutean, model thread kunci (seperti batasan thread utama/penjadwal coroutine), dan kumpulan minimum laporan kerusakan.
- Soft Ready: Anda dapat memasuki bisnis ini jika Anda belum siap, namun Anda harus menurunkan versi dengan cara yang terkendali, seperti caching yang direkomendasikan, eksperimen AB, dan bidang peningkatan yang terkubur.
- Ditangguhkan: Ini dapat dilakukan nanti tanpa memengaruhi semantik interaksi pertama, seperti pemanasan, inisialisasi dekoder gambar, dan SDK non-kritis.
Nilai dari langkah ini adalah mengubah argumen dari “asinkron atau tidak asinkron” menjadi “pada batasan berapa ketergantungan ini harus diselesaikan”.
2) Berikan “kontrak kesiapan” pada setiap ketergantungan, jika tidak, asinkronisasi sama dengan perjudian
Yang disebut kontrak kesiapan adalah untuk memperjelas dua hal:
- Siapa yang akan menilai apakah sudah siap
- Cara melanjutkan ketika bisnis belum siap
Asinkronisasi tanpa kontrak kesiapan, manifestasi umum adalah:
- Penelepon mengira inisialisasi telah selesai dan langsung digunakan
- Penginisialisasi mengira penelepon tidak akan menggunakannya sedini ini -Kedua belah pihak benar, kesalahan online ada pada “waktunya”
Salah satu kerusakan paling umum yang pernah saya lihat adalah mengubah inisialisasi singleton menjadi malas + async.
Kode semu terlihat seperti ini:
object Foo {
@Volatile private var inited = false
fun initAsync() {
GlobalScope.launch(Dispatchers.IO) {
// 读配置/解密/拉取远端
inited = true
}
}
fun doWork() {
check(inited) { "Foo not initialized" }
// ...
}
}
Indikator layar pertama akan menjadi lebih baik, tetapi setelah waktu panggilan doWork() dimajukan sebelum akhir init, ini akan menjadi “sesekali”.
Yang lebih buruknya adalah banyak kode yang tidak check(inited), tetapi akan terus berjalan, menghasilkan status kesalahan, dan tidak akan meledak hingga nanti.
3) Ukur “jendela” kompetisi daripada mengandalkan perasaan
Kondisi yang diperlukan agar asinkronisasi dapat menimbulkan masalah adalah:
- Interaksi pertama terjadi sebelum beberapa inisialisasi selesai
Jadi saya akan menambahkan dua jenis log (perhatikan bahwa keduanya adalah titik waktu yang dapat disejajarkan):
t0: Proses Mulai/Application.onCreateMulait1: Layar pertama bersifat interaktif (benar-benar dapat diklik)t_ready(X): Titik waktu ketika setiap ketergantungan kunci siap
Kemudian lihat distribusinya:
- Berapa proporsi
t1 < t_ready(Auth) - Berapa proporsi
t1 < t_ready(Router) - Dan apakah itu terkait dengan model, jaringan, boot panas dan dingin, dan versi sistem
Ketika jendela ini dapat diukur, banyak “kejadian” yang tiba-tiba menjadi tidak misterius: ini hanyalah sebuah peristiwa yang bersifat probabilitas.
Kesalahpahaman dan kasus kegagalan: Semakin banyak Anda menulis, semakin besar kemungkinan Anda menciptakan masalah yang lebih sulit dipecahkan.
Setelah memulai asinkronisasi, tim secara alami akan berhati-hati:
- Jika ketergantungan belum siap, gunakan nilai default
- Jika konfigurasi tidak ditarik, buka cache terakhir
- AB jatuh kendali sebelum mendapatkannya.
Masing-masing hal ini masuk akal, tetapi memiliki dua efek samping.
Kesalahpahaman 1: Mengubah “kurangnya ketergantungan” menjadi “penyimpangan semantik”
Crash sebenarnya mudah untuk dipecahkan, namun status kesalahan adalah yang paling sulit untuk dipecahkan.
Misalnya status login belum siap maka akan menjadi “belum login”. Ini akan membawa pengguna ke halaman kesalahan ketika klik pertama memicu lompatan. Nanti ketika status login sebenarnya sudah siap, status halaman direset lagi, sehingga muncul “flash”, “jump back” dan “sesekali logout”.
Anda akan melihat sekumpulan cabang “normal” di log: semuanya tercakup dalam desain. Namun pengalaman penggunanya buruk, dan sulit mengaitkannya dengan pengoptimalan startup.
Kesalahpahaman 2: Menutupi rahasia satu sama lain menyebabkan putusnya rantai bukti untuk pemecahan masalah
Ketergantungan A belum siap, sehingga mengambil jalan yang berbahaya.
Pada saat yang sama, ketergantungan B belum siap dan juga telah melalui berbagai macam trik.
Pada akhirnya bisnis berperilaku seperti masalah B, namun akar permasalahannya adalah A.
Yang lebih realistis adalah: agar “tidak crash”, pengecualian ditelan dan kegagalan dicatat sebagai debug, hanya menyisakan satu “hasil yang salah” secara online.
Ini adalah salah satu sumber “tidak dapat direproduksi”: menghapus sinyal kegagalan kunci.
Cara memperbaiki: Ubah “Asinkronisasi” menjadi “Batas Kesiapan yang Dapat Diverifikasi”
Untuk mengatasi masalah ini, semantik startup sistem biasanya diperketat kembali.
Saya akan melakukan tiga langkah dari biaya terendah hingga tertinggi.
1) Tentukan Gerbang Siap yang dapat dieksekusi
Ketergantungan pada Hard Ready memberikan gerbang terpadu:
- Anda harus melewati gerbang sebelum berinteraksi untuk pertama kali
- Jika tidak dapat lolos, blokir operasi utama atau berikan jalur yang jelas untuk menurunkan versi.
Misalnya, tambahkan tanda centang kecil pada entri klik pertama (tombol navigasi/perutean/kunci):
- Lanjutkan jika sudah siap
- Tampilkan loading jika belum siap, atau antri dulu
Kunci dari langkah ini adalah mengubah “ketergantungan belum siap” dari status ras implisit menjadi status eksplisit.
2) Jadikan inisialisasi sebagai “tugas berstatus” alih-alih api-dan-lupakan
Banyak inisialisasi yang dibuang secara langsung menggunakan GlobalScope.launch atau kumpulan thread, dan jika gagal, maka gagal.
Pendekatan yang lebih terkendali adalah dengan:
- Setiap inisialisasi memiliki status:
NotStarted / Running / Ready / Failed - Penelepon mendapat pegangan yang bisa ditunggu (walaupun pada akhirnya tidak menunggu)
Kode semu:
sealed class InitState {
data object NotStarted : InitState()
data object Running : InitState()
data object Ready : InitState()
data class Failed(val error: Throwable) : InitState()
}
class Initializer {
@Volatile private var state: InitState = InitState.NotStarted
private val deferred = CompletableDeferred<Unit>()
fun start() {
if (state != InitState.NotStarted) return
state = InitState.Running
scope.launch(Dispatchers.IO) {
runCatching {
// do init
}.onSuccess {
state = InitState.Ready
deferred.complete(Unit)
}.onFailure {
state = InitState.Failed(it)
deferred.completeExceptionally(it)
}
}
}
suspend fun awaitReady() = deferred.await()
}
Hal ini membuat dua hal menjadi benar:
- Anda dapat memilih tempat menunggu
- Tidak ada lagi “berpikir itu lebih baik”
3) Tetapkan batas dan tombol rollback untuk inisialisasi tertunda
Inisialisasi yang lambat bukan tidak mungkin, tetapi memerlukan kondisi batas:
- Pengguna/skenario mana yang dapat ditunda (seperti hanya start dingin, atau start panas juga tertunda)
- Apa yang harus dilakukan ketika terjadi kegagalan (coba lagi, nonaktifkan, kembalikan)
- Cara mengamati skala abu-abu (distribusi jendela siap pakai, tingkat kegagalan, rasio degradasi)
Saya lebih suka menjadikan “Mulai Asinkronisasi” sebagai peralihan kebijakan rollback daripada perubahan kode satu kali.
Karena begitu masalah sesekali ditemukan secara online, cara tercepat untuk menghentikan pendarahan biasanya adalah dengan “memutar kembali asinkronisasi”.
Batasan yang berlaku: kapan asinkronisasi menguntungkan dan kapan merugikan?
Premis bahwa asinkronisasi menguntungkan adalah:
- Ketergantungan bersifat Soft Ready atau Ditunda
- Kontrak kesiapannya jelas, dan ada rantai bukti kegagalannya
- Jendela siap berukuran kecil dan stabil, dan tidak mencakup interaksi pertama
Skenario umum ketika asinkronisasi mengalami kerugian:
- Ketergantungannya adalah Hard Ready, tetapi dipindahkan demi metrik
- Menutupi kegagalan dengan menutup-nutupi, menyebabkan penyimpangan semantik
- Tidak ada gerbang yang siap, sehingga kondisi balapan menjadi peristiwa yang bersifat probabilistik
Untuk meringkas dalam satu kalimat: Bisakah Anda menjelaskan kapan belum siap, dan jika bisnis dapat mempertahankan semantik yang konsisten saat belum siap, asinkronisasi dianggap dioptimalkan. **
Ringkasan
Pengoptimalan cold start paling mudah didorong oleh KPI menjadi satu masalah tujuan: membuat layar pertama lebih cepat.
Namun yang benar-benar perlu diingat selama fase startup adalah “kapan sistem dianggap tersedia?” Semakin banyak inisialisasi dipecah menjadi beberapa bagian, semakin jelas semantik kesiapan yang perlu ditulis ke dalam kode dan ke dalam observasi.
Jika tidak, yang akan kami lakukan adalah mengganti kelambatan deterministik dengan kesalahan probabilistik.
What to read next
Want more posts about Uncategorized?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home