Migrasi worker aman bukan sekadar mengganti implementasi lama dengan yang lebih cepat. Di sistem terdistribusi, perubahan pada komponen performa-kritis sering gagal bukan karena throughput turun, melainkan karena perilaku operasional berubah: job diproses dua kali, cache menjadi stale, lock tidak pernah dilepas, retry membesar, atau ordering tidak lagi terjaga.
Pelajaran pentingnya sederhana: komponen internal boleh berubah, kontrak operasionalnya tidak boleh diam-diam ikut berubah. Karena itu, migrasi worker harus diperlakukan sebagai perubahan perilaku sistem, bukan hanya optimasi internal. Artikel ini membahas pola praktis lintas stack backend/cloud untuk memigrasikan worker, queue consumer, atau executor tanpa merusak konsistensi saat versi lama dan baru berjalan bersamaan.
Kenapa migrasi worker sering rusak walau benchmark lolos
Benchmark biasanya mengukur latensi, throughput, CPU, atau memori pada kondisi terkontrol. Masalah di produksi muncul di area yang jarang tertangkap benchmark:
- Perbedaan semantik ack/nack: kapan job dianggap selesai, kapan direqueue.
- Perbedaan retry: backoff, jumlah percobaan, atau klasifikasi error transient vs permanent.
- Perbedaan ordering: worker baru mungkin memproses paralel lebih agresif.
- Perbedaan lock: lease lebih pendek, clock drift, atau token ownership tidak dicek.
- Perbedaan cache: format value, TTL, atau strategi invalidasi berubah.
- Perbedaan timeout: job lama memegang resource lebih lama daripada lock TTL.
Jika worker lama dan baru aktif bersamaan, semua perbedaan kecil itu bisa muncul sebagai inkonsistensi. Karena itu, target utama migrasi adalah kompatibilitas perilaku, bukan sekadar kompatibilitas API.
Kontrak yang harus dibekukan sebelum rollout
Sebelum menyentuh implementasi, definisikan kontrak operasional yang wajib dipertahankan. Ini akan menjadi dasar canary, shadow traffic, dual-run, dan rollback.
1. Kontrak queue
- Apakah delivery bersifat at-least-once, at-most-once, atau best effort?
- Kapan consumer melakukan ack?
- Kapan job dianggap gagal permanen?
- Apakah ada dead-letter queue (DLQ)?
- Apakah ada kebutuhan ordering per key, tenant, atau aggregate?
2. Kontrak idempotensi
Dalam praktik, banyak sistem queue memberi jaminan at-least-once. Artinya duplicate processing harus dianggap normal, bukan anomali langka. Jika migrasi worker mengubah waktu ack, retry, atau timeout, duplicate job biasanya naik.
Tentukan idempotency key yang stabil, misalnya order_id + event_type atau job_id dari producer. Simpan hasil eksekusi atau status dedup pada storage yang konsisten terhadap kebutuhan bisnis.
3. Kontrak cache
- Format serialisasi harus backward/forward compatible selama masa transisi.
- TTL tidak boleh berubah diam-diam tanpa alasan.
- Strategi invalidasi harus sama: write-through, write-behind, cache-aside, atau explicit invalidate.
- Namespace key harus jelas jika format value berubah.
4. Kontrak lock
- Bagaimana lock diperoleh?
- Apakah lock punya lease/TTL?
- Siapa yang berhak melepaskan lock?
- Apa yang terjadi jika worker crash saat lock aktif?
Lock yang aman bukan hanya soal SET NX EX atau primitif serupa. Anda juga perlu ownership token agar worker yang sudah kehilangan lock tidak bisa menghapus lock milik worker lain.
Risiko utama saat worker lama dan baru berjalan bersamaan
Duplicate job
Kasus umum: worker baru mengubah urutan operasi dari “simpan hasil lalu ack” menjadi “ack lalu simpan hasil”. Jika proses crash setelah ack tetapi sebelum penyimpanan final, job hilang. Sebaliknya, jika timeout lebih pendek dan ack terlambat, broker bisa mengirim ulang job yang sebenarnya sedang diproses.
Mitigasi:
- Buat handler idempoten.
- Simpan status pemrosesan berbasis idempotency key.
- Pastikan timeout queue, runtime job, dan lock lease saling konsisten.
- Audit kapan ack/nack dilakukan.
Stale cache
Worker baru sering membawa perubahan struktur data hasil komputasi. Jika key cache tetap sama sementara format value berubah, consumer lain bisa membaca data lama dengan asumsi schema baru, atau sebaliknya.
Mitigasi:
- Versioning key, misalnya
user_profile:v2:{id}. - Gunakan masa transisi dengan pembacaan ganda bila perlu.
- Samakan TTL selama rollout kecuali ada alasan kuat untuk mengubahnya.
- Pastikan invalidasi terjadi pada event yang sama di kedua versi.
Lock orphan
Lock orphan terjadi ketika lock tertinggal karena worker crash, jaringan putus, atau proses berhenti sebelum release. Jika worker baru memakai lease lebih panjang atau refresh lock lebih jarang, dampaknya bisa memburuk: resource tampak sibuk padahal tidak ada worker aktif.
Mitigasi:
- Gunakan lease/TTL, jangan lock tanpa kadaluarsa.
- Gunakan ownership token untuk release.
- Monitor jumlah lock aktif, lock expired, dan lock acquisition failure.
- Siapkan prosedur pembersihan manual yang aman.
Backlog spike
Backlog sering naik bukan karena worker baru lebih lambat secara rata-rata, tetapi karena perilaku edge case berubah: retry lebih agresif, lock contention naik, atau dependency downstream lebih sering timeout.
Mitigasi:
- Pantau growth rate backlog, bukan hanya ukuran backlog saat ini.
- Bandingkan distribusi durasi job, bukan cuma rata-rata.
- Bedakan backlog karena slow processing vs poison message vs lock contention.
Ordering rusak
Jika sistem sebelumnya efektif memproses satu job per entitas secara serial, worker baru yang lebih paralel bisa memecah asumsi tersebut. Contoh: event cancel diproses sebelum create, atau update versi lama menimpa versi baru.
Mitigasi:
- Terapkan partitioning per key.
- Gunakan sequence number atau version check saat write.
- Jangan mengandalkan urutan konsumsi global jika broker tidak menjaminnya.
Pola migrasi worker aman yang bisa dipakai lintas stack
1. Canary rollout
Mulai dari sebagian kecil traffic atau sebagian kecil shard/partition. Tujuannya bukan hanya melihat error rate, tetapi juga mendeteksi perubahan perilaku yang halus.
Cocok untuk: perubahan implementasi internal dengan kontrak input/output sama.
Yang dipantau:
- success/failure rate per versi worker
- ack latency
- retry rate
- duplicate detection rate
- lock contention
- cache hit/miss dan stale read indicator
- backlog depth dan age of oldest message
2. Shadow traffic
Pada pola ini, worker baru menerima salinan job atau event yang sama, tetapi hasilnya tidak menjadi sumber kebenaran. Sangat berguna untuk memvalidasi parsing, durasi eksekusi, kebutuhan resource, dan determinisme hasil tanpa risiko langsung ke sistem utama.
Kelebihan: aman untuk validasi awal.
Keterbatasan: tidak selalu menangkap efek samping nyata seperti lock contention, ack semantics, atau interaksi dengan downstream write path jika shadow dijalankan secara read-only.
3. Dual-run dengan compare
Worker lama dan baru sama-sama memproses input yang sama, lalu hasilnya dibandingkan. Pola ini paling kuat untuk memverifikasi kompatibilitas perilaku, terutama jika Anda bisa membandingkan:
- output akhir
- decision path, misalnya retry vs drop
- durasi eksekusi
- key cache yang ditulis
- side effect yang seharusnya terjadi
Namun dual-run berisiko jika kedua worker melakukan write sungguhan ke resource yang sama. Karena itu, biasanya salah satu versi berjalan dalam mode observe-only atau menulis ke sink terisolasi.
4. Partition pinning
Alih-alih mencampur worker lama dan baru dalam satu stream yang sama, pin subset tenant, customer, shard, atau queue tertentu ke versi baru. Ini mengurangi risiko ordering conflict dan memudahkan rollback terarah.
Cocok untuk: sistem dengan kebutuhan ordering per key atau per-tenant isolation.
Desain kompatibilitas untuk queue, cache, dan lock
Queue: pisahkan delivery semantics dari business success
Job dianggap “selesai” oleh broker belum tentu berarti business operation benar-benar committed. Karena itu, letakkan boundary yang jelas:
- validasi input
- ambil dedup/idempotency state
- akuisisi lock jika dibutuhkan
- eksekusi side effect utama
- commit status hasil
- baru lakukan ack
Urutan ini tidak mutlak untuk semua sistem, tetapi prinsipnya sama: jangan mengakui selesai sebelum kondisi yang dibutuhkan untuk recovery benar-benar tercatat.
Contoh pseudocode handler idempoten
function handle(job) {
key = "idem:" + job.idempotency_key
if (store.exists(key)) {
ack(job)
return
}
lockToken = lock.acquire("resource:" + job.resource_id, ttl=30s)
if (!lockToken) {
retry(job)
return
}
try {
result = processBusinessLogic(job)
store.put(key, result.status, ttl=24h)
ack(job)
} catch (e) {
if (isTransient(e)) {
retry(job)
} else {
failToDLQ(job)
}
} finally {
lock.release("resource:" + job.resource_id, lockToken)
}
}Pseudocode di atas menekankan tiga hal: dedup eksplisit, lock dengan token, dan pemisahan error transient vs permanent. Implementasi nyatanya bisa berbeda tergantung broker, database, atau cache yang dipakai.
Cache: gunakan versioned key saat schema berubah
Jika worker baru mengubah bentuk data cache, jangan menimpa value lama di key yang sama kecuali Anda yakin semua reader kompatibel dua arah. Pilihan paling aman adalah namespace baru.
# Lama
product:123 -> {"price": 10000, "stock": 5}
# Baru
product:v2:123 -> {"price": {"amount": 10000, "currency": "IDR"}, "stock": 5}Trade-off-nya jelas: memory cache naik selama transisi, tetapi risiko reader rusak turun drastis. Setelah semua consumer pindah, key lama bisa dipensiunkan bertahap.
Lock: release harus memverifikasi kepemilikan
Kesalahan umum adalah worker A mengambil lock, lease habis, worker B mengambil lock baru, lalu worker A yang terlambat tetap menjalankan release dan menghapus lock milik B. Solusinya adalah release berbasis token kepemilikan.
acquire(resource):
token = random()
success = set_if_absent(resource, token, ttl=30s)
return success ? token : null
release(resource, token):
current = get(resource)
if current == token:
delete(resource)Di sistem nyata, pengecekan dan delete sebaiknya atomik. Detail implementasi bergantung pada storage lock yang digunakan, tetapi prinsip ownership verification tidak berubah.
Metrik wajib saat migrasi worker aman
Jangan hanya melihat CPU dan latency. Metrik berikut lebih penting untuk mendeteksi inkonsistensi operasional:
Metrik queue
- Backlog depth
- Age of oldest message
- Ack latency
- Retry rate
- DLQ rate
- In-flight message count
- Duplicate detection rate
Metrik worker
- throughput per versi
- error rate per kategori
- timeout rate
- job duration percentile
- resource usage: CPU, memori, file descriptor, koneksi DB
Metrik lock
- lock acquisition success/failure
- lock wait time
- lock expiration before completion
- orphan lock cleanup count
Metrik cache
- cache hit/miss per versi key
- stale read indicator jika tersedia
- serialization/deserialization error
- TTL expiry pattern
Metrik consistency bisnis
Ini yang paling sering dilupakan. Buat metrik yang merepresentasikan kebenaran domain, misalnya:
- jumlah order diproses dua kali
- jumlah invoice tanpa status final
- jumlah event out-of-order
- jumlah update yang ditolak karena version conflict
Jika tidak ada metrik tingkat bisnis, Anda bisa memiliki rollout “hijau” di dashboard infrastruktur sambil diam-diam merusak data.
Strategi rollout bertahap yang realistis
Tahap 1: verifikasi offline
- Replay sampel job historis di lingkungan non-produksi.
- Bandingkan output worker lama vs baru.
- Uji job yang timeout, malformed, duplicate, dan poison message.
Tahap 2: shadow traffic
- Kirim salinan job ke worker baru.
- Jangan izinkan write ke sistem utama, atau arahkan ke sink terisolasi.
- Catat perbedaan hasil, durasi, dan pola error.
Tahap 3: canary kecil
- Alihkan 1-5% traffic atau beberapa shard kecil.
- Aktifkan alert ketat untuk backlog, retry, dan duplicate.
- Jaga rollback tetap sederhana: satu flag, satu route switch, atau satu deployment revert.
Tahap 4: dual-run terkontrol
- Untuk alur kritis, bandingkan hasil worker baru dengan baseline lama.
- Pastikan hanya satu versi yang melakukan side effect final jika target resource sama.
Tahap 5: ramp-up bertahap
- Naikkan traffic berdasarkan bukti, bukan jadwal tetap.
- Tunggu beberapa siklus retry dan TTL sebelum menaikkan lagi.
- Pastikan perilaku saat beban puncak ikut teruji, bukan hanya jam sepi.
Checklist rollback yang wajib disiapkan sebelum migrasi
Rollback bukan sekadar deploy versi lama. Anda harus tahu bagaimana sistem pulih dari state campuran.
- Routing rollback: bagaimana menghentikan traffic ke worker baru?
- Queue safety: apakah in-flight job aman jika consumer baru dimatikan?
- Cache compatibility: apakah key baru perlu tetap dibaca worker lama?
- Lock cleanup: apakah lock dengan format/token baru bisa dikenali tooling lama?
- Retry storm control: apakah rollback justru memicu requeue massal?
- DLQ policy: job yang gagal di versi baru akan diproses bagaimana setelah rollback?
- Observability: apakah dashboard memisahkan metrik per versi?
Latih rollback seperti Anda melatih failover. Banyak insiden terjadi karena prosedur rollback hanya ada di dokumen, tidak pernah dicoba saat ada backlog aktif.
Contoh bug nyata yang sering muncul
1. Duplicate job karena timeout lebih pendek dari durasi kerja
Worker baru memakai library lebih cepat di rata-rata kasus, sehingga timeout diperkecil. Masalahnya, 1% job terberat masih memerlukan waktu lebih lama. Broker menganggap worker mati, job direqueue, lalu dua worker memproses job yang sama.
Pelajaran: ukur distribusi tail latency, bukan hanya median.
2. Stale cache karena format value berubah tanpa versioning
Worker baru menulis field nested baru ke key lama. Sebagian service reader lama gagal parse, sebagian lagi fallback diam-diam ke nilai default. Akibatnya perilaku sistem tidak konsisten antar service.
Pelajaran: schema cache adalah kontrak; perlakukan seperti kontrak API.
3. Lock orphan karena proses crash setelah akuisisi
Worker baru memegang lock pada resource, lalu container dihentikan paksa. Karena lock tidak punya lease yang cukup aman atau refresh gagal tidak termonitor, resource terlihat terkunci berjam-jam.
Pelajaran: lock tanpa TTL dan observability hampir selalu menjadi sumber insiden.
4. Backlog spike karena retry policy berubah
Worker lama membedakan error validasi dan error jaringan. Worker baru secara tidak sengaja me-retry semua exception. Hasilnya queue dipenuhi job yang tidak akan pernah sukses, worker sibuk memproses ulang sampah, dan job sehat ikut tertunda.
Pelajaran: klasifikasi error adalah bagian dari kontrak operasional.
Panduan keputusan: kapan perlu shadow, dual-run, atau langsung canary?
- Pakai shadow traffic jika perubahan besar ada pada parsing, komputasi, atau dependency internal, tetapi Anda belum yakin outputnya identik.
- Pakai dual-run jika Anda perlu bukti kuat bahwa hasil dan keputusan operasional setara.
- Pakai canary langsung jika perubahan kecil, handler sudah idempoten, kontrak tidak berubah, dan observability matang.
- Pakai partition pinning jika ordering per key sangat penting atau rollback harus bisa dibatasi per tenant/shard.
Prinsip penutup untuk migrasi worker aman
Migrasi worker aman menuntut lebih dari benchmark performa. Anda perlu memastikan queue semantics, retry, ordering, idempotensi, cache compatibility, TTL, dan lock lifecycle tetap benar saat dua generasi worker hidup bersamaan.
Jika harus merangkum menjadi satu aturan: jangan rollout implementasi baru sebelum Anda bisa menjelaskan bagaimana sistem berperilaku saat duplicate terjadi, lock hilang, cache belum sinkron, dan rollback harus dilakukan di tengah backlog. Di situlah perbedaan antara migrasi yang hanya cepat di lab dan migrasi yang aman di produksi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!