5.1 Ang isyu ng simultaneity

Magsimula tayo sa isang maliit na malayong teorya.

Ang anumang sistema ng impormasyon (o simpleng, isang application) na nilikha ng mga programmer ay binubuo ng ilang karaniwang mga bloke, bawat isa ay nagbibigay ng isang bahagi ng kinakailangang pag-andar. Halimbawa, ang cache ay ginagamit upang matandaan ang resulta ng isang resource-intensive na operasyon upang matiyak ang mas mabilis na pagbabasa ng data ng kliyente, ang mga tool sa pagpoproseso ng stream ay nagbibigay-daan sa iyo na magpadala ng mga mensahe sa iba pang mga bahagi para sa asynchronous na pagproseso, at ang mga tool sa pagpoproseso ng batch ay ginagamit upang " rake" ang mga naipon na volume ng data na may ilang periodicity. .

At sa halos bawat application, ang mga database (DB) ay kasangkot sa isang paraan o iba pa, na karaniwang gumaganap ng dalawang function: mag-imbak ng data kapag natanggap mula sa iyo at sa ibang pagkakataon ay ibibigay ang mga ito sa iyo kapag hiniling. Bihirang may nag-iisip na lumikha ng sarili nilang database, dahil marami nang mga handa na solusyon. Ngunit paano mo pipiliin ang tama para sa iyong aplikasyon?

Kaya, isipin natin na nagsulat ka ng isang application na may isang mobile interface na nagbibigay-daan sa iyo upang i-load ang isang naunang naka-save na listahan ng mga gawain sa paligid ng bahay - iyon ay, basahin mula sa database, at dagdagan ito ng mga bagong gawain, pati na rin bigyang-priyoridad ang bawat partikular na gawain - mula 1 ( pinakamataas) hanggang 3 (pinakamababa). Sabihin nating ang iyong mobile application ay ginagamit lamang ng isang tao sa bawat pagkakataon. Ngunit ngayon ay naglakas-loob kang sabihin sa iyong ina ang tungkol sa iyong paglikha, at ngayon siya ay naging pangalawang regular na gumagamit. Ano ang mangyayari kung magpasya ka sa parehong oras, sa parehong millisecond, na magtakda ng ilang gawain - "hugasan ang mga bintana" - sa ibang antas ng priyoridad?

Sa mga propesyonal na termino, ang iyong mga query sa database ng iyong ina ay maaaring ituring bilang 2 proseso na gumawa ng isang query sa database. Ang proseso ay isang entity sa isang computer program na maaaring tumakbo sa isa o higit pang mga thread. Karaniwan, ang isang proseso ay may larawan ng machine code, memorya, konteksto, at iba pang mapagkukunan. Sa madaling salita, ang proseso ay maaaring mailalarawan bilang ang pagpapatupad ng mga tagubilin ng programa sa processor. Kapag humiling ang iyong aplikasyon sa database, pinag-uusapan natin ang katotohanang pinoproseso ng iyong database ang kahilingang natanggap sa network mula sa isang proseso. Kung mayroong dalawang user na nakaupo sa application sa parehong oras, maaaring mayroong dalawang proseso sa anumang partikular na sandali sa oras.

Kapag ang ilang proseso ay humiling sa database, makikita ito sa isang tiyak na estado. Ang stateful system ay isang sistema na naaalala ang mga nakaraang kaganapan at nag-iimbak ng ilang impormasyon, na tinatawag na "estado". Ang isang variable na idineklara bilang integermaaaring magkaroon ng estado na 0, 1, 2, o sabihing 42. Ang Mutex (mutual exclusion) ay may dalawang estado: naka-lock o naka-unlock , tulad ng isang binary semaphore ("kinakailangan" kumpara sa "inilabas") at sa pangkalahatan ay binary (binary) mga uri ng data at variable na maaaring magkaroon lamang ng dalawang estado - 1 o 0.

Batay sa konsepto ng estado, maraming mga istrukturang matematikal at inhinyero ang nakabatay, tulad ng isang finite automat - isang modelo na may isang input at isang output at nasa isa sa isang may hangganan na hanay ng mga estado sa bawat sandali ng oras - at ang "estado ” pattern ng disenyo, kung saan nagbabago ang gawi ng isang bagay depende sa panloob na estado (halimbawa, depende sa kung anong halaga ang itinalaga sa isa o ibang variable).

