Transakcijas globālos InterSystems IRIS

Transakcijas globālos InterSystems IRISInterSystems IRIS DB atbalsta interesantus datu glabāšanas risinājumus — globālos. Patiesībā tas ir daudzlīmeņu atslēgu kopums ar dažādām papildu iespējām, piemēram, darījumiem, ātrām funkcijām datu koku apgaitē, bloķēšanu un savu ObjectScript valodu.

Plašāk par globāliem rakstu ciklā "Globāli — datu glabāšanas zobeni-kļadences":

Koki. 1. daļa
Koki. 2. daļa
Retināti masīvi. 3. daļa

Mani interesēja, kā tiek īstenoti darījumi globālos, kādas ir to īpatnības. Jo tā ir pilnīgi cita datu glabāšanas struktūra nekā visiem ierastās tabulas. Kaut kas daudz zemāka līmeņa.

Kā zināms, no relāciju datu bāzu teorijas laba darījumu īstenošana jāatbilst prasībām ACID:

A — Atomiskums. Visas izmaiņas, kas veiktas darījuma laikā, tiek ierakstītas vai nekas netiek ierakstīts.

S — Saskaņotība. Pēc darījuma pabeigšanas datu bāzes loģiskajam stāvoklim jābūt iekšēji konsekventam. Daļēji šī prasība attiecas uz programmētāju, bet SQL datu bāzēm tā attiecas arī uz ārējām atslēgām.

I — Izolācija. Vienlaikus notiekošiem darījumiem nedrīkst ietekmēt cits citu.

D — Ilgtspēja. Pēc veiksmīgas darījuma pabeigšanas problēmām apakšējās līmeņos (piemēram, barošanas traucējumi) nedrīkst ietekmēt ar darījumu mainītajiem datiem.

Globāli ir norelāciju datu struktūras. Tās tika izstrādātas super ātrai darbībai uz ļoti ierobežota dzelžiem. Apskatīsim darījumu īstenošanu globālos ar oficiālo IRIS docker attēlu.

Lai atbalstītu darījumus IRIS, tiek izmantotas komandas: TSTART, TCOMMIT, TROLLBACK.

1. Atomiskums

Visvieglāk ir pārbaudīt atomiskumu. Pārbaudām datu bāzes konsolē.

Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMIT

Pēc tam izdarām secinājumu:

Write ^a(1), “ ”, ^a(2), “ ”, ^a(3)

Iegūsim:

1 2 3

Viss ir kārtībā. Atomiskums ir saglabāts: visas izmaiņas ir ierakstītas.

Apgrūtināsim uzdevumu, ieviešot kļūdu un skatīsim, kā saglabāsies darījums, daļēji vai pavisam nē.

Vēlreiz pārbaudīsim atomiskumu:

Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3

Pēc tam piespiedīsim apstāties konteineram, palaidīsim un paskatīsimies.

docker kill my-iris

Šī komanda ir praktiski ekvivalenta piespiedu izslēgšanai, jo tā nosūta tūlītēju procesa SIGKILL apturēšanas signālu.

Vai darījums varbūt ir saglabājies daļēji?

WRITE ^a(1), ^a(2), ^a(3)
^
 ^a(1)

— Nē, tas nav saglabājies.

Izmēģināsim atjaunošanas komandu:

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)

Arī nekas nav saglabājies.

2. Saskaņotība

Tā kā globalizācijas datubāzēs atslēgas tiek veidotas arī uz globalizācijām (atgādinu, ka globalizācija ir zemāka līmeņa datu glabāšanas struktūra nekā relāciju tabula), lai izpildītu saskaņotības prasības, atslēgas izmaiņai ir jābūt iekļautai tajā pašā darījumā kā globalizācijas izmaiņām.

Piemēram, mums ir globalizācija ^person, kurā mēs glabājam personas un kā atslēgu izmantojam INN.

