5.1 Masalah keserentakan

Mari kita mulai dengan teori yang agak jauh.

Sistem informasi apa pun (atau hanya aplikasi) yang dibuat oleh pemrogram terdiri dari beberapa blok tipikal, yang masing-masing menyediakan bagian dari fungsionalitas yang diperlukan. Misalnya, cache digunakan untuk mengingat hasil operasi intensif sumber daya untuk memastikan pembacaan data yang lebih cepat oleh klien, alat pemrosesan aliran memungkinkan Anda mengirim pesan ke komponen lain untuk pemrosesan asinkron, dan alat pemrosesan batch digunakan untuk " menyapu" akumulasi volume data dengan beberapa periodisitas. .

Dan di hampir setiap aplikasi, database (DB) terlibat dalam satu atau lain cara, yang biasanya menjalankan dua fungsi: menyimpan data saat diterima dari Anda dan kemudian memberikannya kepada Anda berdasarkan permintaan. Jarang ada yang berpikir untuk membuat database sendiri, karena sudah banyak solusi yang sudah jadi. Tapi bagaimana Anda memilih yang tepat untuk aplikasi Anda?

Jadi, bayangkan Anda telah menulis aplikasi dengan antarmuka seluler yang memungkinkan Anda memuat daftar tugas yang disimpan sebelumnya di sekitar rumah - yaitu, membaca dari database, dan melengkapinya dengan tugas baru, serta memprioritaskan setiap tugas tertentu. tugas - dari 1 ( tertinggi) hingga 3 (terendah). Katakanlah aplikasi seluler Anda hanya digunakan oleh satu orang dalam satu waktu. Tapi sekarang kamu berani memberi tahu ibumu tentang kreasimu, dan sekarang dia telah menjadi pengguna reguler kedua. Apa yang terjadi jika Anda memutuskan pada saat yang sama, tepat dalam milidetik yang sama, untuk menetapkan beberapa tugas - "cuci jendela" - ke tingkat prioritas yang berbeda?

Dalam istilah profesional, kueri basis data Anda dan ibu dapat dianggap sebagai 2 proses yang membuat kueri ke basis data. Proses adalah entitas dalam program komputer yang dapat berjalan di satu atau lebih utas. Biasanya, suatu proses memiliki gambar kode mesin, memori, konteks, dan sumber daya lainnya. Dengan kata lain, proses dapat dicirikan sebagai pelaksanaan instruksi program pada prosesor. Saat aplikasi Anda membuat permintaan ke database, kami berbicara tentang fakta bahwa database Anda memproses permintaan yang diterima melalui jaringan dari satu proses. Jika ada dua pengguna yang duduk di aplikasi pada saat yang sama, maka bisa ada dua proses pada saat tertentu.

Ketika beberapa proses membuat permintaan ke database, ia menemukannya dalam keadaan tertentu. Sistem stateful adalah sistem yang mengingat peristiwa sebelumnya dan menyimpan beberapa informasi, yang disebut "keadaan". Variabel yang dideklarasikan sebagai integerdapat memiliki status 0, 1, 2, atau katakanlah 42. Mutex (pengecualian bersama) memiliki dua status: terkunci atau tidak terkunci , seperti semafor biner ("diperlukan" vs. "dirilis") dan umumnya biner tipe data (biner) dan variabel yang hanya dapat memiliki dua status - 1 atau 0.

Berdasarkan konsep keadaan, beberapa struktur matematika dan teknik didasarkan, seperti otomat terbatas - sebuah model yang memiliki satu input dan satu output dan berada di salah satu himpunan keadaan yang terbatas pada setiap momen waktu - dan "keadaan ” pola desain, di mana suatu objek mengubah perilaku tergantung pada keadaan internal (misalnya, tergantung pada nilai apa yang diberikan ke satu atau variabel lain).

Jadi, sebagian besar objek di dunia mesin memiliki beberapa status yang dapat berubah seiring waktu: pipa kami, yang memproses paket data besar, melontarkan kesalahan dan menjadi failed , atau properti objek Wallet, yang menyimpan jumlah uang yang tersisa di pengguna akun, perubahan setelah tanda terima gaji.

Transisi ("transisi") dari satu keadaan ke keadaan lain—katakanlah, dari sedang berlangsung ke gagal —disebut operasi. Mungkin, semua orang tahu operasi CRUD - create, read, update, deleteatau metode HTTP serupa - POST, GET, PUT, DELETE. Tetapi programmer sering memberikan nama lain untuk operasi dalam kode mereka, karena operasi bisa lebih kompleks daripada hanya membaca nilai tertentu dari database - itu juga dapat memeriksa data, dan kemudian operasi kita, yang telah berbentuk fungsi, akan dipanggil, misalnya, Dan validate()siapa yang melakukan fungsi operasi ini? proses yang sudah dijelaskan.

