Fencing Token Memblokir Pemegang Lock yang Sudah Kedaluwarsa

Distributed lock sering dipakai agar dua worker tidak mengubah resource yang sama pada saat bersamaan. Kasus yang lebih sulit muncul ketika kepemilikan lock bergantung pada lease. Sebuah client dapat memperoleh lease, berhenti cukup lama sampai lease kedaluwarsa, lalu melanjutkan eksekusi setelah client lain memperoleh lease baru.

Dari sisi client lama, eksekusi hanya sempat terhenti. Dari sisi layanan koordinasi, kepemilikan sudah berpindah. Jika sistem penyimpanan yang dilindungi menerima write dari keduanya, pemegang lama dapat menimpa pekerjaan yang dilakukan pemegang saat ini.

Fencing token menutup celah tersebut. Setiap akuisisi lease yang berhasil menerima token yang lebih besar daripada seluruh token sebelumnya untuk scope terlindungi yang sama. Client membawa token itu ke resource. Resource hanya menerima mutasi jika token tersebut tidak lebih lama daripada token terbesar yang sudah diterima.

Kedaluwarsa lease tidak menghentikan proses yang terhenti

Perhatikan dua worker, A dan B, yang memakai lease dengan masa berlaku terbatas:

t=0   A memperoleh lease, token = 41
t=2   A terhenti
t=10  lease A kedaluwarsa
t=11  B memperoleh lease, token = 42
t=12  B write dengan token 42
t=14  A melanjutkan eksekusi
t=15  A write dengan token 41

Coordinator bekerja dengan benar. Lease diberikan kepada B hanya setelah lease A kedaluwarsa. Risiko yang tersisa berada di luar coordinator: expiry tidak dapat menghapus state dalam memori A secara paksa atau mencegah code yang sudah berjalan mencapai storage API pada waktu berikutnya.

Garbage-collection pause yang panjang, scheduler stall, virtual machine yang ditangguhkan, network partition, host yang kelebihan beban, dan process freeze dapat menghasilkan pola ini. Timeout dapat menyatakan kepemilikan sudah stale, tetapi tidak dapat membuat proses stale tersebut menghilang.

Perbedaan ini penting karena mutual exclusion pada coordinator tidak sama dengan exclusion pada resource yang sedang dilindungi.

Token mengubah kepemilikan menjadi aturan urutan

Fencing token umumnya berupa integer yang meningkat secara monoton dan terkait dengan satu akuisisi lease:

acquire() -> lease, token

A: acquire() -> token 41
B: acquire() -> token 42

Setiap protected write menyertakan token:

write(resource, token=42, value=...)

Resource menyimpan token tertinggi yang sudah diterima untuk scope terkait. Admission rule sederhana dapat berbentuk:

if token < highest_accepted_token:
    reject

apply mutation
highest_accepted_token = max(highest_accepted_token, token)

Setelah write B dengan token 42 diterima, write A dengan token 41 yang datang kemudian pasti stale. A dapat melanjutkan eksekusi, reconnect, retry, atau meneruskan pekerjaan lokal, tetapi tidak lagi memiliki authority untuk memutasi resource tersebut.

Properti pentingnya adalah monotonicity, bukan waktu yang sudah berlalu. Resource membandingkan generation kepemilikan, bukan mencoba menilai apakah clock atau deadline lease milik client masih berlaku.

Identifier lock acak bukan fencing token

Banyak implementasi lock mengembalikan nilai acak dan mewajibkan nilai itu ketika lock dilepas. Nilai tersebut berguna karena mencegah satu client tanpa sengaja melepas lease milik client lain. Namun, nilai acak tidak menetapkan urutan antara pemilik yang datang bergantian.

Contohnya:

A lease id = 8f2c...
B lease id = 19ab...

Tidak ada informasi pada kedua identifier itu yang menunjukkan akuisisi mana yang lebih baru. Storage layer tidak dapat memakai nilai tersebut untuk menolak pemilik lama berdasarkan generation.

Fencing token membawa urutan:

A token = 41
B token = 42

Unique ownership ID dan fencing token karena itu menyelesaikan masalah yang berbeda. Sebuah sistem dapat memakai keduanya: lease ID opaque untuk operasi coordinator dan token monoton untuk write ke resource terlindungi.

Resource harus menerapkan fence

Menerbitkan token saja tidak menambah safety. Komponen yang dapat rusak akibat pemegang stale harus ikut menerapkan protokol.

Misalkan worker memperoleh token 52 lalu menulis ke object store melalui API yang mengabaikan token. Worker lain kemudian memperoleh token 53 dan menulis object yang lebih baru. Jika worker pertama melanjutkan eksekusi dan object store menerima delayed write miliknya, pembuatan token di lock service tidak melindungi object tersebut.

