Sustav DB InterSystems IRIS podržava zanimljive strukture za pohranu podataka - globalne. U suštini, to su višerazinski ključevi s različitim dodatnim mogućnostima u obliku transakcija, brzih funkcija za pretraživanje stabala podataka, zaključavanja i vlastitog jezika ObjectScript.
Više o globalima u seriji članaka „Globali - mačevi blago za pohranu podataka“:
Zanima me kako su implementirane transakcije u globalima, koje su njihove karakteristike. Jer to je potpuno drugačija struktura za pohranu podataka, u odnosu na svima poznate tablice. Mnogo je niskorazinskija.
Kao što je poznato iz teorije relacijskih baza podataka, dobra implementacija transakcija mora zadovoljiti zahtjeve :
A - Atomic (atomarnost). Zapisane su sve promjene napravljene u transakciji ili ništa.
S - Consistency (dosljednost). Nakon završetka transakcije, logičko stanje baze podataka mora biti međusobno dosljedno. Velikim dijelom, ovaj zahtjev odnosi se na programera, ali u slučaju SQL-baza, to se također odnosi na vanjske ključeve.
I - Isolate (izolacija). Paralelno izvršene transakcije ne smiju utjecati jedna na drugu.
D - Durable (izdržljivost). Nakon uspješnog završetka transakcije, problemi na nižim razinama (npr. prekid napajanja) ne smiju utjecati na podatke promijenjene transakcijom.
Globali su neredne strukture podataka. Kreirani su za izuzetno brzi rad na vrlo ograničenom hardveru. Poglejmo implementaciju transakcija u globalima pomoću .
Za podršku transakcijama u IRIS-u koriste se naredbe: , , .
1. Atomarnost
Najlakše je provjeriti atomarnost. Provjeravamo iz konzole baze podataka.
Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMITNakon toga izvlačimo:
Write ^a(1), “ ”, ^a(2), “ ”, ^a(3)Dobivamo:
1 2 3Sve je u redu. Atomarnost je očuvana: sve promjene su zapisane.
Povećajmo složenost, uvest ćemo pogrešku i vidjeti kako će se transakcija očuvati, djelomično ili nikako.
Još jednom provjeravamo atomarnost:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3Nakon čega ćemo prisilno zaustaviti kontejner, pokrenuti ga i pogledati.
docker kill my-irisOva naredba je gotovo ekvivalentna prisilnom isključivanju napajanja, jer šalje signal hitnog zaustavljanja procesa SIGKILL.
Možda je transakcija djelomično sačuvana?
WRITE ^a(1), ^a(2), ^a(3)
^
^a(1)— Ne, nije sačuvana.
Isprobajmo naredbu za povratak:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TROLLBACK
WRITE ^a(1), ^a(2), ^a(3)
^
^a(1)Također ništa nije sačuvano.
2. Dosljednost
Budući da se u bazama na globalima ključevi također rade na globalima (podsjećam, global je niža struktura za pohranu podataka od relacijske tablice), za ispunjavanje zahtjeva dosljednosti, promjenu ključa treba uključiti u istu transakciju kao i promjenu globala.
Na primjer, imamo global ^person, u kojem pohranjujemo osobne podatke i kao ključ koristimo OIB.
^person(1234567, 'firstname') = 'Sergey'
^person(1234567, 'lastname') = 'Kamenev'
^person(1234567, 'phone') = '+74995555555
...Kako bismo imali brz pretraživanje prema prezimenu i imenu, napravili smo ključ ^index.
^index('Kamenev', 'Sergey', 1234567) = 1Kako bi baza bila dosljedna, moramo dodavati osobu na sljedeći način:
TSTART
^person(1234567, 'firstname') = 'Sergey'
^person(1234567, 'lastname') = 'Kamenev'
^person(1234567, 'phone') = '+74995555555
^index('Kamenev', 'Sergey', 1234567) = 1
TCOMMITDakle, pri brisanju također moramo koristiti transakciju:
TSTART
Kill ^person(1234567)
ZKill ^index('Kamenev', 'Sergey', 1234567)
TCOMMITDrugim riječima, ispunjavanje zahtjeva dosljednosti potpuno leži na programerovim leđima. No kada su u pitanju globali — to je normalno, s obzirom na njihovu nisku razinu.
3. Izolacija
Ovdje počinju komplikacije. Puno korisnika istovremeno radi na istoj bazi, mijenjajući iste podatke.
Situacija je slična onoj kada mnogi korisnici istovremeno rade s istim repozitorijem koda i pokušavaju istovremeno poslati promjene u mnoge datoteke.
Baza podataka to mora rješavati u stvarnom vremenu. S obzirom na to da u ozbiljnim tvrtkama postoji čak i posebna osoba koja je odgovorna za kontrolu verzija (za spajanje grana, rješavanje sukoba itd.), a BDP mora raditi sve to u stvarnom vremenu, jasno postaje složenost zadatka i ispravnost dizajniranja baze podataka i koda koji ju održava.
Baza podataka ne može shvatiti svrhu radnji koje korisnici poduzimaju kako bi spriječila sukobe kada rade s istim podacima. Može samo poništiti jednu transakciju koja je u sukobu s drugom ili ih izvršiti uzastopno.
Još jedan problem je što tijekom izvršenja transakcije (prije potvrde), stanje baze može biti nesuglasno, stoga je poželjno da druge transakcije nemaju pristup nesuglasnom stanju baze podataka, što se postiže u relacijskim bazama na mnogo načina: stvaranjem snimaka, višestrukim verzijama redaka itd.
Pri paralelnom izvršavanju transakcija važno nam je da ne ometaju jedna drugu. To je svojstvo izolacije.
SQL definira 4 razine izolacije:
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
Razmotrit ćemo svaku razinu posebno. Troškovi implementacije svake razine rastu skoro eksponencijalno.
READ UNCOMMITTED — to je najniža razina izolacije, ali istovremeno i najbrža. Transakcije mogu čitati promjene koje su jedna drugoj unijele.
READ COMMITTED — to je sljedeća razina izolacije koja predstavlja kompromis. Transakcije ne mogu čitati promjene koje su jedna drugoj unijele do potvrde, ali mogu čitati sve promjene unesene nakon potvrde.
Ako imamo dugu transakciju T1, tijekom koje su se dogodile potvrde u transakcijama T2, T3 … Tn, koje su radile s istim podacima kao i T1, prilikom zahtjeva za podacima u T1 svaki put ćemo dobiti drugačiji rezultat. Ovaj fenomen naziva se neponovljivo čitanje.
REPEATABLE READ — na ovoj razini izolacije nemamo fenomena neponovljivog čitanja, budući da se za svaki zahtjev za čitanje podataka stvara snimak rezultata podataka, a prilikom ponovne uporabe u istoj transakciji koriste se podaci iz snimka. Međutim, na ovoj razini izolacije može doći do čitanja fantomskih podataka. Mislimo na čitanje novih redaka koji su dodani paralelnim fiksiranim transakcijama.
SERIALIZABLE — najviša razina izolacije. Karakterizira je to što su podaci na bilo koji način korišteni u transakciji (čitanje ili izmjena) dostupni drugim transakcijama tek nakon završetka prve transakcije.
Prvo ćemo vidjeti postoji li izolacija operacija u transakciji od glavnog toka. Otvorimo 2 prozora terminala.
Kill ^t
Write ^t(1)
2
TSTART
Set ^t(1)=2Izolacija ne postoji. Jedan tok vidi što radi drugi koji je otvorio transakciju.
Pogledajmo vide li transakcije različitih tokova ono što se događa unutar njih.
Otvorimo 2 prozora terminala i otvorimo 2 paralelne transakcije.
kill ^t
TSTART
Write ^t(1)
3
TSTART
Set ^t(1)=3
Paralelne transakcije vide podatke jedne druge. Tako smo dobili najjednostavniju, ali i najbržu razinu izolacije READ UNCOMMITED.
To se moglo očekivati za globalne, za koje je uvijek brzina bila na prvom mjestu.
Što učiniti ako nam zatreba viša razina izolacije u operacijama na globalima?
Ovdje trebamo razmisliti zašto uopće postoje razine izolacije i kako one rade.
Najviša razina izolacije SERIALIZE znači da je rezultat paralelno izvršenih transakcija ekvivalentan njihovom sekvencijalnom izvršenju, što jamči odsustvo kolizija.
To možemo učiniti pomoću pravilnih zaključavanja u ObjectScriptu, koje imaju mnoštvo različitih načina primjene: možemo raditi obične, inkrementne, višestruke zaključake komandom .
Niže razine izolacije su kompromisi s ciljem povećanja brzine rada baze podataka.
Pogledajmo kako možemo postići različite razine izolacije pomoću zaključavanja.
Ova naredba omogućuje uzimanje ne samo ekskluzivnih zaključavanja potrebnih za promjenu podataka, već i takozvanih shared, koje mogu uzimati paralelno odjednom više tokova kada im je potrebno čitati podatke koji ne bi trebali biti promijenjeni od strane drugih procesa tijekom čitanja.
Više o dvofaznome metodu zaključavanja na hrvatskom i engleskom jeziku:
→
→
Težina leži u tome što stanje baze tijekom transakcije može biti nekonzistentno, ali ti nekonzistentni podaci su vidljivi drugim procesima. Kako to izbjeći?
Napravit ćemo s zaključavanjima takva vidna okna u kojima će stanje baze biti konzistentno. I svi pristupi tim vidnim oknima konzistentnog stanja bit će kontrolirani zaključavanjima.
Dijeljene blokade istih podataka su višekratne — mogu ih preuzeti više procesa. Ove blokade onemogućuju drugim procesima da mijenjaju podatke, tj. koriste se za formiranje prozora dosljednog stanja baze podataka.
Ekskluzivne blokade koriste se za promjene podataka — takvu blokadu može preuzeti samo jedan proces. Eksplicitnu blokadu može preuzeti:
- Bilo koji proces, ako su podaci slobodni.
- Samo onaj proces koji ima na tim podacima dijeljenu blokadu i koji je prvi zatražio ekskluzivnu blokadu.