Sedikit lagi, dan Anda akan mengerti mengapa saya menjelaskan istilah-istilah itu dengan sangat rinci!

Operasi apa pun - baik itu fungsi, atau, dalam sistem terdistribusi, mengirim permintaan ke server lain - memiliki 2 properti: waktu permintaan dan waktu penyelesaian (waktu penyelesaian) , yang akan lebih besar dari waktu permintaan (peneliti dari Jepsen melanjutkan dari asumsi teoretis bahwa kedua stempel waktu ini akan diberikan jam imajiner, tersinkronisasi penuh, dan tersedia secara global).

Bayangkan aplikasi daftar tugas kita. Anda membuat permintaan ke database melalui antarmuka seluler di 14:00:00.014, dan ibu Anda di 13:59:59.678(yaitu, 336 milidetik sebelumnya) memperbarui daftar tugas melalui antarmuka yang sama, menambahkan mencuci piring ke dalamnya. Mempertimbangkan keterlambatan jaringan dan kemungkinan antrian tugas untuk database Anda, jika, selain Anda dan ibu Anda, semua teman ibu Anda juga menggunakan aplikasi Anda, database dapat mengeksekusi permintaan ibu Anda setelah memproses permintaan Anda. Dengan kata lain, ada kemungkinan dua permintaan Anda, serta permintaan dari pacar ibu Anda, akan dikirim ke data yang sama secara bersamaan (bersamaan).

Jadi kita telah sampai pada istilah terpenting di bidang database dan aplikasi terdistribusi - konkurensi. Apa sebenarnya arti simultan dari dua operasi? Jika beberapa operasi T1 dan beberapa operasi T2 diberikan, maka:

  • T1 dapat dimulai sebelum waktu mulai eksekusi T2, dan selesai antara waktu mulai dan akhir T2
  • T2 dapat dimulai sebelum waktu mulai T1, dan selesai antara awal dan akhir T1
  • T1 dapat dimulai dan diselesaikan antara waktu mulai dan akhir eksekusi T1
  • dan skenario lain di mana T1 dan T2 memiliki waktu eksekusi yang sama

Jelas bahwa dalam kerangka kuliah ini, kita terutama berbicara tentang kueri yang memasuki basis data dan bagaimana sistem manajemen basis data memandang kueri ini, tetapi istilah konkurensi penting, misalnya, dalam konteks sistem operasi. Saya tidak akan menyimpang terlalu jauh dari topik artikel ini, tetapi menurut saya penting untuk disebutkan bahwa konkurensi yang kita bicarakan di sini tidak terkait dengan dilema konkurensi dan konkurensi serta perbedaannya, yang dibahas dalam konteks sistem operasi dan komputasi kinerja tinggi. Paralelisme adalah salah satu cara untuk mencapai konkurensi dalam lingkungan dengan banyak inti, prosesor, atau komputer. Kami berbicara tentang konkurensi dalam arti akses simultan dari berbagai proses ke data umum.

Dan apa, sebenarnya, yang bisa salah, murni secara teoritis?

Saat mengerjakan data bersama, banyak masalah yang terkait dengan konkurensi, juga disebut "kondisi balapan", dapat terjadi. Masalah pertama terjadi saat proses menerima data yang seharusnya tidak diterimanya: data tidak lengkap, sementara, dibatalkan, atau "salah". Masalah kedua adalah ketika proses menerima data basi, yaitu data yang tidak sesuai dengan status database yang terakhir disimpan. Katakanlah beberapa aplikasi telah menarik uang dari akun pengguna dengan saldo nol, karena database mengembalikan status akun ke aplikasi, tidak memperhitungkan penarikan uang terakhir darinya, yang terjadi hanya beberapa milidetik yang lalu. Situasinya begitu-begitu, bukan?

5.2 Transaksi Datang untuk Menyelamatkan Kami

Untuk mengatasi masalah seperti itu, konsep transaksi muncul - sekelompok operasi berurutan tertentu (perubahan status) dengan database, yang merupakan operasi tunggal secara logis. Saya akan memberikan contoh lagi dengan bank - dan bukan kebetulan, karena konsep transaksi muncul, ternyata, justru dalam konteks bekerja dengan uang. Contoh klasik dari sebuah transaksi adalah transfer uang dari satu rekening bank ke rekening bank lainnya: Anda harus terlebih dahulu menarik jumlah dari rekening sumber dan kemudian menyetorkannya ke rekening target.

