Isolasi Transaksi di App Engine

Max Ross

Menurut Wikipedia, tingkat isolasi sistem pengelolaan database "menentukan bagaimana/kapan perubahan yang dibuat oleh satu operasi menjadi terlihat oleh operasi serentak lainnya". Tujuan artikel ini adalah 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 ke dalam maupun ke luar transaksi.

Transaksi di Dalam: Serializable

Untuk urutan dari yang terkuat hingga yang 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 transaksi dan operasi datastore lainnya. Transaksi pada grup entity tertentu dijalankan secara berurutan, satu per satu.

Lihat bagian Isolasi dan Konsistensi dalam dokumentasi transaksi untuk mengetahui informasi selengkapnya, serta artikel Wikipedia tentang isolasi snapshot.

Transaksi di Luar: Baca Abonemen

Operasi Datastore di luar sangat mirip dengan tingkat isolasi terikat Dibaca. Entitas yang diambil dari datastore oleh kueri atau diperoleh hanya akan melihat data yang di-commit. Entity yang diambil tidak akan pernah memiliki data yang di-commit sebagian (beberapa dari sebelum commit dan sebagian lagi dari setelahnya). Namun, interaksi antara kueri dan transaksi sedikit lebih halus, dan untuk memahaminya, kita perlu melihat proses commit secara lebih mendalam.

Proses Commit

Jika commit berhasil ditampilkan, transaksi dijamin akan diterapkan, tetapi itu tidak berarti hasil penulisan Anda langsung terlihat oleh pembaca. Menerapkan transaksi terdiri dari dua {i>milestone<i}:

  • Pencapaian A – titik saat perubahan pada suatu entitas telah diterapkan
  • Pencapaian B – titik saat perubahan indeks untuk entity tersebut telah diterapkan

Menampilkan panah progres dari transaksi commit ke perubahan entity yang terlihat pada perubahan entity dan indeks yang terlihat.

Di Cloud Datastore, transaksi biasanya diterapkan sepenuhnya dalam beberapa ratus milidetik setelah commit kembali. Namun, meskipun tidak diterapkan sepenuhnya, operasi baca, tulis, dan kueri ancestor berikutnya akan selalu mencerminkan hasil commit, karena operasi ini menerapkan modifikasi yang belum selesai sebelum dieksekusi. Namun, kueri yang mencakup beberapa grup entitas tidak dapat menentukan apakah ada perubahan yang belum diselesaikan sebelum dieksekusi dan mungkin menampilkan hasil yang sudah tidak berlaku atau diterapkan sebagian.

Permintaan yang mencari entity yang diupdate berdasarkan kuncinya pada waktu setelah pencapaian A dijamin untuk melihat versi terbaru entity tersebut. Namun, jika permintaan serentak mengeksekusi kueri yang predikatnya (klausa WHERE, bagi penggemar SQL/GQL di luar sana) tidak terpenuhi 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 milestone B.

Dengan kata lain, selama periode singkat, set hasil mungkin tidak menyertakan entity yang propertinya, menurut hasil pencarian berdasarkan kunci, memenuhi predikat kueri. Set hasil juga mungkin menyertakan entity yang propertinya, sekali lagi menurut hasil pencarian berdasarkan kunci, gagal memenuhi predikat kueri. Kueri tidak dapat memperhitungkan transaksi yang berada di antara status progres A dan pencapaian B saat memutuskan entitas mana yang akan ditampilkan. Tindakan ini akan dilakukan terhadap data yang sudah tidak berlaku, tetapi melakukan operasi get() pada kunci yang ditampilkan akan selalu mendapatkan versi terbaru dari entity tersebut. Artinya, Anda mungkin akan kehilangan hasil yang cocok dengan kueri atau mendapatkan hasil yang tidak cocok setelah mendapatkan entitas yang sesuai.

Ada beberapa skenario ketika setiap modifikasi tertunda dijamin akan diterapkan sepenuhnya sebelum kueri dieksekusi, misalnya setiap kueri ancestor di Cloud Datastore. Dalam hal ini, hasil kueri akan selalu aktual dan konsisten.

Contoh

Kami telah memberikan penjelasan umum tentang bagaimana update dan kueri serentak berinteraksi. Namun, jika Anda seperti saya, biasanya Anda akan lebih mudah memahami konsep-konsep ini melalui contoh-contoh konkret. Mari kita bahas beberapa contoh berikut. Kita akan mulai dengan beberapa contoh sederhana dan kemudian selesaikan dengan yang lebih menarik.