^person(1234567, ‘firstname’) = ‘Sergejs’
^person(1234567, ‘lastname’) = ‘Kamenevs’
^person(1234567, ‘phone’) = ‘+74995555555
...

Lai veiktu ātru meklēšanu pēc uzvārda un vārda, mēs izveidojām atslēgu ^index.

^index(‘Kamenevs’, ‘Sergejs’, 1234567) = 1

Lai datubāze būtu saskaņota, mums jāievieto persona šādi:

TSTART
^person(1234567, ‘firstname’) = ‘Sergejs’
^person(1234567, ‘lastname’) = ‘Kamenevs’
^person(1234567, ‘phone’) = ‘+74995555555
^index(‘Kamenevs’, ‘Sergejs’, 1234567) = 1
TCOMMIT

Atbilstoši, izdzēšot, mums arī ir jāizmanto darījums:

TSTART
Kill ^person(1234567)
ZKill ^index(‘Kamenevs’, ‘Sergejs’, 1234567)
TCOMMIT

Citādi sakot, saskaņotības prasību izpilde gultas pilnībā uz programmatūras izstrādātāja pleciem. Bet runājot par globalizācijām, tas ir dabiski, ņemot vērā to zemo līmeņa dabu.

3. Izolētība

Šeit sākas sarežģījumi. Daudzi lietotāji vienlaikus strādā ar to pašu datubāzi, maina tos pašus datus.

Situācija ir salīdzināma ar to, ka daudzi lietotāji vienlaikus strādā pie viena un tā paša koda repozitāra un mēģina vienlaikus tajā veikt izmaiņas daudzos failos.

Datubāzei ir jārisina viss šis reālajā laikā. Ņemot vērā, ka nopietnās kompānijās ir pat speciāla persona, kas atbild par versiju kontroles (par zaru apvienošanu, konfliktu risināšanu utt.), un datubāzei tas viss jārisina reālajā laikā, kļūst acīmredzama uzdevuma sarežģītība un pareizā datubāzes un tās apkopes koda projektēšana.

Datu bāze nevar saprast, kā lietotāji rīkojas, lai novērstu konfliktus, ja viņi strādā ar tiem pašiem datiem. Tā var tikai atcelt vienu darījumu, kas ir pretrunā ar citu, vai veikt tos secīgi.

Vēl viena problēma ir tā, ka darījuma izpildes laikā (pirms apstiprināšanas) datu bāzes stāvoklis var būt nesaskaņots, tāpēc ir vēlams, lai citiem darījumiem nebūtu piekļuves nesaskaņotajam datu bāzes stāvoklim, ko relāciju datu bāzēs var sasniegt daudziem veidiem: snapshotu izveide, vairāku versiju rindiņas utt.

Veicot darījumus paralēli, ir svarīgi, lai tie netraucētu viens otram. Tas ir izolācijas īpašums.

SQL nosaka 4 izolācijas līmeņus:

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

Apskatīsim katru līmeni atsevišķi. Izpildes izmaksas katram līmenim pieaug gandrīz eksponenciāli.

READ UNCOMMITTED — tas ir zemākais izolācijas līmenis, taču tajā pašā laikā arī visātrākais. Darījumi var lasīt izmaiņas, ko veikuši viens otru.

READ COMMITTED — tas ir nākamais izolācijas līmenis, kas ir kompromiss. Darījumi nevar lasīt izmaiņas, ko veikuši viens otru līdz apstiprināšanai, bet var lasīt jebkuras izmaiņas, kas veiktas pēc apstiprināšanas.

Ja mums ir garš darījums T1, kurā laikā notiek apstiprinājumi darījumos T2, T3 ... Tn, kas strādā ar tiem pašiem datiem kā T1, tad, veicot datu pieprasījumus T1, mēs katru reizi saņemsim atšķirīgu rezultātu. Šis fenomens tiek saukts par nepārtraukto lasījumu.