Kaya, karamihan sa mga bagay sa mundo ng makina ay may ilang estado na maaaring magbago sa paglipas ng panahon: ang aming pipeline, na nagpoproseso ng malaking data packet, ay naglalabas ng error at nabigo , o ang Wallet object property, na nag-iimbak ng halaga ng pera na natitira sa user account, mga pagbabago pagkatapos ng mga resibo ng payroll.

Ang isang transition (“transition”) mula sa isang estado patungo sa isa pa—sabihin, mula sa progreso hanggang sa nabigo —ay tinatawag na operasyon. Marahil, alam ng lahat ang mga pagpapatakbo ng CRUD - create, read, update, deleteo mga katulad na pamamaraan ng HTTP - POST, GET, PUT, DELETE. Ngunit ang mga programmer ay madalas na nagbibigay ng iba pang mga pangalan sa mga operasyon sa kanilang code, dahil ang operasyon ay maaaring maging mas kumplikado kaysa sa pagbabasa lamang ng isang tiyak na halaga mula sa database - maaari din itong suriin ang data, at pagkatapos ay ang aming operasyon, na kinuha ang anyo ng isang function, tatawagin, halimbawa, At validate()sino ang nagsasagawa ng mga pagpapatakbong ito? mga prosesong inilarawan na.

Kaunti pa, at mauunawaan mo kung bakit inilalarawan ko ang mga termino sa ganoong detalye!

Anumang operasyon - ito man ay isang function, o, sa mga distributed system, ang pagpapadala ng isang kahilingan sa isa pang server - ay may 2 katangian: ang invocation time at ang completion time (completion time) , na magiging mahigpit na mas malaki kaysa sa invocation time (mga mananaliksik mula sa Jepsen magpatuloy mula sa mga teoretikal na pagpapalagay na ang parehong mga timestamp na ito ay bibigyan ng haka-haka, ganap na naka-synchronize, magagamit sa buong mundo na mga orasan).

Isipin natin ang aming application sa listahan ng gagawin. Gumagawa ka ng kahilingan sa database sa pamamagitan ng mobile interface sa 14:00:00.014, at ang iyong ina sa 13:59:59.678(iyon ay, 336 milliseconds bago) na-update ang listahan ng dapat gawin sa pamamagitan ng parehong interface, na nagdaragdag ng paghuhugas ng mga pinggan dito. Isinasaalang-alang ang pagkaantala sa network at ang posibleng pila ng mga gawain para sa iyong database, kung, bilang karagdagan sa iyo at sa iyong ina, lahat ng mga kaibigan ng iyong ina ay gumagamit din ng iyong aplikasyon, ang database ay maaaring isagawa ang kahilingan ng ina pagkatapos nitong iproseso ang sa iyo. Sa madaling salita, may pagkakataon na dalawa sa iyong mga kahilingan, pati na rin ang mga kahilingan mula sa mga kasintahan ng iyong ina, ay ipapadala sa parehong data sa parehong oras (kasabay).

Kaya't nakarating kami sa pinakamahalagang termino sa larangan ng mga database at ibinahagi na mga application - concurrency. Ano nga ba ang ibig sabihin ng pagkakasabay ng dalawang operasyon? Kung ang ilang operasyon T1 at ilang operasyon T2 ay ibinigay, kung gayon:

  • Maaaring simulan ang T1 bago ang oras ng pagsisimula ng pagpapatupad ng T2, at matapos sa pagitan ng oras ng pagsisimula at pagtatapos ng T2
  • Maaaring magsimula ang T2 bago ang oras ng pagsisimula ng T1, at matapos sa pagitan ng simula at pagtatapos ng T1
  • Maaaring simulan at tapusin ang T1 sa pagitan ng oras ng pagsisimula at pagtatapos ng T1 execution
  • at anumang iba pang senaryo kung saan ang T1 at T2 ay may ilang karaniwang oras ng pagpapatupad

