Membangun Sistem Terdistribusi Sehat: Lesson dari NATS Fleet
Ditulis oleh: Ferli Deni Iskandar
Tanggal: 2026-05-27
Disclaimer: Pengalaman operasional pribadi dalam membangun infrastruktur pesan terdistribusi untuk AI agents.
Intro: Ketika Sistem Sederhana Tidak Cukup
Enam bulan lalu, saya memutuskan untuk menggantinya sistem handoff project dari file-based ledger menjadi message queue terdistribusi. Alasan sederhana: satu user, banyak sesi Claude yang berjalan paralel (MBP + tablet), masing-masing perlu tahu apa yang sudah diputuskan. Kalau cuma text file, ada race condition; kalau polling API, latency tinggi.
Pilihan saya jatuh pada NATS JetStream. Bukan Kafka (overkill), bukan RabbitMQ (kompleks), bukan Redis (kurang durability). Alasan: minimal dependencies, append-only semantic sesuai dengan workload saya (handoff log), dan dokumentasi yang jelas.
Enam bulan kemudian, fleet ini masih hidup dan sehat. Pelajaran apa yang saya dapat?
1. Stream ≠ Sistem Penyimpanan; Stream = Log Durabel
Awal-awal saya pikir JetStream adalah “database yang ngebuffer”. Salah. JetStream adalah log terstruktur: setiap pesan di-append atomik, tidak pernah di-update. Kalau mau state terakhir, parse ulang log atau maintain separate index.
Praktik: Saya jaga 7 stream terpisah untuk kategori berbeda:
HANDOFF_HOME(209 MiB) — cross-project decisionsHANDOFF_PROJECT— per-project session stateMEMORY_CLAUDE— user memory snapshotsDSB_EVENTS— search index updates- dll.
Karena schema append-only, retention policy adalah satu-satunya concern: kalau tidak ada policy, disk habis. Saya set berbasis size (max 500 MiB per stream, sebagian sudah tercapai untuk HOME). Policy berbasis waktu (keep last 30 hari) kurang cocok kalau ada idle period; lebih baik size-based untuk operational stability.
2. Consumer Lag = Invisible Canary
Enam bulan bekerja hanya karena consumer lag = 0. Artinya: tidak ada pesan yang tertunda di subscriber. Kalau lag > 0, subscriber ketinggalan atau crash — sistem jadi diam-diam inconsistent.
Setup saya:
- 3 consumer daftar (one per device: MBP, tablet, server)
- Tiap sesi code agent subscribe ke
MEMORY_CODEX,MEMORY_CLAUDE,DSB_EVENTS - Kalau satu consumer ketinggalan >1000 pesan, ada alert (belum automated, tapi mudah dideteksi via
nats consumer info)
Gotcha: Kalau subscriber crash, lag tidak auto-rewind. Harus manual nats consumer reset — bukan disaster, tapi perlu visibility.
3. Cold-Start Query = Invisible Spike di SLO
Tiga hari lalu saya set per-agent SLO profiles:
- P95 latency harus < 50ms untuk default
- < 200ms untuk nadia (Nadia agent, lebih tolerant)
Pertama kali run, semua merah. P95 = 2447ms!
Investigasi: “cold-start” query — first query to daemon setelah startup/long idle, SQLite scan full table, embedding model cold, semua concurrent. One-time cost, bukan sustainable.
Fix: Filter outlier dari window SLO, atau adjust threshold ke 150ms (realistic untuk warm). Lesson: SLO perlu aware cold-start behavior, bukan assume uniform.
4. Retention Policy adalah Compliance Tool
NATS dokumentasi menyebut “retention” sebagai storage saja. Salah. Retention = visible boundary antara “ingat” vs “lupa”.
Saya jaga MEMORY_CLAUDE selama 90 hari (rewind ke apa yang saya learn 3 bulan lalu). HANDOFF_PROJECT selama 30 hari (project decision cache). Kalau mau compliance audit trail, tinggal set nats stream export — legal hold.
Compliance case: Kalau ada satuan kerja minta “tunjukkan apa yang Pak FD putuskan tentang Security Audit tahun lalu”, saya tinggal grep handoff log atau NATS JSON export. Ledger immutable, timestamp otomatis.
5. Operational Visibility = Dashboard + Log
Saya maintenance satu shell script nats-daily-report.sh yang ping setiap komponen:
- Broker status
- Stream inventory (count, size, lag trend)
- Per-consumer last delivery timestamp
- P95 latency via daemon stats
Jalankan daily via cron, output simpan di knowledge-archive. Kalau ada anomali (lag suddenly spike, error rate > 0%), report visible immediately.
Opsi komersial: Nats.io punya cloud monitoring. Tapi untuk single-user development, script lokal sudah cukup — visibility adalah hadiah gratis.
6. Disaster Recovery adalah Backup Durabel, Bukan Replication
NATS JetStream not designed untuk disaster recovery lintas-region. (Ada clustering, tapi kompleks.) Solusi saya: backup daemon ke object storage (B2) daily, restore script tested (tidak pernah perlu, tapi tahu cara).
Last backup: 2026-05-25. If MBP corrupted, restore ke B2 object, spin daemon, continue. RPO = 1 hari (acceptable untuk development).
Lesson: DR adalah separate concern dari JetStream — JetStream hanya durabel lokal. Untuk distributed DR, pairing dengan object storage atau multi-region replica.
Kesimpulan: Sederhana > Sempurna
Enam bulan dengan NATS JetStream mengajarkan satu hal: durability-by-design > high-availability magic. Saya tidak pakai replication, tidak pakai sharding, tidak pakai consensus protocol. Cuma append log, stable consumer, sensible retention.
Hasil: sistem yang sehat, visible, dan maintainable oleh satu orang.
Mungkin boring. Tapi di engineering, boring = production-ready.
Lampiran: Quick Operational Checklist
- Set retention policy (size atau time, tergantung workload)
- Monitor consumer lag daily
- Backup stream export weekly
- SLO profile jangan assume uniform latency — filter cold-start
- Stream ≠ database — maintain separate state store kalau perlu read-optimized access
- Dokumentasi stream schema (satu MD per stream)
Ditulis untuk fdiskandar.com — sharing operational realities, bukan listicle productivity.
“Disclaimer: Tulisan ini adalah pandangan pribadi penulis dan tidak mewakili pandangan organisasi mana pun. Informasi yang disajikan bersifat edukatif dan tidak dimaksudkan sebagai nasihat profesional.”