Što je uži prozor vidljivosti, to duže drugi procesi moraju čekati, ali stanje baze podataka može biti dosljednije u njemu.
READ_COMMITED — suština ovog nivoa je da vidimo samo potvrđene podatke iz drugih tokova. Ako podaci u drugoj transakciji još nisu potvrđeni, vidimo njihovu staru verziju.
To nam omogućava da paraleliziramo rad umjesto da čekamo oslobađanje blokade.
Bez posebnih trikova nećemo moći vidjeti staru verziju podataka u IRIS-u, stoga ćemo se morati osloniti na blokade.
Shodno tome, morat ćemo uz pomoć dijeljenih blokada dozvoliti čitanje podataka samo u trenucima dosljednosti.
Pretpostavimo da imamo bazu korisnika ^person, koji si međusobno transferiraju novac.
Moment transfera od osobe 123 ka osobi 242:
LOCK +^person(123), +^person(242)
Set ^person(123, amount) = ^person(123, amount) - amount
Set ^person(242, amount) = ^person(242, amount) + amount
LOCK -^person(123), -^person(242)Moment zahtjeva za iznosom novca od osobe 123 prije otpisa mora biti praćen ekskluzivnom blokadom (po defaultu):
LOCK +^person(123)
Write ^person(123)A ako je potrebno prikazati stanje računa u korisničkom sučelju, može se koristiti dijeljena blokada ili je uopće ne koristiti:
LOCK +^person(123)#”S”
Write ^person(123)Međutim, ako pretpostavimo da operacije s bazom podataka odvijaju praktički trenutno (podsjećam, globalne promjene su mnogo niže razine od relacijskih tablica), potreba za ovim nivoom opada.
REPEATABLE READ — na ovom nivou izolacije dopušteno je da postoje više čitanja podataka koja mogu biti promijenjena paralelnim transakcijama.
Shodno tome, morat ćemo staviti dijeljenu blokadu na čitanje podataka koje mijenjamo i ekskluzivne blokade na podatke koje mijenjamo.
Operator LOCK allows you to list all necessary locks in detail within a single operator, which can be quite numerous.
LOCK +^person(123, amount)#"S"
reading ^person(123, amount)other operations (during this time parallel threads try to modify ^person(123, amount), but cannot)
LOCK +^person(123, amount)
modifying ^person(123, amount)
LOCK -^person(123, amount)
reading ^person(123, amount)
LOCK -^person(123, amount)#"S"When listing locks separated by commas, they are taken sequentially; however, if done this way:
LOCK +(^person(123),^person(242))they are taken atomically all at once.
SERIALIZE — we will have to set locks so that ultimately all transactions that involve shared data are executed sequentially. For this approach, most locks should be exclusive and applied to the smallest areas of the global for performance.
If we talk about withdrawals in the global ^person, only the SERIALIZE isolation level is acceptable for it, since money must be spent strictly sequentially; otherwise, it's possible to spend the same amount multiple times.
4. Longevity
I conducted tests by hard-killing the container using
docker kill my-irisThe database handled it well. No problems were identified.
Zaključak
For globals in InterSystems IRIS, there is support for transactions. They are indeed atomic and reliable. However, ensuring database consistency in globals requires the programmer's effort and the use of transactions, as there are no complex built-in constructs like foreign keys.
The isolation level for globals without using locks is READ UNCOMMITTED, while using locks can ensure it up to the SERIALIZE level.
The correctness and speed of transactions on globals greatly depends on the programmer's skill: the more widely shared locks are used for reading, the higher the isolation level, and the narrower the exclusive locks are applied, the greater the speed.
Izvor: habr.com