Malinaw na sa loob ng balangkas ng panayam na ito, pangunahing pinag-uusapan natin ang tungkol sa mga query na pumapasok sa database at kung paano nakikita ng database management system ang mga query na ito, ngunit ang terminong concurrency ay mahalaga, halimbawa, sa konteksto ng mga operating system. Hindi ako masyadong lalayo sa paksa ng artikulong ito, ngunit sa tingin ko mahalagang banggitin na ang concurrency na pinag-uusapan natin dito ay hindi nauugnay sa dilemma ng concurrency at concurrency at ang kanilang pagkakaiba, na tinalakay sa konteksto ng mga operating system at high-performance.computing. Ang parallelism ay isang paraan para makamit ang concurrency sa isang environment na may maraming core, processor, o computer. Pinag-uusapan natin ang tungkol sa concurrency sa kahulugan ng sabay-sabay na pag-access ng iba't ibang mga proseso sa karaniwang data.

At ano, sa katunayan, ang maaaring magkamali, puro theoretically?

Kapag nagtatrabaho sa nakabahaging data, maraming problemang nauugnay sa concurrency, na tinatawag ding "mga kundisyon ng lahi", ang maaaring mangyari. Ang unang problema ay nangyayari kapag ang isang proseso ay nakatanggap ng data na hindi dapat natanggap: hindi kumpleto, pansamantala, nakansela, o kung hindi man ay "maling" na data. Ang pangalawang problema ay kapag ang proseso ay tumatanggap ng lipas na data, iyon ay, ang data na hindi tumutugma sa huling na-save na estado ng database. Sabihin nating may ilang application na nag-withdraw ng pera mula sa account ng isang user na may zero na balanse, dahil ibinalik ng database ang status ng account sa application, hindi isinasaalang-alang ang huling pag-withdraw ng pera mula dito, na nangyari ilang milliseconds lang ang nakalipas. Ganun-ganun ang sitwasyon, di ba?

5.2 Dumating ang Mga Transaksyon para Iligtas Kami

Upang malutas ang mga naturang problema, lumitaw ang konsepto ng isang transaksyon - isang tiyak na pangkat ng mga sunud-sunod na operasyon (mga pagbabago sa estado) na may isang database, na isang lohikal na solong operasyon. Magbibigay ako ng isang halimbawa sa isang bangko muli - at hindi sa pamamagitan ng pagkakataon, dahil ang konsepto ng isang transaksyon ay lumitaw, tila, tiyak sa konteksto ng pagtatrabaho sa pera. Ang klasikong halimbawa ng isang transaksyon ay ang paglipat ng pera mula sa isang bank account patungo sa isa pa: kailangan mo munang i-withdraw ang halaga mula sa source account at pagkatapos ay ideposito ito sa target na account.

Para maisagawa ang transaksyong ito, ang application ay kailangang magsagawa ng ilang mga aksyon sa database: pagsuri sa balanse ng nagpadala, pagharang sa halaga sa account ng nagpadala, pagdaragdag ng halaga sa account ng tatanggap, at pagbabawas ng halaga mula sa nagpadala. Magkakaroon ng ilang mga kinakailangan para sa naturang transaksyon. Halimbawa, ang application ay hindi makakatanggap ng hindi napapanahon o hindi tamang impormasyon tungkol sa balanse - halimbawa, kung sa parehong oras ang isang parallel na transaksyon ay natapos sa isang error sa kalagitnaan, at ang mga pondo ay hindi na-debit mula sa account - at ang aming aplikasyon ay nakatanggap na ng impormasyon na ang mga pondo ay tinanggal.

Upang malutas ang problemang ito, ang naturang pag-aari ng isang transaksyon bilang "paghihiwalay" ay tinawag: ang aming transaksyon ay isinasagawa na parang walang ibang mga transaksyon na ginagawa sa parehong sandali. Ang aming database ay gumaganap ng mga kasabay na operasyon na parang isa-isa itong isinasagawa, sunud-sunod - sa katunayan, ang pinakamataas na antas ng paghihiwalay ay tinatawag na Strict Serializable . Oo, ang pinakamataas, na nangangahulugan na mayroong ilang mga antas.

"Tumigil ka," sabi mo. Hawakan ang iyong mga kabayo, ginoo.

Tandaan natin kung paano ko inilarawan na ang bawat operasyon ay may oras ng tawag at oras ng pagpapatupad. Para sa kaginhawahan, maaari mong isaalang-alang ang pagtawag at pag-execute bilang 2 aksyon. Pagkatapos ang pinagsunod-sunod na listahan ng lahat ng mga aksyon sa tawag at pagpapatupad ay maaaring tawaging kasaysayan ng database. Pagkatapos ang antas ng paghihiwalay ng transaksyon ay isang hanay ng mga kasaysayan. Gumagamit kami ng mga antas ng paghihiwalay upang matukoy kung aling mga kuwento ang "mahusay". Kapag sinabi namin na ang isang kuwento ay "nasira ang serializability" o "ay hindi serializable", ang ibig naming sabihin ay ang kuwento ay wala sa hanay ng mga serializable na kwento.