Enforcement dapat berada langsung di storage system atau di trusted service yang menserialisasi akses ke storage. Syarat utamanya adalah client stale tidak dapat melewati perbandingan token.

Untuk sebuah database row, generation dapat disimpan bersama data dan diperiksa secara atomik:

UPDATE jobs
SET result = :result,
    fence_token = :token
WHERE id = :id
  AND fence_token <= :token;

Predicate yang tepat bergantung pada data model dan apakah operasi berulang dengan token yang sama valid. Perbandingan dan mutasi harus menjadi satu operasi storage yang atomik; read terpisah yang diikuti write tanpa pemeriksaan akan membuka race kembali.

Scope token harus mengikuti resource yang dilindungi

Global counter dapat menghasilkan token monoton, tetapi global ordering sering lebih luas daripada kebutuhan. Jika lease melindungi resource yang independen, setiap resource hanya memerlukan ordering yang andal di antara pemilik yang dapat memutasinya.

Sistem dapat menerapkan fence per account, shard, document, job, atau storage key. Coordinator dan resource harus memakai definisi scope yang sama. Token yang dibuat untuk satu scope tidak dapat dengan aman mengurutkan pemilik pada scope lain kecuali skema token memang menyediakan properti tersebut.

Scope juga memengaruhi persistence. Jika sequence fencing dapat kembali ke nilai awal setelah coordinator restart sementara resource masih menyimpan token diterima yang lebih besar, pemegang baru yang valid dapat ditolak. Pembuatan token karena itu memerlukan persistence atau epoch scheme yang mempertahankan ordering melewati recovery coordinator.

Renewal mengurangi risiko expiry tetapi tidak menghapus pemegang stale

Lease renewal berguna ketika pekerjaan yang sah dapat berjalan lebih lama daripada durasi lease awal. Worker dapat memperpanjang lease secara berkala selama kondisinya masih sehat.

Renewal tetap memiliki failure boundary. Pause dapat mencegah renewal, request renewal dapat terlambat, atau coordinator dapat tidak terjangkau. Setelah lease kedaluwarsa dan pemegang lain mengambil alih, proses lama tetap dapat melanjutkan eksekusi pada waktu berikutnya.

Code yang memeriksa lease sebelum setiap write juga memiliki timing gap jika pemeriksaan dan write merupakan operasi terpisah:

check lease -> valid
pause
lease kedaluwarsa
pemegang baru memperoleh lease
pemegang lama write

Fence memindahkan pemeriksaan penentu ke mutasi resource itu sendiri. Write membawa bukti generation kepemilikannya, lalu resource membandingkan generation tersebut secara atomik dengan state yang tersimpan.

Fencing tidak otomatis membuat setiap operasi idempotent

Menolak generation stale mencegah pemegang lama memutasi fenced resource setelah generation baru diterima. Mekanisme ini tidak otomatis menghapus duplikasi write dari generation yang sama.

Jika client mengirim charge, message, atau side effect yang sama dua kali dengan token 60, kedua operasi tetap dapat melewati aturan yang menerima token 60. Idempotency key, unique constraint, transactional state transition, atau deduplication khusus aplikasi masih dapat diperlukan.

Fencing juga tidak memperbaiki side effect yang tidak dapat menerapkan token. Mengirim email, memanggil external service, atau menulis ke legacy system dapat memerlukan coordination boundary berbeda atau intermediary yang mampu memeriksa generation sebelum menghasilkan effect.

Test perlu sengaja melanjutkan pemegang yang lease-nya kedaluwarsa

Concurrency test yang berguna tidak hanya memverifikasi bahwa dua client tidak dapat memegang live lease yang sama. Test perlu memaksa jalur stale-holder:

A memperoleh token 71
hentikan A sementara
buat lease A kedaluwarsa
B memperoleh token 72
B write berhasil
lanjutkan A
write A dengan 71 ditolak

Assertion terakhir merupakan invariant utama. Proses yang pernah memegang lease valid harus kehilangan write authority setelah generation kepemilikan yang lebih baru mencapai resource terlindungi.

Kasus tambahan dapat mencakup coordinator restart, persistence token, kegagalan lease renewal, retry dengan token yang sama, scope resource independen, dan perbandingan atomik pada storage boundary.

Lease menyatakan client yang saat ini memiliki coordination record. Fencing token membawa generation kepemilikan tersebut ke tempat stale execution dapat menimbulkan kerusakan. Pemisahan tanggung jawab ini membuat kegagalan pause-and-resume menjadi eksplisit: pemegang yang lease-nya kedaluwarsa masih dapat berjalan, tetapi resource terlindungi tidak lagi menerima authority miliknya.