Selama berminggu-minggu, sebuah cron job di Elevare Digital terbangun sesuai jadwal, memeriksa antreannya, dan mencatat keberhasilan yang bersih. Ia menyetujui tepat nol draf. Sembilan belas konten tertahan menunggu. Tim baru mengetahuinya kemudian, setelah celah sunyi tersebut berkembang dari sebuah keanehan menjadi tumpukan pekerjaan (backlog) kecil. Tidak ada yang rusak (crash). Tidak ada peringatan (paging alerts) yang berbunyi. Sistem tersebut secara teknis sehat, namun secara fungsional mati.
Inilah kengerian sunyi dari pipeline otonom. Saat Anda menghilangkan peran manusia dalam prosesnya (human in the loop), Anda juga menghilangkan orang yang menyadari bahwa tidak ada yang sedang terjadi.
Pipeline yang Berjalan Sendiri
Elevare Digital menjalankan alur kerja konten yang sepenuhnya otomatis. Agen perangkat lunak menghasilkan draf. Sebuah cron approver yang terjadwal bertindak sebagai penjaga gerbang, meninjau draf tersebut dan mendorong item yang disetujui langsung ke tahap penerbitan. Tidak ada manusia yang membuka dasbor untuk menyetujui setiap batch. Intinya adalah agar mesin menangani pekerjaan yang membosankan sementara tim beralih ke masalah lain.
Di bawah model ini, kepercayaan menjadi antarmuka utama Anda. Anda percaya penjadwal (scheduler) akan berjalan. Anda percaya pekerjaan tersebut akan dieksekusi. Anda percaya pada exit code. Ketika log menunjukkan detak jantung yang stabil berupa respons 200 OK, Anda berasumsi pekerjaan sedang berjalan. Selama berminggu-minggu, detak jantung itu sempurna. Cron berjalan tepat waktu, setiap saat. Ia hanya saja tidak pernah melakukan pekerjaan yang sebenarnya.
Sembilan Belas Draf dan Tanpa Alarm
Penemuan itu terjadi secara tidak sengaja. Seseorang akhirnya menyadari bahwa antrean penerbitan telah menjadi sunyi, atau mungkin mereka memeriksa metrik hilir (downstream metric) dan melihat grafik yang datar. Apa yang mereka temukan adalah tumpukan sembilan belas draf yang sama sekali tidak tersentuh. Sang approver telah berjalan dengan rajin, mencatat keberhasilan setiap hari, namun tidak memproses satu pun dari draf tersebut.
Dalam alur kerja manual, seorang peninjau manusia akan menyadari kotak masuk yang kosong atau tumpukan item tertunda sejak hari pertama. Dalam versi otomatis, tidak adanya aktivitas terlihat persis seperti tidak adanya pekerjaan. Cron tersebut tidak memiliki manajer untuk dikecewakan. Ia hanya terus masuk kerja dan pulang lebih awal.
Dua Bug, Satu Hasil Kosong
Kegagalan ini memiliki dua penyebab. Tidak ada yang berupa kesalahan sintaksis, timeout, atau gangguan dependensi. Keduanya adalah kesalahan semantik yang mengubah sembilan belas baris valid menjadi tidak ada di mata mesin kueri (query engine).
Pertama, ketidakcocokan tipe (type mismatch). Agen yang menghasilkan draf menulis catatan yang ditandai sebagai article. Cron approver melakukan kueri khusus untuk tipe thread. Inilah jenis pergeseran (drift) yang terjadi ketika produsen dan konsumen berkembang di jalur yang sejajar. Satu tim—atau satu agen—memutuskan bahwa outputnya adalah sebuah artikel. Tim lain menulis konsumen dengan asumsi ia akan menyerap thread. Tidak ada sistem tipe yang memicu kesalahan saat kompilasi (compile-time error) karena ini kemungkinan besar adalah tag string yang longgar, mungkin berupa kolom JSON atau nilai varchar yang tidak dipaksakan. Database hanya tidak menemukan kecocokan dan mengembalikan set kosong. Bagi mesin, itu bukanlah kondisi kesalahan. Itu adalah jawaban yang benar untuk pertanyaan yang salah.
Kedua, sebuah inner join dalam kueri approver diam-diam menelan seluruh baris tersebut. Jika kueri menggabungkan tabel draf dengan tabel lain—mungkin pencarian untuk metadata, bendera status (status flags), atau aturan perutean (routing rules)—dan kondisi join gagal, maka inner join berperilaku persis seperti yang dirancang. Ia mengecualikan baris yang tidak cocok. Tidak ada baris yatim (orphan rows) yang muncul dalam set hasil. Tidak ada nilai null yang menandai masalah. Sembilan belas draf tersebut melewati kueri seperti air melewati saringan, dan lapisan aplikasi menerima daftar kosong yang bersih.
Karena kueri tidak mengembalikan baris apa pun, fungsi tersebut keluar dengan bersih. Tidak ada pengecualian (exception) yang muncul. Respons HTTP adalah 200 OK. Cron mencatat keberhasilan dan kembali tidur.
Jebakan "Processed Zero"
Inilah inti masalahnya. Dalam sistem berbasis antrean, seorang konsumen sering kali menemukan nol baris untuk diproses. Antrean menjadi kosong. Pekerja selesai dengan cepat. Log membaca processed: 0 dan tim membacanya sebagai kabar baik: kita mampu memenuhi permintaan. Itu adalah kondisi yang sehat.
Namun, processed: 0 menyandikan dua realitas yang sama sekali berbeda:
- Kondisi sehat: Nol diproses karena nol yang tertunda. Antrean kosong. Sistem dalam keadaan idle sesuai desain.
- Kondisi rusak: Nol diproses karena konsumen tidak dapat melihat pekerjaan tersebut. Antrean memiliki sembilan belas baris. Sistem buta, bukan idle.
Tanpa pemeriksaan independen pada kedalaman antrean (queue depth), kedua kondisi ini memancarkan telemetri yang identik. Keduanya terlihat sama di dasbor, terasa sama di agregator log, dan memicu keheningan yang sama di PagerDuty. Anda telah membangun strategi pemantauan yang mendeteksi saat pekerja berteriak, bukan saat ia berbisik melewati tumpukan pekerjaan nyata.
Menutup Celah
Elevare Digital memperbaiki masalah tersebut dengan mengubah apa yang mereka pantau. Mereka berhenti hanya mengandalkan tingkat kesalahan (error rates) dan status keberhasilan. Sebaliknya, mereka mulai memberikan peringatan pada celah antara pekerjaan yang tersedia dan pekerjaan yang selesai.
Setelah setiap batch, mereka sekarang menjalankan pemeriksaan invarian yang sederhana:
- Jika
processedadalah 0 dan baris yang tertunda (pending rows) lebih besar dari 0, picu peringatan dengan tingkat keparahan tinggi (high severity alert).
Aturan ini sengaja dibuat agnostik terhadap penyebabnya. Aturan ini tidak peduli apakah kegagalan tersebut disebabkan oleh filter yang buruk, join yang rusak, atau string enum yang salah ketik. Aturan ini hanya peduli bahwa ada pekerjaan yang tersedia namun tidak ada pekerjaan yang diselesaikan. Hal ini menggeser pemantauan dari “Apakah prosesnya mengeluh?” menjadi “Apakah pekerjaannya berjalan?”
Untuk mendukung hal ini, mereka memperlakukan kedalaman antrean (queue depth) sebagai metrik utama (first-class metric) yang dilacak dari waktu ke waktu, bukan sekadar pemeriksaan sesaat (spot-check). Jika produsen terus menambahkan baris sementara konsumen terus melaporkan keberhasilan, tren kedalaman tersebut akan menjadi bukti nyata (smoking gun). Sebuah cuplikan statis (static snapshot) mungkin bisa menipu, tetapi penumpukan antrean (backlog) yang terus meningkat tidak pernah berbohong.
Pelajaran untuk Sistem Otonom
Insiden Elevare mengandung beberapa aturan praktis bagi siapa pun yang menjalankan pipeline otomatis (hands-off pipelines).
Catat baris yang dipindai (scanned rows) secara terpisah dari baris yang diproses (processed rows). Konsumen mungkin mengeksekusi kueri yang menyentuh empat puluh baris, menyaring semuanya melalui kriteria yang buruk, dan melaporkan processed: 0. Jika Anda hanya mencatat jumlah akhirnya, Anda akan melewatkan interaksi hantu tersebut. Metrik baris yang dipindai mengungkapkan bahwa pekerja tersebut datang, melihat pekerjaan, lalu pergi dengan bingung. Celah antara baris yang dipindai dan yang diproses sering kali merupakan sinyal awal Anda.
Lacak kedalaman antrean sebagai deret waktu (time-series). Antrean yang kosong untuk sementara tidaklah masalah. Antrean yang tumbuh secara monoton sementara status pekerja tetap hijau (green) adalah masalah. Plotkan kedalaman terhadap throughput konsumen. Ketika keduanya menyimpang, segera selidiki, meskipun setiap pemeriksaan kesehatan (health check) menunjukkan hasil yang baik.
Uji konsumen terhadap output produsen yang sebenarnya, bukan sekadar mock. Unit test dengan data mock membawa asumsi dari pengujinya. Jika pabrik mock menghasilkan tipe thread dan konsumen mengharapkan tipe thread, pengujian Anda akan lulus sementara produksi gagal. Jalankan pengujian integrasi yang mengambil catatan aktual dari output produsen. Pastikan konsumen benar-benar dapat melihat apa yang ditulis oleh produsen.
Perlakukan tipe data dan nilai enum sebagai kontrak. Tag string yang longgar dalam blob JSON memang praktis sampai mereka menjadi titik kegagalan yang tidak terlihat. Definisikan skema secara eksplisit. Bagikan konstanta. Validasi payload pada titik temu antara produsen dan konsumen. Jika kontrak tersebut rusak, sistem harus gagal secara terang-terangan (fail loudly) di batas tersebut, bukan secara diam-diam di dalam klausa WHERE.
Kesimpulan Utama
Sistem otonom tidak gagal seperti manusia. Mereka tidak izin sakit, tidak melempar pengecualian (exceptions) setiap saat, atau meninggalkan dump crash yang jelas. Mereka mengembalikan 200 OK dan membiarkan inventaris membusuk. Jika peringatan Anda hanya mendengarkan teriakan, Anda akan melewatkan kegagalan yang paling mahal—yaitu kegagalan di mana semuanya tampak baik-baik saja tetapi tidak ada pekerjaan yang selesai.
Rancang observabilitas Anda untuk mengawasi celah tersebut. Ukur pekerjaan yang masuk dibandingkan dengan pekerjaan yang keluar. Ketika keduanya tidak lagi cocok, asumsikan mesin sedang berbohong kepada Anda. Karena terkadang, log keberhasilan yang sempurna adalah satu-satunya gejala dari sistem yang telah menjadi buta sepenuhnya.
