Skip to content
Blog Programming

Bagaimana Saya Menyuruh Mesin yang Tak Bisa Dijangkau dan Tahu Kapan Ia Selesai

By Romi Fadli, S.Kom. 19 min read Tooling & Automation
On this page
A laptop and a Colab runtime each connecting outward to a broker in the middle, with the direct line between them crossed out

Pendahuluan

A. Bekerja di mesin yang tak bisa dipanggil

GPU laptop saya punya video memory, atau VRAM, sebesar 4 GB — memori tempat model harus muat supaya bisa berjalan di GPU. Itu mencoret bagian terberat dari beberapa project saya — membuat gambar dengan SDXL, menyintesis narasi dengan model suara yang besar — jadi bagian itu berjalan di runtime Google Colab — virtual machine yang dipinjamkan Google selama satu sesi — dengan GPU T4 dan sekitar 15 GB VRAM. Laptop memegang sisanya: persiapan, peninjauan, dan antarmuka di browser tempat saya memutuskan apa yang digenerate berikutnya.

Pembagian itu melahirkan persoalan yang sama sekali tak berhubungan dengan GPU. Antarmukanya di laptop; worker yang membuat gambar ada di Colab. Antarmuka harus menyuruh worker — mulai project ini, buat ulang gambar itu, berhenti — dan harus tahu apa yang sedang dikerjakan worker: item mana yang berjalan, mana yang selesai, dan apakah worker-nya masih hidup.

Cara yang paling jelas adalah memanggil worker secara langsung. Dan worker itu tak bisa dipanggil. Runtime Colab tak punya alamat tetap, tak punya port yang bisa dibuka ke luar, dan umurnya dihitung dalam jam. Ia bisa membuka sambungan keluar ke mana saja, tapi tak ada pihak luar yang bisa memulai percakapan dengannya. Setiap kali runtime baru dinyalakan, ia muncul di tempat berbeda dengan identitas berbeda, jadi alamat yang dicatat hari ini tak berguna besok.

Tantangannya: menyuruh mesin yang tak bisa saya jangkau mengerjakan sesuatu, dan tahu kapan ia sudah mengerjakannya.

Paruh kedua kalimat itu ternyata paruh yang sulit. Mengirim perintah itu sederhana begitu salurannya ada. Mengetahui — tahu bahwa worker masih hidup, bahwa perintahnya tiba tepat satu kali, bahwa tak adanya kabar berarti "masih bekerja" dan bukan "sudah mati sejam lalu" — itulah sumber hampir semua hambatan di bawah.

Gampang dirumuskan, canggung dibangun. Nyaris semua yang bisa meleset di sini, melesetnya dalam diam: status yang tetap bernilai benar sesudah pengirimnya mati, kegagalan yang ditelan di thread latar, dan batas yang diumumkan dengan satu angka tapi ditegakkan dengan angka lain. Tak satu pun menghentikan program. Semuanya membuat program terus berjalan dengan gambaran yang keliru tentang keadaan di seberang saluran.

B. Apa itu broker, dan kenapa diam adalah musuhnya

Saluran yang membuat semua ini jalan adalah message broker yang berbicara MQTT. Broker itu server kecil yang dihubungi oleh kedua pihak. Tak ada pihak yang perlu bisa dijangkau, sebab keduanya menghubungi keluar. Satu pihak mem-publish pesan ke topic bernama; tiap klien yang berlangganan topic itu menerimanya. Antarmuka mem-publish perintah dan berlangganan status; worker sebaliknya. Topic itulah yang menggantikan alamat: kedua pihak cukup sepakat soal namanya, bukan soal di mana pihak lain berada.

