Kirim pesan chat
ID pesan dari klien, urutan per percakapan dari server, ack setelah tulis tahan lama, dan kursor baca. Itu yang bikin chat nggak kehilangan atau menggandakan pesan.
Pertanyaan
Bagaimana memastikan pesan chat sampai sekali, berurutan, dan tetap ada walau penerimanya offline?
Setelah baca ini, kamu bisa merancang ID pesan, cakupan urutan, alur acknowledgment, pengiriman saat offline, dan status belum dibaca tanpa menyimpan satu baris per pesan per penerima.
Prasyarat dan contoh
Kamu sudah paham idempotensi dan retry (M03), transaksi dasar (M05), dan partisi (M07). Koneksi yang bisa putus dibahas di M13.02.
Budi mengirim "oke, transfer sekarang" lewat jaringan seluler yang lemah. Aplikasi menunggu ack, nggak datang dalam 5 detik, lalu mengirim ulang. Server sebenarnya sudah menyimpan pesan pertama, cuma ack-nya hilang. Tanpa desain yang benar, Sinta melihat pesan itu dua kali. Dengan desain yang salah di arah sebaliknya (nggak ada retry), pesan hilang diam-diam.
Mekanisme
Dua ID yang beda tugasnya.
client_msg_id: UUID yang dibuat klien sebelum kirim. Tugasnya idempotensi: kirim ulang dengan ID yang sama nggak membuat pesan baru. Pasang unique constraint pada(conversation_id, client_msg_id).seq: bilangan bulat yang diberikan server, naik satu per percakapan (1, 2, 3, ...). Tugasnya urutan dan deteksi celah.
Cakupan urutan per percakapan, bukan global. Urutan global menuntut satu penulis buat seluruh
sistem, jadi bottleneck. Per percakapan, yang butuh urutan cuma peserta percakapan itu.
Tetapkan seq dengan satu penulis per percakapan, misalnya partisi berdasarkan conversation_id
(M07), atau di basis data dengan UPDATE conversation SET last_seq = last_seq + 1 ... RETURNING
dalam transaksi yang sama dengan insert pesan. Konsekuensinya: urutan antar percakapan berbeda
nggak didefinisikan, dan itu memang nggak perlu.
Alur kirim.
- Klien menyimpan pesan lokal dengan status "mengirim", lalu kirim
client_msg_id+ isi. - Server menulis pesan ke penyimpanan tahan lama (dengan
seq), baru membalas ack berisiclient_msg_iddanseq. - Klien menandai "terkirim". Kalau ack nggak datang, klien kirim ulang dengan
client_msg_idyang sama. Server melihat duplikat, nggak menulis lagi, dan membalas ack denganseqlama.
Pengiriman ke klien itu at-least-once, deduplikasi pakai ID bikin efeknya sekali. Ack dikirim setelah tulis tahan lama, bukan sebelum, supaya "terkirim" artinya benar-benar tersimpan.
Offline dan celah. Server nggak perlu tahu apakah penerima online. Pesan sudah tersimpan
dengan seq. Saat klien nyambung, ia kirim last_seq per percakapan, dan server membalas semua
pesan dengan seq lebih besar. Kalau klien menerima seq 41 lalu 43, ia tahu 42 hilang dan
meminta ulang. Jalur real-time hanya mempercepat, jalur sinkronisasi menjamin.
Status belum dibaca. Jangan simpan flag per pesan per penerima. Di grup 500 anggota, itu
500 baris untuk satu pesan. Simpan satu kursor per anggota: last_read_seq. Jumlah belum dibaca
= last_seq percakapan dikurangi last_read_seq anggota. Satu update kecil saat membaca, dan
satu angka murah buat badge.
Hitung. Percakapan aktif 30 pesan per menit, dengan 1 juta percakapan aktif itu sekitar 500.000 pesan per detik di puncak ekstrem. Dengan partisi per percakapan, beban tersebar, dan tiap partisi hanya melayani penulisan berurutan buat percakapan miliknya.
Saat rancangan ini gagal
- Ack sebelum tulis. Server membalas ack lalu crash sebelum menyimpan. Klien yakin sudah terkirim, pesan hilang. Ack harus mengikuti durabilitas.
- Urutan pakai waktu klien atau server. Jam berbeda, dua pesan di milidetik yang sama, urutan
bolak-balik di dua perangkat.
seqdari satu penulis menghindarinya. - Tanpa deduplikasi. Retry setelah ack hilang membuat pesan ganda.
- Celah nggak terdeteksi. Real-time dikirim tanpa
seq, pesan hilang di tengah, dan klien nggak punya cara tahu. - Percakapan panas. Grup dengan 100.000 anggota dan ribuan pesan per detik membuat satu penulis jadi bottleneck, dan fan-out ke 100.000 koneksi butuh strategi sendiri (mirip M13.05).
Contoh tandingan: chat internal dengan 30 pengguna nggak perlu sistem partisi, cukup satu tabel dan satu counter per percakapan. Mekanisme ID dan ack tetap dipakai karena murah.
Latihan
Klien mengirim pesan dengan client_msg_id = a1, server menyimpannya sebagai seq = 52, lalu
ack hilang. Klien mengirim ulang.
- Apa yang dilakukan server pada kiriman kedua, dan apa yang dibalas?
- Sinta offline, menerima sampai
seq = 50. Saat nyambung, apa yang terjadi? - Grup 200 anggota,
last_seq = 300. Tiga anggota punyalast_read_seq300, 295, dan 120. Hitung belum dibaca tiap anggota dan jumlah baris yang ditulis saat satu pesan masuk.
Lihat pembahasan
- Unique constraint
(conversation_id, client_msg_id)menolak insert kedua. Server mencari pesan yang ada dan membalas ack denganseq = 52yang sama. Nggak ada pesan baru. - Klien kirim
last_seq = 50. Server membalas pesan 51, 52, dan seterusnya sampailast_seqterkini. Kalau real-time sempat mengirim 53 lebih dulu, klien melihat celah (50 lalu 53) dan menunggu atau meminta 51 sampai 52. - Belum dibaca: 0, 5, dan 180. Satu pesan masuk menulis satu baris pesan dan menaikkan satu
counter
last_seq. Nggak ada baris per anggota. Update kursor baru terjadi saat anggota membaca.
Terkait
- Sebelumnya: M13.02 Kelola koneksi.
- Pola fan-out ke banyak penerima diperluas di M13.05 Buat feed.
- Idempotensi dari M03 dan transaksi dari M05 dipakai langsung di sini. Penerimaan tahan lama untuk event nanti dipakai pada rancangan D14 dan proyek B10 yang belum tersedia.
Catatan
- Asumsi: satu penulis logis per percakapan dan penyimpanan tahan lama dengan transaksi.
- Yang nggak dibahas: enkripsi ujung ke ujung, sinkronisasi multi-perangkat penuh, edit dan hapus pesan, serta lampiran media.
- Pendekatan ID plus kursor adalah pola umum, bukan klaim tentang aplikasi tertentu.
- Terakhir ditinjau: 26 Oktober 2026.