REPEATABLE READ — šajā izolācijas līmenī mums nav nepārtraukta lasījuma fenomena, jo katram datu lasīšanas pieprasījumam tiek izveidots datu rezultātu attēls un, atkārtoti lietojot šajā pašā darījumā, tiek izmantoti dati no attēla. Tomēr šajā izolācijas līmenī ir iespējams lasīt fantomu datus. Tiek domāts lasīt jaunas rindas, kas tika pievienotas paralēli fiksētiem darījumiem.

SERIALIZABLE — visaugstākais izolācijas līmenis. To raksturo tas, ka dati, kas tiek kādā veidā izmantoti darījumā (lasīšana vai mainīšana), kļūst pieejami citiem darījumiem tikai pēc pirmā darījuma pabeigšanas.

Sāksim ar to, vai operāciju izolācija darījumā ir no galvenā plūsmā. Atvērsim 2 termināla logus.

Kill ^t

Write ^t(1)
2

TSTART
Set ^t(1)=2

Izolācijas nav. Viena plūsma redz, ko dara otra, kas atvērusi darījumu.

Paskatīsimies, vai dažādu plūsmu darījumi redz to, kas notiek iekšienē.

Atvērsim 2 termināla logus un uzsāksim 2 darījumus paralēli.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

Paralēlie darījumi redz viens otra datus. Tātad, mēs saņēmām visvienkāršāko, bet arī visātrāko izolācijas līmeni READ UNCOMMITED.

Principā to varēja gaidīt globālajiem mainīgajiem, kuros veiktspēja vienmēr bijusi prioritāte.

Ko darīt, ja mums nepieciešams augstāks izolācijas līmenis darbībām ar globālajiem mainīgajiem?

Šeit jāpadomā, kāpēc vispār ir nepieciešami izolācijas līmeņi un kā tie darbojas.

Augstākais izolācijas līmenis SERIALIZE nozīmē, ka paralēli izpildīto darījumu rezultāts ir ekvivalents to secīgajai izpildei, kas garantē kolīziju trūkumu.

To mēs varam sasniegt ar pareizām bloķēšanām ObjectScript, kurām ir daudz dažādu pielietojumu: var veikt parasto, inkrementālo, vairāku bloķēšanu ar komandu LOCK.

Zemāki izolācijas līmeņi ir kompromisi, kuriem mērķis ir palielināt datu bāzes darbības ātrumu.

Paskatīsimies, kā mēs varam sasniegt dažādus izolācijas līmeņus ar bloķēšanām.

Šis operators ļauj iegūt ne tikai ekskluzīvas bloķēšanas, kas nepieciešamas datu izmaiņām, bet arī tā sauktās koplietošanas bloķēšanas, kuras var veikt paralēli vairāki plūsmu, kad tām vajag lasīt datus, kuriem nevajadzētu tikt mainītiem citu procesu laikā lasīšanas procesā.

Sīkāk par divfāžu bloķēšanu krievu un angļu valodā:

→ Divfāžu bloķēšana
→ Two-phase locking

Grūtība ir tāda, ka darījuma laikā datu bāzes stāvoklis var būt nesakārtots, tomēr šie nesakārtotie dati ir redzami citiem procesiem. Kā to novērst?

Mēs izveidosim ar bloķēšanām tādas redzamības logus, kuros datu bāzes stāvoklis būs sakārtots. Un visi pieprasījumi uz šādiem redzamības logiem ar sakārtoto stāvokli tiks kontrolēti ar bloķēšanām.

Koplietojamās bloķēšanas mērķis ir aizsargāt vienādas datus no daudzkārtējas piekļuves — tās var iegūt vairāki procesi. Šīs bloķēšanas novērš citu procesu iespēju mainīt datus, t.i., tās tiek izmantotas, lai izveidotu saskaņotu datu bāzes stāvokļa logus.