Dua fitur MQTT memikul sebagian besar beban di sini, dan keduanya pantas didefinisikan sebelum muncul. Pesan retained adalah pesan yang disimpan broker lalu diserahkan kepada siapa pun yang berlangganan belakangan, sehingga antarmuka yang dibuka lima menit sesudah worker memperkenalkan diri tetap tahu perkenalan itu. Last Will adalah pesan yang didaftarkan klien saat tersambung, yang akan di-publish broker atas nama klien itu bila sambungannya putus tanpa pamit dengan benar. Keduanya memang dirancang berpasangan: klien memperkenalkan diri dengan pesan retained dan mendaftarkan Last Will retained yang membantahnya, sehingga yang terakhir di-publish itulah yang dilihat pendatang baru. Seperti yang akan terlihat nanti, pasangan itu menutup lebih sedikit ketimbang kesannya.

Inti yang lebih dalam menyangkut diam. Pada pemanggilan langsung, tak ada jawaban sudah merupakan jawaban: pemanggilannya gagal. Pada broker, tak ada pesan tak berarti apa-apa. Worker bisa saja sibuk, bisa sudah mati, bisa sedang mengirim pesan yang tak pernah tiba, atau bisa sedang menerima perintah yang sudah pernah ia kerjakan. Tiap hambatan di bawah adalah tempat ketika diam itu bermakna ganda, dan tiap keputusan di bawah adalah cara mengubah satu jenis diam menjadi sinyal.

Itulah sebabnya artikel ini lebih banyak berbicara soal tahu ketimbang soal mengirim. Mengirim cuma butuh satu baris; tahu butuh keputusan di kedua ujung saluran, dan keputusan itu harus sepakat satu sama lain.

C. Enam hambatan yang menghalangi

Justru worker-nya yang tak bisa dijangkau. Bentuk yang biasa — klien memanggil server — di sini berjalan ke arah yang salah. Versi pertama menaruh web server di dalam runtime dan menjangkaunya lewat proxy port milik Colab sendiri, dan Colab memperingatkan serta membatasi cara itu. Polling penyimpanan bersama untuk mencari perubahan adalah opsi lain yang ditolak: lambat menyadari apa pun, dan boros selagi tak ada yang terjadi. Keduanya juga tetap menyisakan pertanyaan yang sama: bagaimana antarmuka tahu worker di seberang masih ada.

Dua bahasa membaca key yang sama dengan cara berbeda. Worker-nya Python dan antarmukanya TypeScript. Tiap item punya key komposit, kedua pihak mengurainya, dan baris code yang tampak sama berperilaku berbeda di kedua bahasa.

Pesan terakhir worker yang sudah mati tetap benar selamanya. Worker mengumumkan dirinya online lewat pesan retained. Kalau runtime-nya mati, tak ada yang menggantinya, dan tiap antarmuka yang dibuka sesudahnya diberi tahu worker itu hidup.

Batas yang diumumkan broker bukan batas sebenarnya. Broker publik mengumumkan satu angka untuk jumlah pesan yang boleh melayang sekaligus. Diukur, ia menerima seperdelapannya.

Kegagalan di thread jaringan lenyap. Library MQTT memanggil balik code saya dari thread miliknya sendiri, dan exception di sana ditelan tanpa jejak — ikut membawa pesan status yang paling pertama.

Menunggu konfirmasi pesan bisa menghentikan pesan sampai. Pengaman yang mengonfirmasi tiap kiriman berujung mencekik sambungannya, memaksa reconnect — dan tiap reconnect memutar ulang seluruh perintah lama, sehingga worker menghabiskan berjam-jam mengulang pekerjaan yang sudah selesai.

Urutannya berjalan dari bentuk salurannya, melewati apa yang wajib disepakati kedua ujung, sampai ke apa yang bisa dan tak bisa diketahui tiap ujung tentang ujung lainnya. Hambatan terakhir yang paling mahal sekaligus paling tak kentara, sebab ia menggabungkan tiga perilaku terpisah yang masing-masing tak berbahaya bila berdiri sendiri.

Metode

A. Kedua pihak menghubungi keluar

Inilah jawaban atas hambatan pertama: berhenti berusaha menjangkau worker, dan beri kedua pihak tempat ketiga untuk dijangkau. Worker menyambung keluar ke broker, persis seperti antarmuka:

python
c.will_set(f"{PREFIX}/{TOPIC_WORKER_STATUS}",
           json.dumps({"online": False, "ts": int(time.time())}),
           qos=MQTT_QOS, retain=True)
c.connect(MQTT_HOST, MQTT_PORT, keepalive=60)
c.loop_start()

Tak ada satu pun di sisi Colab yang perlu bisa dijangkau. Runtime membuka satu sambungan keluar lalu menjaganya tetap terbuka, dan segala sisanya lewat di sana ke dua arah.

Broker-nya sendiri broker publik, gratis dan sudah berjalan. Pilihan itu pantas digambarkan jujur: tak ada yang perlu dipasang maupun dirawat, dan itulah yang membuatnya cara yang tepat untuk mencari tahu apakah rancangannya bekerja sama sekali. Ia juga punya sifat yang ternyata berguna untuk pengujian. Di broker publik, worker palsu yang mem-publish ke topic yang sama tak bisa dibedakan dari worker asli — jadi seluruh antarmuka bisa dibangun dan diverifikasi terhadap script kecil yang berpura-pura jadi worker, sebelum worker sungguhan ada.

Di balik pilihan itu duduk alternatif yang ditolak. Versi pertama sistem ini menyajikan antarmukanya dari dalam runtime Colab dan menjangkaunya lewat proxy port Colab. Colab memperingatkan serta membatasi susunan itu. Memindahkan antarmuka ke laptop dan menjadikan worker pekerja murni — tanpa web server, tanpa halaman, cuma loop yang mendengarkan lalu bekerja — itulah yang membuat broker jadi perlu sejak awal.

B. Satu key, dua pengurai

Ini menjawab hambatan kedua. Tiap item punya key komposit seperti shot:chapter:scene:index atau obj:object:state. Worker mengurai key di Python; antarmuka mengurainya di TypeScript. Langkah yang paling alami adalah menerjemahkan baris Python satu per satu. Justru langkah itulah yang gagal:

python
# Python: sisanya masuk ke field terakhir
"a:b:c:d".split(":", 2)   # ['a', 'b', 'c:d']

# JavaScript: sisanya dibuang
"a:b:c:d".split(":", 2)   // ['a', 'b']

Nama method sama, argumen sama, dan artinya berbeda. Batas di Python menyatakan berapa kali memotong; di JavaScript, berapa potongan yang disimpan. Nama state yang memuat titik dua akan tiba utuh di worker dan terpotong di antarmuka, dan tak satu pun akan mengeluh.

Jadi antarmuka menyusun ulang perilaku Python secara eksplisit ketimbang bersandar pada kemiripannya:

typescript
const rest = key.slice(4)
const i = rest.indexOf(':')
if (i <= 0) return null
const objectId = rest.slice(0, i)
const stateId = rest.slice(i + 1)   // seluruh sisanya, seperti split(":", 2) menyimpannya

Aturan di baliknya lebih ketat ketimbang "samakan keluarannya". Antarmuka wajib menolak persis key yang ditolak worker. Antarmuka yang lebih permisif dari worker-nya menampilkan item yang takkan pernah diproses siapa pun, dan itu kegagalan senyap tersendiri. Pengurainya diuji terhadap 2.148 key — 2.131 key nyata dari project yang sedang berjalan ditambah 17 kasus tepi — masing-masing dibandingkan dengan hasil Python aslinya. Semuanya cocok.

C. Status yang hidup lebih lama dari pengirimnya

Ini menjawab hambatan ketiga. Worker memperkenalkan diri lewat pesan status retained, supaya antarmuka yang dibuka kapan pun langsung tahu ada tidaknya worker. Celahnya terletak pada apa yang terjadi ketika runtime mati. Tak ada yang mem-publish koreksi. Pesan retained itu tetap berbunyi online: true, dan akan terus berbunyi begitu kepada tiap pelanggan, selamanya.

Tiga mekanisme menutup celah itu, dan tiap satu menutup kasus yang terlewat oleh dua lainnya. Last Will, didaftarkan saat tersambung, adalah online: false retained yang di-publish broker bila sambungan putus dengan tidak rapi. Denyut mem-publish ulang status tiap tiga puluh detik, bahkan saat menganggur:

python
HEARTBEAT_SEC = 30

def _heartbeat_loop():
    while True:
        time.sleep(HEARTBEAT_SEC)
        if globals().get("pipe") is None:
            continue    # model masih dimuat
        publish_worker_status(True)

Denyut itu melewatkan ketukannya sampai model selesai dimuat. Selama pemuatan, main thread menahan interpreter lock Python dalam potongan panjang, dan ketukan yang ditembakkan saat itu cuma menghasilkan peringatan pengiriman; status untuk periode itu sudah diumumkan saat tersambung, jadi tak ada yang hilang karena menunggu.

Dan antarmuka menilai tiap status retained berdasarkan timestamp-nya sendiri, bukan berdasarkan saat ia tiba:

typescript
if (retained) {
  // pesan retained cuma foto lama, bukan bukti hidup
  stale.value = Date.now() - (m.ts ?? 0) * 1000 > STALE_MS
} else {
  bump()
}

Dengan STALE_MS 90 detik — tiga denyut yang terlewat — pesan retained berumur 204 detik dinilai basi walau isinya online: true. Yang menentukan: pesan retained tidak menyegarkan timer kebasian. Cuma pesan hidup yang menyegarkannya. Tanpa aturan itu, membuka ulang antarmuka akan me-reset jam memakai pesan yang umurnya justru sedang dipertanyakan.

Denyut itu juga akibat dari aturan tersebut, dan pantas dikatakan terus terang. Begitu cuma pesan hidup yang dihitung sebagai bukti hidup, worker menganggur yang tak mengirim apa pun tak bisa dibedakan dari worker yang mati. Denyut ada supaya worker yang menganggur tak dilaporkan mati.

Antarmuka mengubah semua ini jadi empat keadaan, bukan dua, sebab tiap keadaan menuntut tindakan berbeda: broker terputus (periksa jaringan), worker mati (jalankan notebook), worker siaga (tekan Mulai), worker bekerja (tunggu). Pembedaan siaga dan bekerja yang paling berarti. Menampilkan worker siaga dengan hijau yang sama seperti worker yang bekerja akan membuat orang menunggu run yang tak pernah mereka mulai.

D. Batas yang diumumkan, dan batas yang ditegakkan

Ini menjawab hambatan keempat. MQTT membolehkan broker mengumumkan berapa pesan yang belum dikonfirmasi boleh melayang dari satu klien sekaligus. Broker publik itu mengumumkan 32. Mengujinya langsung memberi jawaban lain:

bash
# publish QoS 2 yang melayang sekaligus, broker publik yang sama
announced receiveMaximum   32
4  in flight               all delivered
8  in flight               "Quota exceeded"

Angka yang mengatur perilaku adalah angka hasil ukur. Dan itu berarti, sebab worker yang mem-publish pembaruan progres sesudah tiap langkah kecil mengirim hampir dua ribu pesan yang nyaris sama dalam satu run. Terhadap batas empat pesan melayang, itu banjir.

Pembaruan progres dibatasi paling sering sekali tiap lima detik:

python
PROGRESS_MIN_GAP = 5.0

def publish_progress(name, force=False):
    now = time.time()
    if not force and now - _last_progress[0] < PROGRESS_MIN_GAP:
        return
    _last_progress[0] = now

Pembatas itu aman karena sifat progres itu sendiri. Ia ringkasan yang juga bisa dihitung ulang antarmuka secara mandiri dengan memindai ulang penyimpanan, jadi pembaruan yang terlewat membuat tampilan telat, tak pernah membuatnya keliru. Peristiwa per item — yang ini mulai, yang itu selesai — tidak dibatasi, sebab itulah yang tak bisa disusun ulang oleh antarmuka.

E. Exception yang tak dilihat siapa pun

Ini menjawab hambatan kelima. Ketika worker tersambung, library MQTT memanggil satu fungsi milik saya, _on_connect, yang mem-publish status pertama worker. Library memanggilnya dari thread jaringannya sendiri, sesaat setelah sambungan terbuka.