Agar transaksi ini dapat dilakukan, aplikasi perlu melakukan beberapa tindakan dalam database: memeriksa saldo pengirim, memblokir jumlah pada akun pengirim, menambahkan jumlah tersebut ke akun penerima, dan mengurangi jumlah tersebut dari pengirim. Akan ada beberapa persyaratan untuk transaksi semacam itu. Misalnya, aplikasi tidak dapat menerima informasi saldo yang kedaluwarsa atau salah - misalnya, jika pada saat yang sama transaksi paralel berakhir dengan kesalahan di tengah jalan, dan dana tidak didebit dari akun - dan aplikasi kami telah menerima informasi bahwa dana tersebut dihapuskan.

Untuk mengatasi masalah ini, properti transaksi seperti "isolasi" dipanggil: transaksi kami dieksekusi seolah-olah tidak ada transaksi lain yang dilakukan pada saat yang sama. Basis data kami melakukan operasi bersamaan seolah-olah mengeksekusinya satu demi satu, secara berurutan - sebenarnya, tingkat isolasi tertinggi disebut Strict Serializable . Ya paling tinggi, artinya ada beberapa tingkatan.

"Berhenti," katamu. Pegang kudamu, tuan.

Mari kita ingat bagaimana saya menjelaskan bahwa setiap operasi memiliki waktu panggilan dan waktu eksekusi. Untuk kenyamanan, Anda dapat mempertimbangkan untuk memanggil dan mengeksekusi sebagai 2 tindakan. Kemudian daftar yang disortir dari semua panggilan dan tindakan eksekusi dapat disebut sejarah database. Kemudian tingkat isolasi transaksi adalah kumpulan sejarah. Kami menggunakan tingkat isolasi untuk menentukan cerita mana yang "baik". Saat kami mengatakan bahwa sebuah cerita "melanggar kemampuan serial" atau "tidak dapat diserialkan", maksud kami adalah bahwa cerita tersebut tidak termasuk dalam rangkaian cerita yang dapat diserialkan.

Untuk memperjelas cerita seperti apa yang kita bicarakan, saya akan memberikan contoh. Misalnya, ada semacam sejarah - bacaan perantara . Itu terjadi ketika transaksi A diizinkan untuk membaca data dari baris yang telah dimodifikasi oleh transaksi B lain yang sedang berjalan dan belum dilakukan ("tidak dilakukan") - yaitu, pada kenyataannya, perubahan tersebut belum dilakukan oleh transaksi B, dan sewaktu-waktu dapat membatalkannya. Dan, misalnya, pembacaan yang dibatalkan hanyalah contoh kami dengan transaksi penarikan yang dibatalkan

Ada beberapa kemungkinan anomali. Artinya, anomali adalah semacam keadaan data yang tidak diinginkan yang dapat terjadi selama akses kompetitif ke database. Dan untuk menghindari keadaan tertentu yang tidak diinginkan, database menggunakan tingkat isolasi yang berbeda - yaitu, tingkat perlindungan data yang berbeda dari keadaan yang tidak diinginkan. Level ini (4 buah) terdaftar dalam standar ANSI SQL-92.

Deskripsi tingkat ini tampaknya tidak jelas bagi beberapa peneliti, dan mereka menawarkan klasifikasi mereka sendiri yang lebih rinci. Saya menyarankan Anda untuk memperhatikan Jepsen yang telah disebutkan, serta proyek Hermitage, yang bertujuan untuk mengklarifikasi dengan tepat tingkat isolasi apa yang ditawarkan oleh DBMS tertentu, seperti MySQL atau PostgreSQL. Jika Anda membuka file dari repositori ini, Anda dapat melihat urutan perintah SQL apa yang mereka gunakan untuk menguji database untuk anomali tertentu, dan Anda dapat melakukan hal serupa untuk database yang Anda minati). Inilah salah satu contoh dari repositori untuk membuat Anda tetap tertarik:

-- Database: MySQL

-- Setup before test
create table test (id int primary key, value int) engine=innodb;
insert into test (id, value) values (1, 10), (2, 20);

-- Test the "read uncommited" isolation level on the "Intermediate Reads" (G1b) anomaly
set session transaction isolation level read uncommitted; begin; -- T1
set session transaction isolation level read uncommitted; begin; -- T2
update test set value = 101 where id = 1; -- T1
select * from test; -- T2. Shows 1 => 101
update test set value = 11 where id = 1; -- T1
commit; -- T1
select * from test; -- T2. Now shows 1 => 11
commit; -- T2

-- Result: doesn't prevent G1b

