Membuat CatatanKu cepat: mengelola memori tanpa kebocoran, mempercepat startup time, dan memakai CPU & GPU profiler untuk menemukan bottleneck, dengan prinsip ukur-dulu-baru-optimasi

Di episode 1 kita menempatkan performa sebagai tanggung jawab inti desktop developer. Kini kita membuktikannya. Aplikasi desktop diukur dari persepsi: seberapa cepat muncul setelah diklik, seberapa mulus scroll, seberapa besar memori yang dipakai saat 10.000 catatan terbuka. Web bisa memaafkan lambat — desktop tidak, karena pengguna bisa melihat proses lain di layar yang sama.
Episode ini membangun disiplin performa CatatanKu dengan urutan yang benar: ukur dulu, optimasi kemudian. Tanpa pengukuran, optimasi hanyalah tebakan — dan tebakan biasanya salah sasaran. Kita bahas tiga domain utama: memori, startup time, dan CPU — plus tool profiling tiap framework.
Startup → ≤ 1.5 detik sampai window interaktif
Memori → ≤ 150 MB idle dengan 10.000 catatan
Pencarian → ≤ 50 ms untuk query FTS pada 10.000 catatan
Scroll → 0 frame drop saat menggulir daftar panjangJadikan metrik ini regression guard: jalankan di CI (episode 17), gagalkan build jika melampaui ambang. Performa yang tidak diukur akan menguap perlahan tanpa disadari.
Kebocoran memori di desktop terjadi saat objek tidak lagi dipakai tetapi tetap dirujuk — GC (JS/Dart) atau refcount (Rust) tidak bisa membebaskannya. Gejalanya: aplikasi makin berat seiring pemakaian, hingga "out of memory".
Polanya sulit dirasakan dalam sesi pendek — itulah mengapa pemantauan jangka panjang penting. Tiga pola umum di CatatanKu:
// ❌ tambah listener tiap render, tidak pernah dibersihkan
function initEvents() {
window.addEventListener("resize", onResize);
}
// ✅ kembalikan cleanup, panggil saat tidak dibutuhkan
function initEvents() {
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}// ❌ interval tetap hidup setelah fitur mati
setInterval(() => checkSync(), 5000);
// ✅ simpan id dan clear saat fitur tidak aktif
const syncTimer = setInterval(() => checkSync(), 5000);
// ... clearInterval(syncTimer) saat logout / window ditutupCache hasil query yang tidak pernah dibersihkan
→ cache TTL / LRU, hapus entri lama--trace-gc untuk GC log. Bandingkan heap setelah sesi panjang.valgrind --tool=memcheck atau heaptrack untuk leak di sisi Rust.Note
Aturan praktis: cek memori setelah skenario yang berulang (buka 100 catatan → tutup → buka lagi → tutup). Heap yang terus naik tanpa turun adalah tanda kebocoran. Ingat pula memori di sisi backend: di Electron, main process punya heap sendiri yang juga bisa bocor.
Startup = waktu dari klik hingga pengguna bisa berinteraksi. Sumber keterlambatan umum:
1. Tampilkan UI segera dari cache memori (daftar terakhir)
2. Muat data lengkap secara async (episode 9)
3. Defer pekerjaan berat: indexing setelah window interaktif
4. Lazy-load modul/plugin saat benar-benar dipakai// ❌ muat semua di awal
const { sqlite } = require("./storage-heavy");
// ✅ muat saat dibutuhkan
async function openDatabase() {
const { openDb } = await import("./storage-heavy");
return openDb();
}Di Flutter, pola serupa dengan memindahkan inisialisasi DB/state ke FutureBuilder atau loading screen ringan yang menampilkan cache — bukan memblokir sampai semua siap.
Saat UI tersendat, CPU profiler menjawab "siapa yang makan waktu?" — bukan tebakan. Di web stack, Chrome DevTools → Performance; di Flutter, DevTools → CPU profiler.
1. Reproduksi skenario (scroll daftar 10.000 catatan)
2. Rekam profil selama 10-30 detik
3. Cari fungsi dengan self-time terbesar
4. Optimasi yang paling besar pengaruhnya
5. Ukur ulang — bandingkan dengan baselineOptimasi klasik yang sering muncul dari profil CatatanKu:
import { memo } from "react";
const NoteRow = memo(function NoteRow({ note }: { note: Note }) {
return <li>{note.title}</li>;
});1. Render ulang seluruh list setiap keystroke pencarian
2. Indexing di thread utama (episode 9)
3. JSON.stringify besar di event handler (event loop macet)UI yang berat menggambar ulang setiap frame. Hindari menyalakan ulang seluruh panel saat satu item berubah; batasi re-render ke subtree yang berubah. Di Flutter, const constructor dan RepaintBoundary membatasi area repaint. Ukur frame drop (jank) dengan profiler rendering, bukan perasaan.
| Framework | Memori | CPU | Startup |
|---|---|---|---|
| Electron | DevTools Heap | DevTools Perf | chrome://tracing / --profiler |
| Tauri | DevTools (webview) + valgrind (Rust) | Perf + cargo flamegraph | binary profile (perf/Instruments) |
| Flutter | DevTools Memory | DevTools CPU | DevTools Startup (flutter run --profile) |
Inti yang harus dibawa pulang:
Di episode 17 selanjutnya kita mengotomasi semua kualitas yang sudah dibangun: CI/CD desktop — build matrix OS, signing, dan release automation dengan GitHub Actions. Sampai jumpa di episode 17!