Misalkan fungsi itu membaca variabel yang menjelaskan model yang dimuat. Di notebook, code tingkat atas berjalan berurutan, dan variabel semacam itu didefinisikan lebih ke bawah — sesudah model selesai dimuat. Callback-nya lalu melempar NameError, dan exception yang dilempar di dalam callback library itu ditelan tanpa pesan. Status retained pertama tak pernah terkirim, dan antarmuka melaporkan worker mati padahal ia sedang sibuk memuat model sebelas giga.

Karena itu callback-nya membaca nilai tersebut dengan cara yang tahan terhadap ketiadaannya:

python
"model": globals().get("current_model_id"),

Bagian yang lebih berguna justru uji yang wajib menangkapnya. Simulator menjalankan cell-cell notebook dengan library berat diganti stub, dan klien MQTT palsu yang tak pernah memanggil on_connect akan membiarkan callback-nya tak tersentuh, sehingga memasukkan kembali kesalahan itu dengan sengaja tak membuat ujinya gagal. Versi palsunya berperilaku seperti library aslinya pada satu perilaku yang menentukan:

python
class FakeClient:
    def connect(self, *a, **k):
        calls.append(("connect",))
        if self.on_connect:
            self.on_connect(self, None, None, 0, None)

Dengan begitu, memasukkan kembali kesalahan itu menggagalkan simulasi — dan cuma itu bukti bahwa simulasinya menguji apa pun. Uji yang tak bisa gagal tidak membuktikan apa-apa.

F. Bagaimana menunggu kiriman bisa merusak kiriman

Ini menjawab hambatan keenam, dan inilah yang paling mahal. Gejalanya: antrean permintaan pembuatan ulang yang menghitung mundur lalu macet berjam-jam, sementara runtime tetap hidup dan GPU tetap sibuk.

GPU-nya sibuk membuat ulang gambar yang sudah jadi. Tiga perilaku bergabung melahirkannya, masing-masing tak berbahaya bila sendirian.

Yang pertama sebuah pengaman: tiap publish menunggu sampai lima detik untuk mengonfirmasi kiriman. Di pengubah PDF ia sungguh menolong — tanpanya, main thread langsung kembali ke pekerjaan berat, jabat tangan pengirimannya tak pernah tuntas, dan antarmuka menampilkan "0 dari 11 selesai" padahal semuanya sudah jadi. Obat yang sama di worker yang satu gambarnya makan sekitar delapan puluh detik berbuat sebaliknya. Penantian terjadi di thread yang menahan global interpreter lock Python selama generate, sehingga thread jaringan library kelaparan, melewatkan keepalive enam puluh detiknya, dan broker memutus sambungan.

Yang kedua, worker yang tersambung ulang dengan sesi persisten. Broker menyimpan tiap perintah yang ditujukan ke sesi itu — setiap permintaan pembuatan ulang — dan pada tiap reconnect ia mengirim semuanya lagi.

Yang ketiga, perintah pembuatan ulang mengerjakan persis yang diperintahkan. Menerimanya sekali lagi menghasilkan versi baru, sebagaimana mestinya. Worker-nya tidak bingung; ia mematuhi perintah yang sudah pernah ia patuhi.

Bukti kegagalan berbentuk begini selalu ada di log: baris yang mengumumkan sambungan muncul dua kali di tengah satu run, dan baris "selesai" yang berulang. Menghitung yang berbeda dibanding jam yang terpakai menunjukkan berapa banyak GPU habis untuk duplikat.

Yang menutupnya cuma satu argumen:

python
c.connect(MQTT_HOST, MQTT_PORT, keepalive=60,
          clean_start=getattr(mqtt, "MQTT_CLEAN_START_FIRST_ONLY", 3))

Clean start berarti broker tak menyimpan sesi, jadi reconnect tak membawa kembali apa pun yang lama. Antarmuka juga memakai sesi bersih, jadi kedua sisi mengikuti aturan yang sama.