Penting untuk dipahami bahwa untuk database yang sama, sebagai aturan, Anda dapat memilih salah satu dari beberapa jenis isolasi. Mengapa tidak memilih insulasi terkuat? Karena, seperti segala sesuatu dalam ilmu komputer, tingkat isolasi yang dipilih harus sesuai dengan trade-off yang siap kami lakukan - dalam hal ini, trade-off dalam kecepatan eksekusi: semakin kuat level isolasi, semakin lambat permintaannya. diproses. Untuk memahami tingkat isolasi yang Anda butuhkan, Anda perlu memahami persyaratan untuk aplikasi Anda, dan untuk memahami apakah database yang Anda pilih menawarkan tingkat ini, Anda harus melihat dokumentasi - untuk sebagian besar aplikasi ini sudah cukup, tetapi jika Anda memiliki beberapa persyaratan yang sangat ketat, lebih baik mengatur tes seperti yang dilakukan oleh orang-orang dari proyek Hermitage.

5.3 "I" dan huruf lainnya dalam ACID

Isolasi pada dasarnya adalah apa yang orang maksud ketika mereka berbicara tentang ACID secara umum. Dan untuk alasan inilah saya memulai analisis akronim ini dengan isolasi, dan tidak berurutan, seperti yang biasa dilakukan oleh mereka yang mencoba menjelaskan konsep ini. Sekarang mari kita lihat tiga huruf yang tersisa.

Ingat kembali contoh kita dengan transfer bank. Transaksi untuk mentransfer dana dari satu akun ke akun lainnya mencakup operasi penarikan dari akun pertama dan operasi pengisian ulang pada akun kedua. Jika operasi pengisian akun kedua gagal, Anda mungkin tidak ingin operasi penarikan dari akun pertama terjadi. Dengan kata lain, baik transaksi berhasil sepenuhnya, atau tidak terjadi sama sekali, tetapi tidak dapat dilakukan hanya untuk sebagian saja. Properti ini disebut "atomicity", dan merupakan "A" dalam ACID.

Ketika transaksi kami dieksekusi, seperti operasi apa pun, ia mentransfer database dari satu status valid ke status lainnya. Beberapa database menawarkan apa yang disebut batasan - yaitu, aturan yang berlaku untuk data yang disimpan, misalnya, mengenai kunci primer atau sekunder, indeks, nilai default, tipe kolom, dll. Jadi, saat melakukan transaksi, kita harus yakin bahwa semua kendala tersebut akan terpenuhi.

Jaminan ini disebut "konsistensi" dan surat Cdalam ACID (jangan bingung dengan konsistensi dari dunia aplikasi terdistribusi, yang akan kita bicarakan nanti). Saya akan memberikan contoh yang jelas untuk konsistensi dalam arti ACID: aplikasi untuk toko online ingin menambahkan ordersbaris ke tabel, dan ID dari tabel product_idakan ditunjukkan di kolom - tipikal .productsforeign key

Jika produk, katakanlah, telah dihapus dari bermacam-macam, dan, karenanya, dari database, maka operasi penyisipan baris tidak boleh terjadi, dan kami akan mendapatkan kesalahan. Jaminan ini, dibandingkan dengan yang lain, menurut saya agak dibuat-buat - jika hanya karena penggunaan aktif kendala dari database berarti mengalihkan tanggung jawab atas data (serta mengalihkan sebagian logika bisnis, jika kita berbicara tentang kendala seperti CHECK ) dari aplikasi ke database, yang, seperti yang mereka katakan sekarang, memang begitu.

Dan akhirnya, tetap D- "ketahanan" (daya tahan). Kegagalan sistem atau kegagalan lainnya tidak boleh menyebabkan hilangnya hasil transaksi atau konten basis data. Artinya, jika database menjawab bahwa transaksi berhasil, maka ini berarti data tersebut direkam dalam memori non-volatile - misalnya di hard disk. Ngomong-ngomong, ini tidak berarti Anda akan langsung melihat data pada permintaan baca berikutnya.

Beberapa hari yang lalu, saya bekerja dengan DynamoDB dari AWS (Amazon Web Services), dan mengirim beberapa data untuk disimpan, dan setelah menerima jawaban HTTP 200(OK), atau sesuatu seperti itu, saya memutuskan untuk memeriksanya - dan tidak melihat ini data dalam database selama 10 Detik berikutnya. Artinya, DynamoDB mengkomit data saya, tetapi tidak semua node langsung disinkronkan untuk mendapatkan salinan data terbaru (walaupun mungkin ada di cache). Di sini kami kembali naik ke wilayah konsistensi dalam konteks sistem terdistribusi, tetapi waktu untuk membicarakannya masih belum tiba.

Jadi sekarang kita tahu apa itu jaminan ACID. Dan kami bahkan tahu mengapa mereka berguna. Tetapi apakah kita benar-benar membutuhkannya di setiap aplikasi? Dan jika tidak, kapan tepatnya? Apakah semua DB menawarkan jaminan ini, dan jika tidak, apa yang mereka tawarkan?