Rule engine AdOps punya tiga bagian yang bergerak. Dispatcher menyapu MongoDB setiap 10 detik mencari rule yang waktu eksekusinya sudah lewat, lalu mengantrekan pekerjaan. Antrean berbasis Redis membawa satu job per ad account. Worker mengambil tiap job, membaca Meta Insights, mengevaluasi kondisi, menjalankan action, lalu mencatat persis apa yang dilakukannya. Inilah yang terjadi antara menyimpan sebuah rule dan melihat budget berubah.
Apa yang jalan setiap 10 detik?
Sapuan cron di dispatcher. Pada tiap tick ia menanyakan koleksi rule untuk semua rule berstatus ACTIVE yang jadwal eksekusi berikutnya sudah lewat, dengan rentang tanggal opsional pada rule tetap dihormati.
Sapuan itu dikonfigurasi menunggu sampai selesai, artinya tick berikutnya tidak bisa masuk ke sapuan yang masih berjalan. Cara jujur menyebut jaminannya: tick tidak pernah bertumpuk. Itu bukan janji bahwa rule menembak dalam 10 detik sejak jatuh tempo — sapuan menelusuri rule jatuh tempo satu per satu, dan satu ad account dengan banyak campaign serta respons Graph yang lambat menahan antrean di belakangnya. Rule menembak pada sapuan pertama setelah jatuh tempo, dan di bawah beban, “sapuan pertama” adalah besaran yang berubah-ubah.
Setelah sebuah rule dikirim, dispatcher menghitung ulang waktu eksekusi berikutnya: interval pengecekan ditambahkan ke waktu sekarang, atau, pada mode timetable, slot berikutnya di grid tujuh hari — memindai maju sampai tujuh hari ketika hari ini sudah habis slotnya.
Kenapa dispatcher tidak bisa mengubah campaign?
Karena tidak ada kode di dalamnya yang menulis ke Meta. Satu-satunya panggilan Graph API dari dispatcher adalah panggilan baca: ia mendaftar campaign atau ad set milik tiap ad account, meminta nama dan waktu pembuatan, dengan filter rule diterjemahkan ke parameter filtering milik Meta dan pagination diikuti kursor demi kursor.
Semua yang ditulisnya masuk ke dua field di MongoDB — timestamp eksekusi berikutnya dan terakhir. Semua perubahan status, penulisan budget, penggantian nama, dan duplikasi hidup di worker, proses terpisah. Pemisahan itu batas radius kerusakan yang nyata, bukan sekadar kebijakan: deploy dispatcher yang keliru tidak bisa mengubah akun pengiklan, karena kemampuan itu memang tidak ada di dalamnya.
Pengambilan entitas dicoba 3 kali total, dengan jeda 1 detik lalu 2 detik. Kalau percobaan ketiga gagal, ad account itu dilewati untuk eksekusi ini, bukan menggagalkan seluruh rule, dan sapuan berikutnya mencoba lagi.
Bagaimana satu rule menjadi sejumlah job?
Dengan dipecah per ad account. Rule yang dipasang di tiga ad account menghasilkan tiga job, masing-masing membawa access token, definisi rule, sebuah batch id, daftar campaign yang ditemukan dispatcher untuk akun itu, dan akunnya sendiri.
Job diantrekan tanpa percobaan ulang dan dihapus dari antrean baik saat selesai maupun saat gagal. Bentuk ini disengaja dan perlu dipahami: antrean tidak memutar ulang pekerjaan yang gagal. Pengulangannya adalah evaluasi terjadwal berikutnya. Rule berinterval 15 menit praktis mencoba lagi 15 menit kemudian, dan itu biasanya yang Anda inginkan untuk otomasi yang menyentuh spend hidup — ledakan pengulangan setelah gangguan Meta bukan kebaikan.
Batch id itulah yang menyatukan kembali satu eksekusi di antarmuka. Setiap catatan yang ditulis worker membawanya, karena itu layar Rule Logs bisa menampilkan satu baris per batch lalu dibuka menjadi detail per campaign.
Berapa panggilan Meta API untuk satu evaluasi?
Lebih sedikit daripada versi naifnya, dan bukan angka tetap. Sebelum mengevaluasi, worker mengelompokkan kondisi rule berdasarkan periode laporan, membuang field metric yang kembar di dalam tiap kelompok, lalu mengirim semuanya sebagai satu panggilan Meta Batch API per campaign. Rule dengan enam kondisi yang tersebar di Today dan 7 hari terakhir menjadi dua permintaan berkelompok di dalam satu batch, bukan enam panggilan.
Sebagian metric key tidak pernah masuk pengambilan itu, karena bukan field Insights. Delapan key dikecualikan dan diselesaikan dari sumber lain: dua metric jam, custom metric, daily budget, umur campaign, remaining budget, dan lifetime budget termasuk di dalamnya. Field budget dibaca dari objek campaign hanya ketika rule benar-benar merujuknya, dan kondisi metric-lawan-metric mengambil metric pembandingnya secara terpisah — jadi perbandingan lintas metric yang membuat rule ekspresif juga berbiaya satu perjalanan tambahan per kondisi.
Pengambilan utama untuk evaluasi rule dikirim dengan unified attribution setting milik Meta, itu sebabnya ambang ROAS di dalam rule sejalan dengan ROAS level akun yang dilaporkan dashboard.
Bagaimana engine memutuskan, lalu bertindak?
Setiap kondisi diselesaikan menjadi sebuah angka dan diuji dengan salah satu dari 6 operator. Sebagian besar metric datang langsung dari respons Insights, dengan metric key bertitik dicocokkan ke rincian action type; delapan key khusus punya penyelesainya sendiri, termasuk custom metric yang dicari di Google Sheets saat evaluasi. Operator AND atau OR pada task kemudian melipat hasil masing-masing kondisi menjadi satu putusan.
Kalau putusannya lolos, action jalan — tetapi tidak buta. Ketiga action budget membaca ulang daily budget yang live dari Meta tepat sebelum menulis, dan ketiga action penamaan membaca ulang nama campaign yang live, sehingga perubahan persentase atau penambahan teks berlaku terhadap kondisi campaign saat ini, bukan terhadap snapshot basi. Setiap penulisan lalu melewati satu pembungkus terinstrumentasi, yang mencatat request dan respons Graph dan, saat gagal, mendorong nama fungsi, pesan, stack, isi request persis, serta respons error Meta ke error log.
Error log itu melakukan deduplikasi. Pesan dinormalkan — timestamp, UUID, alamat IP, id akun, dan id numerik panjang disamarkan — lalu dikelompokkan berdasarkan nama fungsi plus pesan yang sudah dinormalkan, dengan penghitung kejadian, waktu pertama dan terakhir terlihat, 10 sampel parameter dan respons terakhir, serta status active, resolved, atau ignored. Kegagalan berulang mengerucut jadi satu baris dengan penghitung, bukan banjir baris.
Apa yang dicatat engine?
Dua tingkat catatan, plus cooldown.
Per batch, satu baris benchmark: rule, ad account, jumlah applied dan affected item, tanggal mulai, execute count, dan status yang bergerak dari RUNNING ke DONE, dengan progres dilaporkan ke antrean saat worker menelusuri daftar campaign. Baris itu dibuat lewat upsert berkunci batch id, jadi job yang dikirim ulang memakai baris audit yang sama alih-alih meninggalkan baris setengah jadi yang kembar.
Per campaign, per task, per eksekusi, satu catatan eksekusi: action, apakah benar-benar jalan, nilai sebelum dan sesudah bila ada yang berubah, setiap kondisi dengan status pass atau fail beserta angka yang dilihatnya, snapshot campaign, ad account, timestamp next execute, total waktu eksekusi, dan jejak 4 tahap bernama beserta durasi detiknya. Jejak itulah yang dibaca laporan slow rule di layar Benchmark ketika Anda bertanya rule mana yang berat.
Cooldown ditulis sebagai timestamp next execute per campaign dan per task setelah sebuah action menembak. Ketika rule dievaluasi ulang sebelum waktu itu, eksekusinya dicatat sebagai skipped dengan alasan next_execute_not_reached — satu baris eksplisit, bukan diam tanpa jejak.
Di mana batasnya?
Ada empat yang layak disebut terus terang, karena mengetahuinya mengubah cara Anda menulis rule.
Latensi pengiriman tidak dibatasi. Sapuannya berurutan. Banyak rule di banyak akun mendorong interval efektifnya melar.
Tidak ada percobaan ulang di level antrean. Evaluasi terjadwal berikutnya adalah pengulangannya.
Dispatcher berjalan sebagai satu instance supaya rule yang jatuh tempo dikirim satu kali; worker-lah bagian yang melebar ke banyak core.
Endpoint health hanyalah penanda hidup. Tiap service menyediakan rute health yang mengembalikan status dan uptime proses; ia tidak memeriksa MongoDB, Redis, atau antrean, jadi jawabannya “proses menyala”, bukan “dependensinya sehat”. Pantau execution log untuk memastikan pekerjaan benar-benar mengalir.