Topik ini membahas lock terdistribusi sebagai alat koordinasi yang reputasinya sering melebihi jaminannya. Pembahasan dimulai dari alasan lock pada satu proses tidak dapat diperluas begitu saja ke jaringan, dilanjutkan dengan pola lease dengan masa berlaku yang membatasi dampak pemegang lock yang terhenti, dan diakhiri fencing token yang menjaga kebenaran pada sisi penyimpanan. Satu simulasi Python memperlihatkan kegagalan mutual exclusion beserta perbaikannya secara berurutan.
Mengapa lock tunggal tidak cukup#
Lock pada satu proses berjalan benar karena dua kondisi yang tidak ada di jaringan. Pertama, seluruh pihak berbagi memori yang sama, sehingga status terkunci atau tidak terkunci mustahil berbeda antara penjaga dan pemakainya. Kedua, sistem operasi dapat menghentikan proses secara paksa, sehingga pemegang lock tidak mungkin melanjutkan pekerjaannya tanpa melewati penjaganya. Pada klaster, lock dijaga oleh layanan koordinasi yang berdiri sendiri, sedangkan pemegangnya berjalan di mesin yang berbeda dengan jam, keadaan memori, dan jadwal eksekusinya masing-masing.
Kondisi berbahaya muncul ketika pemegang lock terhenti lebih lama dari perkiraan siapa pun. Pause garbage collection, kehabisan CPU, atau penuhnya memori halaman dapat menghentikan proses selama beberapa detik, dan pause sepanjang itu terdokumentasi berulang pada sistem production. Selama terhenti, proses tidak mengetahui apa pun tentang dunia luar, termasuk status lock yang dipegangnya. Sistem yang benar harus tetap aman pada kejadian ini, karena kejadian tersebut bukan hipotesis, melainkan kondisi kerja yang pasti dialami setiap layanan yang berumur panjang.
Lease dengan masa berlaku#
Lease adalah lock yang diberi masa berlaku. Layanan koordinasi menerbitkan lock beserta batas waktunya, dan lock dianggap kedaluwarsa setelah batas itu lewat tanpa pembaruan. Masa berlaku memastikan pemegang yang mati tidak menahan lock selamanya, tetapi juga membuka jendela baru: pemegang lama dapat terbangun setelah leasenya berakhir tanpa sempat mengetahuinya, sementara layanan koordinasi sudah menerbitkan lock pengganti kepada pemegang lain. Dua pihak kini sama-sama yakin memegang lock yang sama, dan tidak ada pesan yang bisa mengubah keyakinan itu dengan seketika.
Simulasi berikut menjalankan skenario tersebut. Klien 1 memperoleh lease sepuluh detik, lalu mengalami pause GC hingga detik kedua belas. Klien 2 memperoleh lock pengganti pada detik ketiga belas karena lease klien 1 sudah lewat. Bagian pertama keluaran menunjukkan bahwa penyimpanan menerima tulisan dari keduanya, padahal seluruh tujuan lock adalah mencegah hal itu.
# Simulasi lock berbasis lease pada penyimpanan bersama.
# Waktu disimulasikan dalam satuan detik, dan lock dinyatakan usang
# bila waktu berjalan sudah melewati masa berlaku leasenya.
LEASE = 10 # masa berlaku lease dalam detik
print("Detik 0: klien 1 memperoleh lock, lease berlaku sampai detik 10")
print("Detik 2: klien 1 mulai menulis, lalu mengalami pause GC panjang")
print(f"Detik 12: klien 1 terbangun. Lease klien 1 masih sah: {12 < 0 + LEASE}")
print("Detik 13: klien 2 memperoleh lock karena lease klien 1 sudah lewat")
print()
print("Tanpa fencing token, penyimpanan menerima kedua tulisan:")
print(" klien 2 menulis berkas catatan.log -> diterima")
print(" klien 1 menulis berkas catatan.log -> diterima")
print("Dua penulis aktif pada saat yang sama, tulisan saling menimpa.")
print()
print("Perbaikan dengan fencing token:")
token_terbit = 32
def terbitkan_token():
global token_terbit
token_terbit += 1
return token_terbit
token_diterima_tertinggi = 0
def simpan(token):
global token_diterima_tertinggi
if token < token_diterima_tertinggi:
return f"DITOLAK (token {token} lebih tua dari {token_diterima_tertinggi})"
token_diterima_tertinggi = token
return "DITERIMA"
token_1 = terbitkan_token() # token klien 1, pemegang lock lama
token_2 = terbitkan_token() # token klien 2, pemegang lock pengganti
print(f"klien 2 menulis dengan token {token_2}: {simpan(token_2)}")
print(f"klien 1 menulis dengan token {token_1}: {simpan(token_1)}")Detik 0: klien 1 memperoleh lock, lease berlaku sampai detik 10
Detik 2: klien 1 mulai menulis, lalu mengalami pause GC panjang
Detik 12: klien 1 terbangun. Lease klien 1 masih sah: False
Detik 13: klien 2 memperoleh lock karena lease klien 1 sudah lewat
Tanpa fencing token, penyimpanan menerima kedua tulisan:
klien 2 menulis berkas catatan.log -> diterima
klien 1 menulis berkas catatan.log -> diterima
Dua penulis aktif pada saat yang sama, tulisan saling menimpa.
Perbaikan dengan fencing token:
klien 2 menulis dengan token 34: DITERIMA
klien 1 menulis dengan token 33: DITOLAK (token 33 lebih tua dari 34)Baris keempat keluaran menunjukkan inti jebakannya: klien 1 masih menganggap dirinya pemegang lock, padahal penilaian itu sudah tidak berlaku. Tidak ada perbaikan pada sisi lease yang menutup jendela ini sepenuhnya, karena lease yang dipanjangkan hanya memperlambat pemulihan ketika pemegang benar-benar mati, sedangkan pause yang lebih panjang dari lease tetap mungkin terjadi. Kesimpulannya, mutual exclusion pada jaringan tidak dapat dijamin oleh lock itu sendiri, melainkan harus dijaga oleh pihak yang menerima akibatnya, yaitu penyimpanan.
Fencing token sebagai pengaman#
Fencing token adalah nomor yang meningkat secara monoton dan diterbitkan bersama setiap lock atau lease. Penyimpanan yang dituju mencatat nomor tertinggi yang pernah diterimanya dan menolak permintaan dengan nomor yang lebih tua. Kebenaran tidak lagi bergantung pada keyakinan pemegang lock tentang dirinya, melainkan pada pemeriksaan di sisi penyimpanan yang tidak dapat dibohongi oleh pause apa pun. Bagian kedua simulasi menerapkan pola ini: token 34 dari klien 2 diterima dan tercatat, sedangkan token 33 dari klien 1 yang terlambat ditolak lengkap dengan alasannya.
Pembedaan terakhir yang berguna adalah tujuan locknya sendiri. Lock untuk efisiensi mencegah pekerjaan ganda, sehingga pelanggaran sesekali hanya membuang tenaga dan jarang memerlukan fencing token. Lock untuk kebenaran menjaga invariant data, sehingga pelanggaran sekali saja sudah merusak hasil, dan fencing token menjadi wajib, bukan pilihan. Penentuan ini dibuat saat merancang, karena biaya pemeriksaan token pada setiap operasi penyimpanan tidak selalu murah.
Kesalahan umum#
Kesalahan pertama adalah memakai lock terdistribusi untuk kebenaran tanpa fencing token. Penyebabnya adalah menganggap respons layanan koordinasi sebagai kepastian mutlak, padahal pesan dan pause membuatnya hanya perkiraan. Perbaikannya adalah memindahkan pemeriksaan terakhir ke sisi penyimpanan dengan nomor monoton.
Kesalahan kedua adalah menetapkan masa lease tanpa mengukur. Lease yang terlalu pendek memicu pemegang pengganti beruntun dan konflik berulang, sedangkan lease yang terlalu panjang memperpanjang waktu tunggu ketika pemegang benar-benar mati. Perbaikannya adalah mengukur distribusi durasi pekerjaan dan pause, lalu menetapkan lease di atas nilai ekstrem yang dipantau, dengan mekanisme pembaruan lease saat pekerjaan masih berjalan.
Kesalahan ketiga adalah menyimpan status lock di satu node tanpa replikasi. Node tunggal yang mati membuat semua lock-nya hilang, sehingga dua klien dapat memperoleh lock yang sama secara beruntun. Perbaikannya adalah layanan koordinasi yang direplikasi dengan konsensus, seperti dibahas pada topik Posisi Konsensus.
Ringkasan#
- Lock terdistribusi tidak dapat menjamin mutual exclusion, karena pemegangnya dapat terhenti tanpa mengetahui bahwa leasenya sudah kedaluwarsa.
- Lease membatasi dampak pemegang yang mati melalui masa berlaku, tetapi tetap membuka jendela dua pemegang yang tidak dapat ditutup dari sisi lock.
- Fencing token memindahkan kebenaran ke sisi penyimpanan: nomor monoton diterbitkan bersama lock, dan permintaan dengan nomor lebih tua ditolak.
Referensi#
- How to do distributed locking — Martin Kleppmann — Analisis kegagalan lock terdistribusi akibat pause GC dan pola fencing token untuk kebenaran.
- Is Redlock broken? — antirez — Tanggapan Salvatore Sanfilippo atas analisis tersebut yang memperjelas perbedaan lock efisiensi dan lock kebenaran.