Max Ross
Menurut Wikipedia, tingkat isolasi sistem pengelolaan database "menentukan bagaimana/kapan perubahan yang dilakukan oleh satu operasi menjadi terlihat oleh operasi serentak lainnya". Tujuan artikel ini adalah untuk menjelaskan isolasi kueri dan transaksi di Cloud Datastore yang digunakan oleh App Engine. Setelah membaca artikel ini, Anda akan memiliki pemahaman yang lebih baik tentang perilaku operasi baca dan tulis serentak, baik dalam maupun di luar transaksi.
Di Dalam Transaksi: Dapat Di-serialisasi
Secara berurutan dari yang terkuat hingga terlemah, empat tingkat isolasi adalah Serializable, Repeatable Read, Read Committed, dan Read Uncommitted. Transaksi Datastore memenuhi tingkat isolasi Serializable. Setiap transaksi sepenuhnya terisolasi dari semua operasi dan transaksi datastore lainnya. Transaksi pada entity group tertentu dijalankan secara serial, satu per satu.
Lihat bagian Isolasi dan Konsistensi dalam dokumentasi transaksi untuk mengetahui informasi selengkapnya, serta artikel Wikipedia tentang isolasi snapshot.
Di Luar Transaksi: Baca Di-commit
Operasi datastore di luar transaksi paling mirip dengan tingkat isolasi Read Committed. Entity yang diambil dari datastore oleh kueri atau get hanya akan melihat data yang di-commit. Entitas yang diambil tidak akan pernah memiliki data yang di-commit sebagian (beberapa dari sebelum commit dan beberapa dari setelahnya). Namun, interaksi antara kueri dan transaksi agak lebih halus, dan untuk memahaminya, kita perlu melihat proses commit secara lebih mendalam.
Proses Commit
Jika commit berhasil ditampilkan, transaksi akan dijamin diterapkan, tetapi bukan berarti hasil penulisan Anda akan langsung terlihat oleh pembaca. Penerapan transaksi terdiri dari dua tonggak pencapaian:
- Puncak A – titik saat perubahan pada entitas telah diterapkan
- Puncak B – titik saat perubahan pada indeks untuk entity tersebut telah diterapkan
Di Cloud Datastore, transaksi biasanya diterapkan sepenuhnya dalam beberapa ratus milidetik setelah commit ditampilkan. Namun, meskipun tidak diterapkan sepenuhnya, operasi baca, tulis, dan kueri ancestor berikutnya akan selalu mencerminkan hasil commit, karena operasi ini menerapkan modifikasi yang belum dilakukan sebelum dieksekusi. Namun, kueri yang mencakup beberapa grup entity tidak dapat menentukan apakah ada perubahan yang belum selesai sebelum dieksekusi dan dapat menampilkan hasil yang usang atau diterapkan sebagian.
Permintaan yang mencari entity yang diupdate berdasarkan kuncinya pada suatu waktu setelah tonggak pencapaian A dijamin akan melihat versi terbaru entity tersebut. Namun, jika permintaan serentak mengeksekusi kueri yang predikatnya (klausa WHERE
, untuk Anda penggemar SQL/GQL) tidak dipenuhi oleh entity pra-update, tetapi dipenuhi oleh entity pasca-update, entity tersebut akan menjadi bagian dari set hasil hanya jika kueri dieksekusi setelah operasi penerapan mencapai tonggak pencapaian B.
Dengan kata lain, selama periode singkat, rangkaian hasil dapat tidak menyertakan entity yang propertinya, menurut hasil pencarian berdasarkan kunci, memenuhi predikat kueri. Kumpulan hasil juga dapat menyertakan entity yang propertinya, lagi-lagi sesuai dengan hasil pencarian berdasarkan kunci, gagal memenuhi predikat kueri. Kueri tidak dapat memperhitungkan transaksi yang berada di antara tonggak pencapaian A dan tonggak pencapaian B saat memutuskan entity mana yang akan ditampilkan. Operasi ini akan dilakukan terhadap data yang sudah tidak berlaku, tetapi melakukan operasi get()
pada kunci yang ditampilkan akan selalu mendapatkan versi terbaru entity tersebut. Artinya, Anda mungkin tidak mendapatkan hasil yang cocok dengan kueri atau mendapatkan hasil yang tidak cocok setelah mendapatkan entity yang sesuai.
Ada skenario saat modifikasi yang tertunda dijamin akan diterapkan sepenuhnya sebelum kueri dieksekusi, seperti semua kueri ancestor di Cloud Datastore. Dalam hal ini, hasil kueri akan selalu terbaru dan konsisten.
Contoh
Kami telah memberikan penjelasan umum tentang cara update dan kueri serentak berinteraksi, tetapi jika Anda seperti saya, biasanya Anda akan lebih mudah memahami konsep ini dengan mempelajari contoh konkret. Mari kita bahas beberapa. Kita akan mulai dengan beberapa contoh sederhana, lalu menyelesaikan dengan contoh yang lebih menarik.
Misalkan kita memiliki aplikasi yang menyimpan entitas Orang. Orang memiliki properti berikut:
- Nama
- Tinggi
Aplikasi ini mendukung operasi berikut:
updatePerson()
getTallPeople()
, yang menampilkan semua orang dengan tinggi lebih dari 72 inci.
Kita memiliki 2 entity Person di datastore:
- Adam, yang tingginya 172,72 cm.
- Bob, yang tingginya 185 cm.
Contoh 1 - Membuat Adam Lebih Tinggi
Misalkan aplikasi menerima dua permintaan pada dasarnya secara bersamaan. Permintaan pertama memperbarui tinggi Adam dari 68 inci menjadi 74 inci. Lonjakan pertumbuhan! Permintaan kedua memanggil getTallPeople(). Apa yang ditampilkan getTallPeople()?
Jawabannya bergantung pada hubungan antara dua tonggak pencapaian commit yang dipicu oleh Permintaan 1 dan kueri getTallPeople() yang dieksekusi oleh Permintaan 2. Misalkan tampilannya seperti ini:
- Permintaan 1,
put()
- Permintaan 2,
getTallPeople()
- Permintaan 1,
put()
-->commit()
- Permintaan 1,
put()
-->commit()
-->milestone A - Permintaan 1,
put()
-->commit()
-->milestone B
Dalam skenario ini, getTallPeople()
hanya akan menampilkan Bob. Mengapa? Karena
update pada Adam yang meningkatkan tinggi badannya belum di-commit, sehingga
perubahan tersebut belum terlihat oleh kueri yang kami berikan dalam Permintaan 2.
Sekarang, anggap tampilannya seperti ini:
- Permintaan 1,
put()
- Permintaan 1,
put()
-->commit()
- Permintaan 1,
put()
-->commit()
-->milestone A - Permintaan 2,
getTallPeople()
- Permintaan 1,
put()
-->commit()
-->milestone B
Dalam skenario ini, kueri dieksekusi sebelum Permintaan 1 mencapai tonggak pencapaian B, sehingga pembaruan pada indeks Orang belum diterapkan. Akibatnya, getTallPeople() hanya menampilkan Bob. Ini adalah contoh set hasil yang mengecualikan entity yang propertinya memenuhi predikat kueri.
Contoh 2 - Membuat Bob Lebih Pendek (Maaf, Bob)
Dalam contoh ini, kita akan meminta Permintaan 1 untuk melakukan sesuatu yang berbeda. Alih-alih
meningkatkan tinggi Adam dari 68 inci menjadi 74 inci, tindakan ini akan mengurangi tinggi
Bob dari 73 inci menjadi 65 inci. Sekali lagi, apa yang dilakukan
getTallPeople()
- Permintaan 1,
put()
- Permintaan 2,
getTallPeople()
- Permintaan 1,
put()
-->commit()
- Permintaan 1,
put()
-->commit()
-->milestone A - Permintaan 1,
put()
-->commit()
-->milestone B
Dalam skenario ini, getTallPeople()
hanya akan menampilkan Bob. Mengapa? Karena
update pada Bob yang menurunkan tinggi badannya belum di-commit,
jadi perubahannya belum terlihat oleh kueri yang kami berikan dalam Permintaan 2.
Sekarang, anggap tampilannya seperti ini:
- Permintaan 1,
put()
- Permintaan 1,
put()
-->commit()
- Permintaan 1,
put()
-->commit()
-->milestone A - Permintaan 1,
put()
-->commit()
-->milestone B - Permintaan 2,
getTallPeople()
Dalam skenario ini, getTallPeople()
akan menampilkan no one. Mengapa? Karena pembaruan pada Bob yang mengurangi tinggi badannya telah dilakukan pada saat kami mengeluarkan kueri di Permintaan 2.
Sekarang, anggap tampilannya seperti ini:
- Permintaan 1,
put()
- Permintaan 1,
put()
-->commit()
- Permintaan 1,
put()
-->commit()
-->milestone A - Permintaan 2,
getTallPeople()
- Permintaan 1,
put()
-->commit()
-->milestone B
Dalam skenario ini, kueri dieksekusi sebelum tonggak pencapaian B, sehingga pembaruan
pada indeks Orang belum diterapkan. Akibatnya,
getTallPeople()
masih menampilkan Bob, tetapi properti tinggi entity
Orang yang ditampilkan adalah nilai yang diperbarui: 65. Ini adalah contoh
kumpulan hasil yang menyertakan entity yang propertinya gagal memenuhi
predikat kueri.
Kesimpulan
Seperti yang dapat Anda lihat dari contoh di atas, tingkat isolasi transaksi Cloud Datastore cukup dekat dengan Read Committed. Tentu saja, ada perbedaan yang signifikan, tetapi setelah memahami perbedaan ini dan alasannya, Anda akan berada dalam posisi yang lebih baik untuk membuat keputusan desain terkait datastore yang cerdas dalam aplikasi Anda.