Staged rollout mendistribusikan pembaruan aplikasi ke sebagian kecil pengguna (misal 5% atau 10%) untuk mengisolasi risiko bug kritis sebelum pembaruan mencakup seluruh basis pengguna. Masalah utama dari pendekatan manual adalah latensi respons manusia: developer sering terlambat menyadari lonjakan crash pada rilis bertahap hingga rating aplikasi jatuh atau pengguna komplain. Otomatisasi circuit breaker menghentikan rollout secara instan ketika batas toleransi crash terlampaui.
Arsitektur Observabilitas Sentry untuk React Native
Aplikasi React Native memiliki dua runtime: JavaScript thread dan Native runtime (JVM/ART di Android dan Objective-C/Swift runtime di iOS). Sentry melacak sesi dan crash melalui SDK @sentry/react-native yang mengikat handler native crash (seperti Thread.UncaughtExceptionHandler di Android dan signal handler di iOS) bersama integrasi Global Error Handler JS engine (Hermes).
Metrik utama yang dipantau untuk rilis produksi adalah Crash-Free Session Rate. Sesi dimulai saat aplikasi berpindah dari background ke foreground dan berakhir ketika aplikasi dihentikan atau di-background selama lebih dari durasi timeout yang ditentukan. Jika crash terjadi di salah satu layer runtime, sesi tersebut ditandai sebagai crash.
Konfigurasi Metric Alert di Sentry
Untuk memicu circuit breaker, buat Metric Alert pada Sentry Dashboard:
- Dataset: Sessions / Release Health
- Target Entity:
percentage()dari crash-free sessions terhadap total sessions pada spesifikreleasetag. - Kondisi Ambang Batas (Threshold): Crash-free session rate < 99.0% (atau crash rate > 1.0%) dalam jendela waktu 10 menit dengan volume minimum 500 sesi untuk menghindari false positive akibat low traffic.
- Action: Panggil webhook kustom yang mengarah ke endpoint orkestrasi internal.
Implementasi Webhook Handler: Auto-Halt via Google Play API
Saat Sentry Metric Alert terpicu, Sentry mengirimkan HTTP POST payload ke webhook receiver. Receiver memvalidasi payload, mengekstrak versi rilis, lalu memanggil Google Play Developer API (Android Publisher API v3) untuk mengubah status rilis pada track: production menjadi HALTED.
Script Webhook Handler (Node.js & Express)
Script berikut menerima webhook, mengotentikasi ke Google Cloud menggunakan Service Account dengan hak akses Release Manager pada Google Play Console, dan menghentikan staged rollout.
import express from 'express';
import { google } from 'googleapis';
const app = express();
app.use(express.json());
const PACKAGE_NAME = process.env.ANDROID_PACKAGE_NAME || 'com.example.app';
const PLAY_TRACK = 'production';
// Inisialisasi Google Auth client
const auth = new google.auth.GoogleAuth({
keyFile: process.env.GOOGLE_APPLICATION_CREDENTIALS,
scopes: ['https://www.googleapis.com/auth/androidpublisher'],
});
const play = google.androidpublisher({ version: 'v3', auth });
app.post('/webhooks/sentry-alert', async (req, res) => {
const { action, data } = req.body;
// Validasi event alert Sentry
if (action !== 'triggered') {
return res.status(200).send('Ignored non-triggered event.');
}
// Sentry metric alert payload extraction
const releaseVersion = data?.metric_alert?.filters?.release;
console.warn(`[CIRCUIT BREAKER] Threshold crash tembus pada rilis: ${releaseVersion}`);
try {
// 1. Buat Edit ID baru untuk Google Play
const editRes = await play.edits.insert({ packageName: PACKAGE_NAME });
const editId = editRes.data.id;
// 2. Ambil status rilis production saat ini
const trackRes = await play.edits.tracks.get({
packageName: PACKAGE_NAME,
editId: editId,
track: PLAY_TRACK,
});
const activeRelease = trackRes.data.releases?.find(
(r) => r.status === 'inProgress'
);
if (!activeRelease) {
return res.status(200).send('Tidak ada rilis berstatus inProgress untuk di-halt.');
}
// 3. Ubah status rilis menjadi halted
// ponytail: Pertahankan fraction saat ini untuk keperluan resume manual pasca-patch.
activeRelease.status = 'halted';
await play.edits.tracks.update({
packageName: PACKAGE_NAME,
editId: editId,
track: PLAY_TRACK,
requestBody: {
track: PLAY_TRACK,
releases: [activeRelease],
},
});
// 4. Commit perubahan
await play.edits.commit({
packageName: PACKAGE_NAME,
editId: editId,
});
console.info(`[CIRCUIT BREAKER SUCCESS] Rollout rilis ${activeRelease.name} di-halt.`);
return res.status(200).json({ status: 'halted', release: activeRelease.name });
} catch (error) {
console.error('[CIRCUIT BREAKER ERROR] Gagal menghentikan rollout:', error);
return res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => console.log('Auto-halt listener running on port 3000'));
Skipped: Verifikasi HMAC signature header Sentry dan implementasi queue retry.
Tambahkan saat: Endpoint terekspos ke internet publik dan membutuhkan proteksi idempotency ketat.
Studi Kasus Postmortem: Debugging Native Crash via Telemetri Breadcrumbs
Berikut simulasi postmortem dari insiden produksi di mana rilis staged 10% di-halt otomatis 18 menit setelah diluncurkan.
1. Deteksi dan Insiden
Rilis versi 2.4.0 (142) dinaikkan ke 10% di Google Play. Dalam 15 menit, crash-free session rate anjlok dari 99.8% ke 97.2%. Webhook Sentry memicu auto-halt pada Google Play API saat total crash mencapai 42 kejadian dari 1.500 sesi. Distribusi crash berhenti menyebar ke sisa 90% pengguna.
2. Analisis Telemetri dan Breadcrumbs
Membuka stack trace Sentry menunjukkan Native SIGSEGV / NullPointerException di layer Android runtime:
java.lang.NullPointerException: Attempt to invoke virtual method
'java.lang.String com.facebook.react.bridge.ReadableMap.getString(java.lang.String)'
on a null object reference
at com.externalmodule.NativePaymentModule.processPayload(NativePaymentModule.java:48)
at java.lang.reflect.Method.invoke(Native Method)
at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
Membaca stack trace saja tidak cukup untuk mengetahui mengapa map tersebut bernilai null. Pemeriksaan riwayat Breadcrumbs sebelum crash menunjukkan urutan telemetri berikut:
- UI Navigation:
ui.click > Button[id="btn-checkout"] - Network Event:
http.post > /api/v2/checkout/initiate (Status: 200) - Console Log:
Payment config received without optional metadata field - Native Bridge Call:
NativePaymentModule.startTransaction({...}) - Crash Event:
NullPointerExceptionpadaNativePaymentModule.java:48
3. Root Cause Analysis
Payload response endpoint /api/v2/checkout/initiate mengalami perubahan skema minor di backend: properti metadata yang sebelumnya selalu mengembalikan objek kosong {} kini mengembalikan null jika pengguna tidak memiliki histori transaksi. Pada sisi React Native JavaScript, payload diteruskan langsung ke TurboModule/NativeModule Java tanpa sanitasi payload?.metadata ?? {}. Sisi Java langsung mengakses readableMap.getMap("metadata").getString("tier"), menghasilkan NullPointerException yang mematikan thread UI Android.
Pencegahan Teknis Sebelum Promosi Rilis Penuh
Agar insiden serupa tidak berulang sebelum bundle dipromosikan ke tahap rollout berikutnya, terapkan pagar pembatas berikut di dalam pipeline CI/CD:
- Validasi Simbolik Otomatis (ProGuard/R8 & Source Maps): Pastikan CI memvalidasi upload mapping file ProGuard dan Hermes JS bundle source map ke Sentry sebelum artifacts dikirim ke Play Console. Stack trace yang tidak ter-symbolicate menyamarkan native crash sebagai obfuscated class/method, menghambat analisis akar masalah.
- Automated Smoke Testing di Perangkat Riil: Jalankan skenario checkout kritis menggunakan framework pengujian E2E (seperti Maestro atau Detox) di Firebase Test Lab atau AWS Device Farm terhadap production release build (APK/AAB release mode dengan minifikasi aktif) bukan debug build.
- Runtime Schema Contract Validation: Terapkan runtime parsing (misal Zod) pada layer integrasi API di React Native untuk setiap payload yang dikirim ke native module bridge. Validasi bahwa seluruh field non-opsional memiliki nilai fallback sebelum diproses oleh layer native (Java/Kotlin atau Obj-C/Swift).
- Gradual Rollout Cadence: Terapkan batas waktu minimum observasi metrik (misal: 10% selama minimal 6 jam, 20% selama 12 jam, 50% selama 24 jam) terlepas dari urgensi rilis.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!