Membangun Real User Monitoring (RUM) Kustom: Melacak dan Menganalisis Pengalaman Pengguna Secara Mendalam
1. Pendahuluan: Mengapa RUM Kustom?
Sebagai developer web, kita seringkali fokus pada performa di lingkungan pengembangan atau staging. Kita menggunakan tools seperti Lighthouse, WebPageTest, atau Synthetic Monitoring lainnya untuk mengukur seberapa cepat website kita dimuat. Tapi, ada satu pertanyaan krusial yang sering terlewat: bagaimana sebenarnya pengalaman pengguna di dunia nyata?
Di sinilah Real User Monitoring (RUM) berperan penting. RUM adalah metode untuk mengumpulkan data performa dan interaksi pengguna langsung dari browser pengguna asli. Berbeda dengan Synthetic Monitoring yang mensimulasikan pengguna dari lokasi dan perangkat tertentu, RUM memberikan gambaran jujur tentang apa yang dialami oleh setiap pengguna, di berbagai kondisi jaringan, perangkat, dan lokasi geografis.
Blog ini pernah membahas dasar-dasar RUM dalam artikel “Mengintip Pengalaman Pengguna: Memahami Synthetic Monitoring dan Real User Monitoring (RUM)”. Kali ini, kita akan melangkah lebih jauh. Kita akan membahas mengapa Anda mungkin ingin membangun sistem RUM kustom Anda sendiri, dan bagaimana cara melakukannya, dari pengumpulan data di frontend hingga penanganan di backend.
⚠️ Keterbatasan Tools Pihak Ketiga: Meskipun ada banyak penyedia RUM komersial yang hebat (seperti Sentry, Datadog, New Relic), membangun RUM kustom memberi Anda fleksibilitas penuh terhadap data yang ingin Anda kumpulkan, bagaimana Anda menyimpannya, dan yang terpenting, kontrol penuh atas aspek privasi. Ini juga bisa menjadi solusi yang lebih hemat biaya untuk proyek dengan skala tertentu.
🎯 Tujuan Artikel Ini: Setelah membaca artikel ini, Anda akan memiliki pemahaman yang kuat dan panduan praktis untuk mulai membangun sistem RUM kustom Anda sendiri, yang mampu memberikan wawasan mendalam tentang pengalaman pengguna aplikasi web Anda.
2. Memahami Metrik Kunci untuk RUM
Sebelum kita mulai mengumpulkan data, kita perlu tahu data apa yang penting. Metrik performa web terus berkembang, dan saat ini, Core Web Vitals adalah standar emas yang direkomendasikan Google untuk mengukur pengalaman pengguna.
📌 Metrik Esensial yang Harus Anda Lacak:
- Largest Contentful Paint (LCP): Mengukur waktu yang dibutuhkan untuk merender elemen konten terbesar yang terlihat di viewport. Ini adalah indikator utama kecepatan pemuatan halaman.
- Cumulative Layout Shift (CLS): Mengukur stabilitas visual halaman dengan menjumlahkan semua pergeseran layout yang tidak terduga selama masa pakai halaman. CLS yang rendah berarti pengalaman yang lebih mulus dan menyenangkan.
- Interaction to Next Paint (INP): Mengukur responsivitas halaman terhadap interaksi pengguna (klik, tap, keypress). Ini menjadi Core Web Vital baru menggantikan First Input Delay (FID) yang berfokus pada responsivitas secara keseluruhan.
- Time to First Byte (TTFB): Waktu yang dibutuhkan browser untuk menerima byte pertama dari respons server. Ini mengukur seberapa cepat server Anda merespons.
- First Contentful Paint (FCP): Waktu saat browser merender potongan konten DOM pertama. Ini menandakan awal dari pengalaman pemuatan yang dirasakan pengguna.
- Custom Metrics: Selain metrik standar, Anda bisa melacak hal-hal spesifik aplikasi Anda, seperti:
- Waktu pemuatan komponen tertentu (misalnya, widget rekomendasi).
- Waktu yang dihabiskan pengguna pada suatu halaman.
- Kecepatan respons API tertentu.
- Tingkat keberhasilan/kegagalan interaksi penting (misalnya, submit form).
✅ Dengan metrik-metrik ini, Anda dapat membangun gambaran komprehensif tentang performa dan pengalaman pengguna di aplikasi web Anda.
3. Mengumpulkan Data Performa di Browser
Browser modern menyediakan berbagai API yang kuat untuk mengumpulkan data performa. Kita akan memanfaatkan PerformanceObserver dan navigator.sendBeacon sebagai fondasi utama.
Menggunakan PerformanceObserver untuk Core Web Vitals
PerformanceObserver adalah alat paling efisien untuk melacak metrik performa seiring waktu tanpa memblokir thread utama.
// rum.js
const reportEndpoint = '/api/rum'; // Endpoint API di backend Anda
// Fungsi untuk mengirim data ke backend
function sendMetrics(metrics) {
if (navigator.sendBeacon) {
navigator.sendBeacon(reportEndpoint, JSON.stringify(metrics));
} else {
// Fallback untuk browser lama
fetch(reportEndpoint, {
method: 'POST',
body: JSON.stringify(metrics),
headers: {
'Content-Type': 'application/json'
},
keepalive: true // Penting agar request tidak dibatalkan saat halaman ditutup
});
}
}
// Objek untuk menyimpan metrik
const collectedMetrics = {
pageUrl: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now(),
// ... bisa tambahkan info user ID (anonim), session ID, dsb.
};
// Mengumpulkan LCP, CLS, INP
const po = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (entry.name === 'LCP') {
collectedMetrics.lcp = entry.startTime;
} else if (entry.name === 'CLS') {
collectedMetrics.cls = entry.value;
} else if (entry.name === 'INP') {
// INP adalah akumulasi dari semua interaksi, kita perlu mengambil nilai terakhir
// Atau mengumpulkan event interaksi individual
// Untuk RUM kustom sederhana, kita bisa ambil entry.value (yang merupakan nilai akumulatif)
// Atau lebih baik, lacak interaksi individual
collectedMetrics.inp = entry.value;
}
}
});
// Perhatikan jenis entry yang dilacak
po.observe({ type: ['largest-contentful-paint', 'layout-shift', 'long-animation-frame'], buffered: true });
// 'long-animation-frame' digunakan untuk INP
// Mengumpulkan FCP
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (entry.name === 'first-contentful-paint') {
collectedMetrics.fcp = entry.startTime;
}
}
}).observe({ type: 'paint', buffered: true });
// Mengumpulkan TTFB (menggunakan Navigation Timing API)
function getTTFB() {
const navEntry = performance.getEntriesByType('navigation')[0];
return navEntry ? navEntry.responseStart - navEntry.requestStart : 0;
}
collectedMetrics.ttfb = getTTFB();
// Mengirim semua metrik saat halaman akan di-unload
window.addEventListener('beforeunload', () => {
sendMetrics(collectedMetrics);
});
// Contoh Custom Metric: Waktu loading gambar utama
window.addEventListener('load', () => {
const mainImage = document.querySelector('img.main-content');
if (mainImage && mainImage.complete) {
collectedMetrics.mainImageLoadTime = performance.now();
}
});
// Contoh Custom Metric: Error JavaScript
window.addEventListener('error', (event) => {
sendMetrics({
type: 'error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error ? event.error.stack : 'N/A',
pageUrl: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
});
});
💡 Tips Praktis:
- Pastikan script RUM Anda dimuat se-awal mungkin di
<head>(denganasyncataudeferjika memungkinkan) untuk menangkap metrik secara akurat. - Gunakan
buffered: truesaat mengamatiPerformanceObserveruntuk menangkap entri yang terjadi sebelum observer diinisialisasi. - Untuk INP, idealnya Anda juga melacak setiap interaksi pengguna secara individual untuk mendapatkan pemahaman yang lebih granular tentang di mana latensi terjadi.
4. Mengirim Data ke Backend Anda
Setelah data performa berhasil dikumpulkan di browser, langkah selanjutnya adalah mengirimkannya ke backend Anda untuk disimpan dan dianalisis. Ada beberapa cara, namun navigator.sendBeacon() adalah pilihan terbaik untuk skenario RUM.
Menggunakan navigator.sendBeacon()
navigator.sendBeacon() dirancang khusus untuk mengirimkan data kecil ke server saat halaman sedang di-unload atau ditutup. Keunggulannya adalah:
- Non-blocking: Tidak menunda proses unload halaman.
- Reliable: Browser menjamin pengiriman data, bahkan jika halaman ditutup.
- Low-priority: Permintaan dikirim secara asinkron di latar belakang.
// rincian implementasi sudah ada di bagian 3 dalam fungsi sendMetrics
function sendMetrics(metrics) {
if (navigator.sendBeacon) {
// ✅ Pilihan terbaik untuk RUM
navigator.sendBeacon(reportEndpoint, JSON.stringify(metrics));
} else {
// ⚠️ Fallback untuk browser lama, kurang ideal karena bisa dibatalkan
fetch(reportEndpoint, {
method: 'POST',
body: JSON.stringify(metrics),
headers: {
'Content-Type': 'application/json'
},
keepalive: true // Coba pertahankan request
});
}
}
// ... panggil sendMetrics saat event 'beforeunload' atau event lain yang relevan
window.addEventListener('beforeunload', () => {
sendMetrics(collectedMetrics);
});
Strategi Batching Data
Untuk mengurangi jumlah permintaan ke server, terutama jika Anda melacak banyak custom event atau interaksi, Anda bisa menerapkan strategi batching. Kumpulkan metrik dalam buffer dan kirimkan secara berkala (misalnya, setiap 10-15 detik) atau saat buffer penuh, selain saat beforeunload.
let metricsBuffer = [];
const BATCH_INTERVAL = 10000; // Kirim setiap 10 detik
const MAX_BATCH_SIZE = 10; // Atau jika buffer mencapai 10 item
function addToBufferAndSend(metric) {
metricsBuffer.push(metric);
if (metricsBuffer.length >= MAX_BATCH_SIZE) {
sendMetrics(metricsBuffer);
metricsBuffer = [];
}
}
// Kirim sisa buffer sebelum unload
window.addEventListener('beforeunload', () => {
if (metricsBuffer.length > 0) {
sendMetrics(metricsBuffer);
metricsBuffer = [];
}
});
// Kirim buffer secara berkala
setInterval(() => {
if (metricsBuffer.length > 0) {
sendMetrics(metricsBuffer);
metricsBuffer = [];
}
}, BATCH_INTERVAL);
// Ganti sendMetrics(collectedMetrics) menjadi addToBufferAndSend(collectedMetrics)
// ketika mengumpulkan metrik LCP, CLS, dll.
// Misalnya, ketika PerformanceObserver mendeteksi entri baru:
// addToBufferAndSend({ type: 'lcp', value: entry.startTime });
5. Pertimbangan Backend: Ingesti dan Penyimpanan Data
Sisi backend bertanggung jawab untuk menerima data RUM yang dikirim dari frontend, memvalidasinya, dan menyimpannya.
Endpoint API untuk Ingesti Data
Buat endpoint API yang ringan dan cepat untuk menerima data RUM. Endpoint ini harus seefisien mungkin karena akan menerima banyak permintaan.
// Contoh sederhana dengan Node.js Express
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.post('/api/rum', (req, res) => {
const metrics = req.body;
// Lakukan validasi data di sini
if (!metrics || !metrics.pageUrl || !metrics.timestamp) {
return res.status(400).send('Invalid metrics data');
}
// ✅ Log atau simpan data ke database
console.log('Received RUM metrics:', metrics);
// Contoh: saveMetricsToDatabase(metrics);
// Respon secepat mungkin
res.status(204).send(); // 204 No Content adalah respons yang baik untuk sendBeacon
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`RUM Ingestion API running on port ${PORT}`);
});
Pilihan Database untuk Data RUM
Data RUM bersifat time-series (terkait waktu) dan volume tinggi. Pilihan database yang cocok meliputi:
- Time-Series Databases (TSDB): Seperti InfluxDB, Prometheus (dengan adapter penyimpanan jangka panjang seperti Thanos/Cortex), atau TimescaleDB (ekstensi PostgreSQL). Dirancang khusus untuk data berbasis waktu, menawarkan performa query dan kompresi yang sangat baik.
- NoSQL Databases: Seperti MongoDB, Cassandra, atau DynamoDB. Menawarkan skalabilitas horizontal dan fleksibilitas skema yang baik untuk data yang bervariasi.
- Data Lake/Object Storage: Seperti AWS S3, Google Cloud Storage. Data mentah bisa disimpan di sini, kemudian diproses dan dianalisis menggunakan tools seperti Apache Spark atau Athena.
💡 Saran: Untuk memulai, Anda bisa menggunakan PostgreSQL dengan ekstensi TimescaleDB atau MongoDB karena fleksibilitasnya. Seiring pertumbuhan data, migrasi ke solusi TSDB khusus akan sangat bermanfaat.
6. Privasi dan Etika dalam RUM
⚠️ Ini adalah bagian KRUSIAL. Saat mengumpulkan data dari pengguna nyata, privasi harus menjadi prioritas utama. Melanggar privasi dapat merusak kepercayaan pengguna dan berujung pada masalah hukum (misalnya, GDPR di Eropa, UU PDP di Indonesia).
📌 Praktik Terbaik untuk Privasi:
- Anonimisasi Data:
- IP Address: Jangan pernah menyimpan alamat IP pengguna secara langsung. Anonimkan (misalnya, hanya simpan 3 oktet pertama) atau hash.
- Personal Identifiable Information (PII): Jangan kumpulkan nama, email, atau informasi identitas pribadi lainnya melalui RUM. Jika Anda perlu mengaitkan metrik dengan pengguna, gunakan ID pengguna yang di-hash atau anonim.
- URL: Hapus query parameter yang mungkin berisi PII (misalnya,
?email=...).
- Transparansi:
- Sertakan informasi tentang pengumpulan data RUM dalam Kebijakan Privasi (Privacy Policy) Anda.
- Jelaskan data apa yang dikumpulkan, mengapa, dan bagaimana data tersebut digunakan.
- Keamanan Data:
- Pastikan data RUM Anda dienkripsi saat transit (HTTPS) dan saat disimpan (encryption at rest).
- Batasi akses ke data RUM hanya untuk personel yang berwenang.
- Retensi Data: Tentukan berapa lama data RUM akan disimpan. Data lama mungkin tidak lagi relevan dan hanya menambah biaya penyimpanan.
- Persetujuan Pengguna (Cookie Consent): Di banyak yurisdiksi, pengumpulan data RUM mungkin memerlukan persetujuan pengguna (consent), terutama jika melibatkan cookie atau pelacakan lintas sesi. Integrasikan dengan solusi cookie consent Anda.
✅ Dengan mematuhi prinsip-prinsip privasi ini, Anda dapat membangun sistem RUM yang kuat tanpa mengorbankan kepercayaan pengguna.
Kesimpulan
Membangun sistem Real User Monitoring (RUM) kustom adalah investasi yang sangat berharga untuk memahami dan meningkatkan pengalaman pengguna aplikasi web Anda. Dengan RUM, Anda tidak lagi menebak-nebak, melainkan melihat data nyata tentang bagaimana performa aplikasi Anda di tangan pengguna sesungguhnya.
Kita telah membahas langkah-langkah penting, mulai dari mengidentifikasi metrik kunci seperti Core Web Vitals, teknik pengumpulan data efisien menggunakan PerformanceObserver dan navigator.sendBeacon(), hingga pertimbangan backend untuk ingesti dan penyimpanan data, serta aspek privasi yang tidak boleh diabaikan.
Sekarang, giliran Anda untuk memulai! Dengan fondasi yang kuat ini, Anda bisa mulai mengumpulkan wawasan berharga, mengidentifikasi bottleneck, dan membuat keputusan berbasis data untuk terus mengoptimalkan aplikasi web Anda. Selamat mencoba!
🔗 Baca Juga
- Mengintip Pengalaman Pengguna: Memahami Synthetic Monitoring dan Real User Monitoring (RUM)
- Menguasai Core Web Vitals: Strategi Praktis untuk Performa Web yang Unggul
- Membangun Observability Dashboard yang Efektif: Mengubah Data Mentah Menjadi Wawasan Berharga
- Time-Series Databases: Fondasi Aplikasi IoT, Monitoring, dan Analitik Skalabel