Upang maging malinaw kung anong uri ng mga kuwento ang ating pinag-uusapan, magbibigay ako ng mga halimbawa. Halimbawa, mayroong ganitong uri ng kasaysayan - intermediate read . Nangyayari ito kapag pinahintulutan ang transaksyon A na magbasa ng data mula sa isang row na binago ng isa pang tumatakbong transaksyon B at hindi pa nagagawa ("not committed") - iyon ay, sa katunayan, ang mga pagbabago ay hindi pa nagagawa sa wakas ng transaksyon B, at maaari nitong kanselahin ang mga ito anumang oras. At, halimbawa, ang na-abort na pagbabasa ay ang aming halimbawa lamang sa isang kinanselang transaksyon sa pag-withdraw

Mayroong ilang mga posibleng anomalya. Iyon ay, ang mga anomalya ay ilang uri ng hindi kanais-nais na estado ng data na maaaring mangyari sa panahon ng mapagkumpitensyang pag-access sa database. At upang maiwasan ang ilang mga hindi gustong estado, gumagamit ang mga database ng iba't ibang antas ng paghihiwalay - iyon ay, iba't ibang antas ng proteksyon ng data mula sa mga hindi gustong estado. Ang mga antas na ito (4 na piraso) ay nakalista sa pamantayan ng ANSI SQL-92.

Ang paglalarawan ng mga antas na ito ay tila malabo sa ilang mga mananaliksik, at nag-aalok sila ng kanilang sarili, mas detalyado, mga pag-uuri. Ipinapayo ko sa iyo na bigyang-pansin ang nabanggit na Jepsen, gayundin ang proyekto ng Hermitage, na naglalayong linawin nang eksakto kung anong mga antas ng paghihiwalay ang inaalok ng partikular na DBMS, tulad ng MySQL o PostgreSQL. Kung bubuksan mo ang mga file mula sa repositoryong ito, makikita mo kung anong pagkakasunud-sunod ng mga SQL command ang ginagamit nila upang subukan ang database para sa ilang mga anomalya, at maaari kang gumawa ng katulad para sa mga database na interesado ka). Narito ang isang halimbawa mula sa repository upang mapanatili kang interesado:

-- 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

Mahalagang maunawaan na para sa parehong database, bilang panuntunan, maaari kang pumili ng isa sa ilang uri ng paghihiwalay. Bakit hindi piliin ang pinakamatibay na pagkakabukod? Dahil, tulad ng lahat ng bagay sa computer science, ang napiling antas ng paghihiwalay ay dapat tumugma sa isang trade-off na handa naming gawin - sa kasong ito, isang trade-off sa bilis ng pagpapatupad: mas malakas ang antas ng paghihiwalay, mas mabagal ang mga kahilingan. naproseso. Upang maunawaan kung anong antas ng paghihiwalay ang kailangan mo, kailangan mong maunawaan ang mga kinakailangan para sa iyong aplikasyon, at upang maunawaan kung ang database na iyong pinili ay nag-aalok ng antas na ito, kakailanganin mong tingnan ang dokumentasyon - para sa karamihan ng mga aplikasyon ito ay magiging sapat, ngunit kung mayroon kang ilang partikular na mahigpit na mga kinakailangan, mas mahusay na ayusin ang isang pagsubok tulad ng ginagawa ng mga lalaki mula sa proyekto ng Hermitage.

5.3 "I" at iba pang mga titik sa ACID

Ang paghihiwalay ay karaniwang kung ano ang ibig sabihin ng mga tao kapag pinag-uusapan nila ang tungkol sa ACID sa pangkalahatan. At ito ay para sa kadahilanang ito na sinimulan ko ang pagsusuri ng acronym na ito na may paghihiwalay, at hindi napunta sa pagkakasunud-sunod, tulad ng karaniwang ginagawa ng mga sumusubok na ipaliwanag ang konseptong ito. Ngayon tingnan natin ang natitirang tatlong titik.

