Hijrah Backend (7): Dari Paket TCP ke Time-Series Database

10 September 2026 oleh Faiq Najib Al-Aziz


Setiap detik, armada bicara. Puluhan ribu tracker GPS mengirim paket TCP berisi posisi, status mesin, tinggi sinyal, dan sistem wajib menelan semuanya, sepanjang hari, tanpa kampanye “ayo hemat kirim data” hahaha.

Di episode pertama saya singgung: sistem lama menerima dan menulis dalam satu layanan yang sama. Sistem baru membelahnya menjadi tiga panggung: listener yang hanya menerima dan menerjemahkan paket; antrean pesan di tengah; dan consumer yang menulis ke database. Episode ini cerita tiap panggungnya, termasuk dua kali saya tersandung di antaranya hehe~

Kenapa Ada Antrean di Tengah Jalan #

Pertanyaan yang wajar: bukannya tambah hop berarti tambah lambat?

Logikanya begini: kecepatan menerima paket dan kecepatan menulis ke database itu dua kecepatan yang berbeda. Armada tidak peduli database sedang sibuk, tracker tetap kirim. Kalau penerimaan dan penulisan diikat satu sama lain, database yang lambat membuat penerimaan ikut lambat, dan data yang gagal masuk cuma bisa berharap device-nya baik hati mengirim ulang.

Antrean memisahkan dua kecepatan itu. Listener menerima secepat armada bicara dan menitipkan paket ke antrean; consumer menarik dari antrean secepat database sanggup. Lonjakan siang hari diserap buffer. Tapi, dan ini pelajaran besar episode ini, setiap komponen baru membawa cara gagal yang baru. Dan saya menemukannya dengan cara yang benar-benar klasik: langsung di produksi hahaha.

Ketika Broker Tidur: Producer yang Membuang Segalanya #

Skenario yang tidak pernah ada di sistem lama: bagaimana kalau antreannya mati? Sistem lama tidak punya mode gagal ini, dia insert langsung; kalau database-nya ngambek, ya device yang coba lagi nanti. Sistem baru saya punya jawaban pertama yang sekarang saya sesali: kalau koneksi ke broker putus… pesan dibuang.

Bungkam, efisien, dan mengerikan. Data posisi kendaraan pelanggan dibuang diam-diam karena infrastruktur sedang bersin.

Perbaikannya berlapis. Pertama: jangan menyerah cepat, retry bertingkat beberapa detik, sehingga broker yang sekadar tersendat tak menjatuhkan satu paket pun. Kedua: kalau benar-benar mati lebih lama, jangan buang diam-diam, buing keras-keras: log error yang jelas, bukan peringatan yang bisa ditemukan tiga minggu kemudian. Ketiga, dan favorit saya: backpressure yang jujur. Antrean saya berkapasitas ±8 ribu paket;1 kalau penuh, Send() ikut menunggu, listener otomatis melambat, dan tracker di ujung sana akan retry TCP. Hasil akhirnya filosofis: data tidak hilang, dia hanya datang terlambat. Sumber yang menunggu itu bukan kegagalan; dia mekanisme penyimpan termurah yang pernah ada hehe.

Komentar di kode producer saya sampai mendokumentasikan rantai ini dengan bangga: antrean penuh → Send() blok → listener melambat → device retry. Rantai yang tadinya terlihat seperti bug ternyata dirancang sebagai pengaman.

Disiplin yang Salah Tempat #

Panggung kedua: consumer yang menulis ribuan baris lokasi sekaligus (bulk insert). Di sini saya pernah memasang kesalahan yang ironis: dalam loop penulisan massal, kalau satu baris gagal, fungsi langsung return err, dan seluruh sisa lokasi dalam gundukan itu ikut dibuang.

Yang membuatnya lucu: sistem PHP lama justru continue di kasus ini. Versi Go saya yang “lebih disiplin soal error” ternyata lebih boros data daripada versi yang saya anggap santai. Perbaikan pertamanya sembilan huruf: return menjadi continue, plus log supaya baris yang gagal tetap kelihatan. Lalu diteruskan ke semua sender lain, dan ditutup penyetelan: buffer konsumen dinaikkan sepuluh kali lipat dengan worker yang ikut ditambah, semuanya tercatat di komentar config sebagai keputusan, bukan angka misterius hahaha.2

Pelajarannya menempel sampai sekarang: perhatian pada error itu bagus; menyerah total karena satu error bukan disiplin, itu drama. Di pipeline data, keputusan antara continue dan return itu keputusan hidup-mati data, dan dia layak dipikirkan serius setiap kali muncul.

Database yang Mengerti Waktu #

Panggung terakhir: data lokasi itu data waktu, nilainya di tautan (kapan, di mana), dan barisnya lahir tanpa henti. Untuk ini saya pakai PostgreSQL dengan ekstensi time-series: tabel lokasi jadi hypertable yang terpartisi otomatis per waktu.

Dua kemewahan yang tidak saya dapat di sistem lama. Pertama, kompresi: chunk-chunk data yang sudah tua dikompresi otomatis, lebih dari seratus chunk sekarang hidup hemat, dan index tertentu dibangun langsung di atas bentuk terkompresi tanpa perlu membongkarnya. Kedua, retensi yang jalan sendiri: kebijakan mingguan membuang data lebih tua dari batas yang ditentukan. Sistem lama merapikan data lama secara manual, kala itu, dengan berdoa. Sekarang database-nya yang punya kalender hehe~

Kombinasi keduanya mengubah pertanyaan “kapan kita beres-beres data?” dari kenaifan menjadi konfigurasi.

Pelajaran #

  1. Setiap komponen baru punya cara gagal baru. Menambah antrean berarti harus menjawab sejak awal: kalau broker mati, apa yang terjadi pada data?
  2. Gagal boleh, gagal diam-diam jangan. Retry, lalu error yang nyaring. Warning yang sabar menunggu dibaca itu lobang.
  3. continue vs return di loop massal adalah keputusan hidup-mati data. Satu kata mengubah “satu baris gagal” menjadi “seribu baris dibuang”.
  4. Backpressure itu fitur. Saat kapasitas habis, membiarkan sumber menunggu adalah penyimpanan termurah yang ada.
  5. Retensi otomatis menggantikan ritual. Apa yang dulu rutin manual dan penuh doa, kini cuma kebijakan yang jalan sendiri.

Episode berikutnya episode terakhir berseri ini: refleksi hampir dua tahun, yang akan saya kerjakan beda kalau mengulang, dan bagian yang paling saya syukuri. Sampai ketemu di penutup.

Sekian. Salam.


  1. Kapasitas infrastruktur (ukuran antrean, jumlah worker, retensi) ditampilkan sebagai orde besaran; yang penting untuk cerita adalah arah dan alasannya. ↩︎

  2. Potongan kode dan angka konfigurasi disederhanakan, bentuk aslinya lebih panjang dan membawa detail internal. ↩︎