Dua keputusan lagi membuang alasan terjadinya reconnect. Jalur publish tak menunggu, sementara kode balik library tetap diperiksa. Dan jaminan pengirimannya QoS 1, paling tidak satu kali, bukan QoS 2, tepat satu kali — di kedua sisi, sebagai satu kontrak. Itu aman cuma karena pesan status yang terduplikasi tak berbahaya: antarmuka menilai pesan dari isi dan timestamp-nya, bukan dari berapa kali ia datang.

Hasil dan Pembahasan

A. Apa yang dijumlahkan keenam keputusan itu

Antarmuka kini memulai, menghentikan, dan mengalihkan pekerjaan di mesin yang tak pernah bisa ia panggil, dan ia bisa membedakan empat keadaan yang menuntut tanggapan berbeda dari saya. Tabel di bawah menjajarkan keputusannya dengan apa yang digantikannya.

KeputusanYang dibeliAlternatif yang ditolak
broker yang dihubungi keluar oleh kedua pihakworker tak pernah perlu bisa dijangkauweb server di dalam Colab lewat proxy port-nya — dibatasi
broker publik lebih dulutak ada yang dipasang; worker palsu bisa menggantikan untuk ujimembangun infrastruktur sebelum rancangannya terbukti
pengurai disusun ulang, bukan diterjemahkan2148 key terurai identik di kedua sisimenyalin baris Python ke TypeScript
Last Will + denyut 30 dtk + kebasian 90 dtkworker mati tampil mati; yang menganggur tidakmemercayai online: true yang retained
progres paling sering tiap 5 dtk1867 pesan per run menyusut jadi tetesanmemercayai batas 32 yang diumumkan
simulator yang menembakkan callbackbug callback menggagalkan ujiklien palsu yang tak pernah memanggil balik
clean start, QoS 1, tanpa penantian memblokirtak ada perintah terputar ulang, tak ada sambungan tercekiksesi persisten, QoS 2, menunggu tiap publish

B. Diam bukan jawaban

Dijajarkan, hambatan-hambatan itu berbagi satu ciri. Di tiap satunya, ketiadaan sesuatu dibaca sebagai makna yang bukan maknanya.

Tak adanya koreksi sesudah online: true terbaca sebagai "masih online". Tak adanya error dari callback terbaca sebagai "callback-nya jalan". Tak adanya keluhan dari broker di bawah batas yang diumumkannya terbaca sebagai "masih di dalam batas". Tak adanya error dari pengurai key terbaca sebagai "terurai dengan benar". Dan tak adanya pemutusan di pandangan aplikasi terbaca sebagai "tak ada reconnect", sementara sesinya diam-diam memutar ulang pekerjaan berjam-jam.

Tiap perbaikan mengubah satu jenis diam menjadi sinyal. Last Will membuat kepergian terdengar. Denyut membuat worker menganggur bisa dibedakan dari yang mati. Timestamp membuat pesan lama bisa dikenali sebagai lama. Uji yang menembakkan callback membuat exception yang ditelan jadi kelihatan. Sesi bersih berarti reconnect tak bisa membawa kembali yang sudah dikerjakan.

Itulah pelajaran umumnya, dan ia berlaku bagi sistem berbasis pesan jauh melampaui yang ini. Ketika dua bagian sistem cuma berbicara lewat pesan, tak ada kegagalan yang mengumumkan dirinya — tiap kegagalan tampak seperti sepi. Pekerjaan rancangannya adalah memutuskan, untuk tiap jenis sepi, apa artinya, lalu memastikan ada yang bersuara ketika artinya ternyata yang lain.

C. Yang tidak dicakup

Semua ini sengaja berjalan di broker publik, sebab pertanyaan di tahap ini adalah apakah rancangannya bekerja sama sekali. Broker publik tak punya identitas dan kendali akses, jadi apa pun yang menangani data pribadi atau berjalan tanpa ditunggui butuh broker dengan identitas, kendali akses, dan enkripsi — pekerjaan besar dengan hambatannya sendiri.