Alalahanin muli ang aming halimbawa sa isang bank transfer. Ang isang transaksyon upang maglipat ng mga pondo mula sa isang account patungo sa isa pa ay may kasamang operasyon sa pag-withdraw mula sa unang account at isang operasyon sa muling pagdadagdag sa pangalawa. Kung nabigo ang pagpapatakbo ng muling pagdadagdag ng pangalawang account, malamang na hindi mo gustong mangyari ang pagpapatakbo ng pag-alis mula sa unang account. Sa madaling salita, maaaring ganap na magtagumpay ang transaksyon, o hindi ito nangyayari, ngunit hindi ito maaaring gawin para lamang sa ilang bahagi. Ang property na ito ay tinatawag na "atomicity", at ito ay isang "A" sa ACID.

Kapag ang aming transaksyon ay naisakatuparan, kung gayon, tulad ng anumang operasyon, inililipat nito ang database mula sa isang wastong estado patungo sa isa pa. Ang ilang mga database ay nag-aalok ng tinatawag na mga hadlang - iyon ay, mga panuntunan na nalalapat sa nakaimbak na data, halimbawa, tungkol sa pangunahin o pangalawang key, index, default na halaga, mga uri ng column, atbp. Kaya, kapag gumagawa ng isang transaksyon, dapat nating siguraduhin na ang lahat ng mga hadlang na ito ay matutupad.

Ang garantiyang ito ay tinatawag na "consistency" at isang liham Csa ACID (hindi malito sa pagkakapare-pareho mula sa mundo ng mga ipinamamahaging aplikasyon, na pag-uusapan natin mamaya). Magbibigay ako ng isang malinaw na halimbawa para sa pagkakapare-pareho sa kahulugan ng ACID: ang isang application para sa isang online na tindahan ay gustong magdagdag ordersng isang row sa talahanayan, at ang ID mula sa talahanayan product_iday ipahiwatig sa column - typical .productsforeign key

Kung ang produkto, sabihin nating, ay tinanggal mula sa assortment, at, nang naaayon, mula sa database, kung gayon ang row insert operation ay hindi dapat mangyari, at magkakaroon tayo ng error. Ang garantiyang ito, kumpara sa iba, ay medyo malayo, sa aking opinyon - kung dahil lamang sa aktibong paggamit ng mga hadlang mula sa database ay nangangahulugan ng paglilipat ng responsibilidad para sa data (pati na rin ang bahagyang paglilipat ng lohika ng negosyo, kung pinag-uusapan natin ang tungkol sa tulad ng isang pagpilit bilang CHECK ) mula sa aplikasyon sa database, na, gaya ng sinasabi nila ngayon, ay ganoon lang.

At sa wakas, nananatili ito D- "paglaban" (tibay). Ang pagkabigo ng system o anumang iba pang pagkabigo ay hindi dapat humantong sa pagkawala ng mga resulta ng transaksyon o nilalaman ng database. Iyon ay, kung ang database ay tumugon na ang transaksyon ay matagumpay, nangangahulugan ito na ang data ay naitala sa hindi pabagu-bagong memorya - halimbawa, sa isang hard disk. Ito, sa pamamagitan ng paraan, ay hindi nangangahulugan na makikita mo kaagad ang data sa susunod na kahilingan sa pagbasa.

Noong isang araw lang, nagtatrabaho ako sa DynamoDB mula sa AWS (Amazon Web Services), at nagpadala ng ilang data para sa pag-save, at pagkatapos makatanggap ng sagot HTTP 200(OK), o isang katulad nito, nagpasya akong suriin ito - at hindi ko ito nakita. data sa database para sa susunod na 10 Segundo. Iyon ay, ginawa ng DynamoDB ang aking data, ngunit hindi lahat ng mga node ay agad na naka-synchronize upang makuha ang pinakabagong kopya ng data (bagaman maaaring ito ay nasa cache). Dito kami muling umakyat sa teritoryo ng pagkakapare-pareho sa konteksto ng mga ipinamamahaging sistema, ngunit ang oras upang pag-usapan ito ay hindi pa rin dumating.

Kaya ngayon alam na natin kung ano ang mga garantiya ng ACID. At alam din natin kung bakit kapaki-pakinabang ang mga ito. Ngunit kailangan ba talaga natin ang mga ito sa bawat aplikasyon? At kung hindi, kailan ba talaga? Ang lahat ba ng DB ay nag-aalok ng mga garantiyang ito, at kung hindi, ano ang kanilang inaalok sa halip?