Katakanlah kita memiliki aplikasi yang menyimpan entitas Person. Person 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 entitas Orang di datastore:

  • Adam, yang tingginya 68 inci.
  • Bob, dengan tinggi 73 inci.

Contoh 1 - Membuat Adam Lebih Tinggi

Misalkan sebuah aplikasi menerima dua permintaan pada waktu yang hampir bersamaan. Permintaan pertama memperbarui tinggi Adam dari 68 inci menjadi 74 inci. Percepatan pertumbuhan! Permintaan kedua memanggil getTallPeople(). Apa yang ditampilkan getTallPeople()?

Jawabannya bergantung pada hubungan antara dua pencapaian commit yang dipicu oleh Permintaan 1 dan kueri getTallPeople() yang dijalankan oleh Permintaan 2. Misalkan terlihat seperti ini:

  • Permintaan 1, put()
  • Permintaan 2, getTallPeople()
  • Permintaan 1, put()-->commit()
  • Permintaan 1, put()-->commit()-->pencapaian A
  • Permintaan 1, put()-->commit()-->pencapaian B

Dalam skenario ini, getTallPeople() hanya akan menampilkan Bob. Mengapa demikian? Karena update untuk Adam yang menambah tingginya belum dilakukan, sehingga perubahan tersebut belum terlihat oleh kueri yang kita keluarkan dalam Permintaan 2.

Sekarang anggaplah terlihat seperti ini:

  • Permintaan 1, put()
  • Permintaan 1, put()-->commit()
  • Permintaan 1, put()-->commit()-->pencapaian A
  • Permintaan 2, getTallPeople()
  • Permintaan 1, put()-->commit()-->pencapaian B

Dalam skenario ini, kueri dijalankan sebelum Permintaan 1 mencapai tahap pencapaian B, sehingga pembaruan pada indeks Orang belum diterapkan. Akibatnya, getTallPeople() hanya menampilkan Bob. Ini adalah contoh kumpulan hasil yang mengecualikan entity yang propertinya memenuhi predikat kueri.

Contoh 2 - Membuat Bob menjadi lebih pendek (Maaf, Bob)

Dalam contoh ini, Permintaan 1 akan melakukan sesuatu yang berbeda. Bukannya meningkatkan tinggi Adam dari 68 inci menjadi 74 inci, ini akan mengurangi tinggi Bob dari 73 inci menjadi 65 inci. Sekali lagi, apa fungsi getTallPeople()

return?
  • Permintaan 1, put()
  • Permintaan 2, getTallPeople()
  • Permintaan 1, put()-->commit()
  • Permintaan 1, put()-->commit()-->pencapaian A
  • Permintaan 1, put()-->commit()-->pencapaian B

Dalam skenario ini, getTallPeople() hanya akan menampilkan Bob. Mengapa demikian? Karena pembaruan untuk Bob yang menurunkan tinggi belum dilakukan, sehingga perubahan tersebut belum terlihat oleh kueri yang kita berikan dalam Permintaan 2.

Sekarang anggaplah terlihat seperti ini:

  • Permintaan 1, put()
  • Permintaan 1, put()-->commit()
  • Permintaan 1, put()-->commit()-->pencapaian A
  • Permintaan 1, put()-->commit()-->pencapaian B
  • Permintaan 2, getTallPeople()

Dalam skenario ini, getTallPeople() tidak akan menampilkan siapa pun. Mengapa demikian? Karena pembaruan pada Bob yang menurunkan tinggi telah dilakukan saat kami mengeluarkan kueri dalam Permintaan 2.

Sekarang anggaplah terlihat seperti ini:

  • Permintaan 1, put()
  • Permintaan 1, put()-->commit()
  • Permintaan 1, put()-->commit()-->pencapaian A
  • Permintaan 2, getTallPeople()
  • Permintaan 1, put()-->commit()-->pencapaian B

Dalam skenario ini, kueri dijalankan sebelum tonggak pencapaian B, sehingga update pada indeks Orang belum diterapkan. Akibatnya, getTallPeople() tetap menampilkan Bob, tetapi properti tinggi entitas Orang yang kembali adalah nilai yang diperbarui: 65. Ini adalah contoh kumpulan hasil yang menyertakan entity yang propertinya gagal memenuhi predikat kueri.

Kesimpulan

Seperti yang Anda lihat dari contoh di atas, tingkat isolasi transaksi Cloud Datastore cukup dekat dengan Read Committed. Tentu saja ada perbedaan yang berarti, tetapi setelah Anda memahami perbedaan ini dan alasannya, Anda seharusnya berada di posisi yang lebih baik untuk membuat keputusan desain terkait datastore yang cerdas dalam aplikasi Anda.