Ekskluzīvās bloķēšanas tiek izmantotas datu izmaiņām — šādu bloķēšanu var iegūt tikai viens process. Ekskluzīvo bloķēšanu var iegūt:

  1. Jebkurš process, ja dati ir brīvi.
  2. Tikai tas process, kuram ir uz šiem datiem shared bloķēšana un kurš pirmais pieprasīja ekskluzīvo bloķēšanu.

Transakcijas globālos InterSystems IRIS

Jo šaurāka ir redzamības loga robeža, jo ilgāk citi procesi to gaida, bet tas ļauj saglabāt saskaņotāku datu bāzes stāvokli tajā.

READ_COMMITED — šī līmeņa būtība ir tāda, ka mēs redzam tikai citos plūsmās apstiprinātos datus. Ja dati citā transakcijā vēl nav apstiprināti, tad mēs redzam to veco versiju.

Tas ļauj mums paralelizēt darbu, nevis gaidīt bloķēšanas atbrīvošanu.

Bez īpašām viltībām mēs nespēsim redzēt veco datu versiju IRIS, tāpēc mums nāksies iztikt ar bloķēšanām.

Attiecīgi mums būs jāatļauj datu lasīšana ar shared bloķēšanām tikai saskaņotības brīžos.

Pieņemsim, ka mums ir lietotāju datu bāze ^person, kuri pārskaita naudu viens otram.

Naudas pārskaitīšana no personas 123 uz personu 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)

Naudas summas pieprasīšana no personas 123 pirms norakstīšanas prasa ekskluzīvo bloķēšanu (pēc noklusējuma):

LOCK +^person(123)
Write ^person(123)

Ja nepieciešams parādīt konta stāvokli personīgajā kabinetā, tad var izmantot shared bloķēšanu vai vispār to neizmantot:

LOCK +^person(123)#”S”
Write ^person(123)

Tomēr, pieņemot, ka darījumi ar datu bāzi notiek praktiski uzreiz (atgādinu, ka globālās datu struktūras ir daudz zemāka līmeņa nekā relāciju tabulas), tad šī līmeņa nepieciešamība samazinās.

REPEATABLE READ — šajā izolācijas līmenī ir atļauts, ka var būt vairākas datu lasīšanas, kuras var mainīt paralēlas transakcijas.

Attiecīgi mums būs jāizmanto shared bloķēšana datu lasīšanai, kuras mēs mainām, un ekskluzīvās bloķēšanas uz datiem, kurus mēs mainām.

Operator LOCK allows you to detail all the necessary locks within a single operator, which can be quite numerous.

LOCK +^person(123, amount)#”S”
reading ^person(123, amount)

other operations (at this time parallel streams 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, but if you do it this way:

LOCK +(^person(123),^person(242))

then they are taken atomically all at once.

SERIALIZE — we will have to set the locks so that ultimately all transactions that have shared data are executed sequentially. For this approach, most locks must be exclusive and taken on the smallest areas of the global for performance.

If we talk about withdrawals from the global ^person, then only the SERIALIZE isolation level is acceptable, as money must be spent strictly sequentially; otherwise, it is possible to spend the same amount multiple times.

4. Longevity

I conducted tests with a hard shutdown of the container by

docker kill my-iris

The database handled it well. No problems were identified.

Secinājums

For globals in InterSystems IRIS, there is support for transactions. They are truly atomic and reliable. To ensure database consistency in globals, programming effort and the use of transactions are necessary, as there are no complex built-in constructs like foreign keys.

The isolation level for globals without the use of locks is READ UNCOMMITTED, while by using locks, it can be ensured up to the SERIALIZE level.

The correctness and speed of transaction operations on globals strongly depend on the programmer’s skill: the more widely shared locks are used during reading, the higher the isolation level, and the more narrowly exclusive locks are taken, the greater the performance.

Avots: habr.com

Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem 🔥 Iegādājieties uzticamu tīmekļa hostingu ar DDoS aizsardzību, VPS VDS serveriem | ProHoster