Ini juga mengandaikan satu worker per jenis pekerjaan. Dua worker yang memungut perintah yang sama butuh cara membaginya, dan pesan yang tahan duplikat takkan lagi cukup dengan sendirinya.

Simpulan

GPU 4 GB memindahkan pekerjaan berat beberapa project ke runtime Colab, dan runtime Colab tak bisa dipanggil. Message broker menyelesaikan soal keterjangkauan itu dalam satu sore: kedua pihak menghubungi keluar, dan topic menggantikan alamat. Selebihnya soal tahu — apakah worker hidup, apakah perintah tiba satu kali, apakah tak ada kabar berarti kabar baik.

Keenam hambatan itu jarang berisik. Status retained tetap benar sesudah pengirimnya mati. Callback gagal dan tak ada yang bilang. Broker menegakkan batas yang besarnya seperdelapan dari yang diumumkannya. Dua bahasa membaca satu key dengan dua cara. Dan pengaman yang disalin dari project tempat ia menolong, digabung sesi persisten, bisa menghabiskan berjam-jam waktu GPU untuk mengulang pekerjaan yang sudah jadi.

Yang terbawa ke mana-mana adalah satu kebiasaan: untuk tiap diam dalam sistem berbasis pesan, putuskan artinya, lalu beri arti yang lain jalan untuk terdengar.

Istilah

MQTT
Protokol pesan publish/subscribe yang ringan. Klien tersambung ke broker, mem-publish pesan ke topic bernama, dan menerima pesan dari topic yang dilangganinya.
pesan retained
Pesan yang disimpan broker lalu diserahkan kepada tiap klien yang berlangganan topic itu belakangan, sehingga pendatang baru langsung tahu nilai terakhirnya.
Last Will
Pesan yang didaftarkan klien saat tersambung. Broker mem-publish-nya atas nama klien itu bila sambungannya putus tanpa pemutusan yang semestinya.
QoS
Quality of Service. Level 1 mengirim pesan paling tidak satu kali dan bisa menduplikasinya; level 2 mengirim tepat satu kali dengan ongkos jabat tangan empat langkah per pesan.
clean start
Opsi sambungan yang menyuruh broker membuang sesi tersimpan milik klien, sehingga tak ada yang diantrekan pada sambungan sebelumnya dikirim ulang.
GIL
Global interpreter lock milik Python. Cuma satu thread yang menjalankan code Python pada satu waktu, jadi thread yang menahannya dalam potongan panjang bisa membuat kelaparan tiap thread lain di proses itu.

Technologies covered

  • Python
  • TypeScript
  • Google Colab
  • EMQX
  • MQTT

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Related Articles

A stack of Markdown documents and an orange arrow leading to a browser page with a side menu, a heading, paragraphs, a dark code panel, and a table of contents with its active entry highlighted

Satu script yang mengubah dokumentasi Markdown satu project menjadi situs statis yang terbuka dari folder tanpa server maupun internet: menu, daftar isi yang mengikuti pembaca, tabel yang tak pernah terpotong, kode dengan tombol salin, dan tautan yang tetap berfungsi.

7 min read Read more →
An old layout with a grey banner and a new structure with a green banner, each holding a blue database, a purple document store, and an orange telemetry store; three coloured paths pass through one export package marked with a green check

Empat perintah yang memindahkan stack dari layout lama ke struktur baru melintasi versi mayor: export tanpa mengubah data sumber, import tiap store sesuai cara yang diizinkan versinya, verifikasi apa yang benar-benar tiba, dan rename modul sambil memindahkan datanya.

11 min read Read more →
Three image layer stacks, each with a version tag of its own colour, blue, orange, and green, each leading to an archive file banded in the same colour

Perintah yang memberi versi tersendiri pada tiap modul stack, mem-build image-nya di bawah tag yang berarti tepat satu build, menolak memakai ulang versi atau menelan compile yang gagal, dan mengarsipkan image menurut nama dan tag supaya versi tak saling menimpa.

7 min read Read more →