Transaktioner i globale InterSystems IRIS

Transaktioner i globale InterSystems IRISDatabasen InterSystems IRIS understĂžtter nysgerrige strukturer til datalagring — globale. I bund og grund er det flerniveausnĂžgler med forskellige ekstra funktioner som transaktioner, hurtige funktioner til traversal af datatrĂŠer, lĂ„sninger og sit eget sprog ObjectScript.

Lés mere om globale i rékken af artikler "Globale — svérd-skattekister til datalagring":

TrĂŠer. Del 1
TrĂŠer. Del 2
Sparsomme arrays. Del 3

Jeg blev nysgerrig pÄ, hvordan transaktioner er implementeret i globale, og hvilke specielle egenskaber de har. For det er en helt anden struktur til datalagring end de velkendte tabeller. Meget mere lavniveau.

Som det er kendt fra teorien om relationelle databaser, skal en god implementering af transaktioner opfylde kravene ACID:

A — Atomaritet. Alle éndringer foretaget i transaktionen skal enten registreres eller ingenting.

C — Konsistens. Efter at transaktionen er afsluttet, skal den logiske tilstand af databasen vĂŠre internt konsistent. I hĂžj grad vedrĂžrer dette programmĂžren, men i tilfĂŠlde af SQL-databaser gĂŠlder det ogsĂ„ for fremmede nĂžgler.

I — Isolation. Parallelt udfĂžrte transaktioner mĂ„ ikke pĂ„virke hinanden.

D — Holdbarhed. Efter en succesfuld afslutning af transaktionen mĂ„ problemer pĂ„ de lavere niveauer (som strĂžmsvigt) ikke pĂ„virke de data, der er ĂŠndret af transaktionen.

Globale er ikke-relationelle datastrukturer. De blev skabt til ekstremt hurtig drift pÄ meget begrÊnset hardware. Lad os undersÞge implementeringen af transaktioner i globale ved hjÊlp af den officielle IRIS Docker-image.

For at understĂžtte transaktioner i IRIS anvendes kommandoerne: TSTART, TCOMMIT, TROLLBACK.

1. Atomaritet

Det er lettest at teste atomaritet. Lad os tjekke fra databasekonsollen.

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

Derefter laver vi udskriften:

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

Vi fÄr:

1 2 3

Alt er i orden. Atomariteten er overholdt: alle ĂŠndringer blev registreret.

Lad os komplicere opgaven, introducere en fejl og se, hvordan transaktionen bevares, delvist eller slet ikke.

Lad os kontrollere atomariteten igen:

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

Herefter stopper vi containeren tvungent, genstarter den og ser.

docker kill my-iris

Denne kommando er praktisk talt ĂŠkvivalent med at tvinge strĂžmmen fra, da den sender et signal om Ăžjeblikkelig stop af processen SIGKILL.

Kan det vĂŠre, at transaktionen er blevet gemt delvist?

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

— Nej, den blev ikke gemt.

Lad os prĂžve rollback-kommandoen:

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)

Der blev heller ikke gemt noget.

2. Konsistens

Da nÞgler i globale databaser ogsÄ er lavet pÄ globale niveauer (jeg minder om, at en global er en lavere niveau struktur til lagring af data end en relations tabel), skal Êndringen af nÞglen inkluderes i den samme transaktion som Êndringen af den globale for at opfylde konsistenskravene.

For eksempel har vi en global ^person, hvor vi opbevarer personoplysninger, og vi bruger CVR nummeret som nĂžgle.

^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
...

For at have hurtig sÞgning pÄ efternavn og fornavn har vi lavet nÞglen ^index.

^index(‘Kamenev’, ‘Sergey’, 1234567) = 1

For at databasen skal vÊre konsistent, skal vi tilfÞje personligheden sÄdan:

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

Tilsvarende skal vi ogsÄ bruge en transaktion ved sletning:

TSTART
Kill ^person(1234567)
ZKill ^index(‘Kamenev’, ‘Sergey’, 1234567)
TCOMMIT

Med andre ord ligger opfyldelsen af konsistenskravene helt pÄ programmÞrens skuldre. Men nÄr det drejer sig om globale, er det normalt i kraft af deres lavniveau karaktÊr.

3. Isolering

Her begynder kompleksiteten. Mange brugere arbejder samtidig med den samme database og ĂŠndrer de samme data.

Situationen kan sammenlignes med, nÄr mange brugere samtidig arbejder med det samme kode-repository og forsÞger at commite Êndringer til mange filer samtidigt.

Databasen skal hÄndtere alt dette i realtid. I betragtning af at stÞrre virksomheder endda har en person der er ansvarlig for versionskontrol (for sammenlÊgning af branches, lÞsning af konflikter osv.), og at databasen skal gÞre alt dette i realtid, bliver opgavens kompleksitet og vigtigheden af korrekt design af databasen og den kode, der understÞtter den, Äbenlys.

Databasen kan ikke forstÄ meningen med de handlinger, som brugerne udfÞrer, for at undgÄ konflikter, hvis de arbejder med de samme data. Den kan kun tilbagekalde én transaktion, der modsiger en anden, eller udfÞre dem i rÊkkefÞlge.

Et andet problem er, at under udfĂžrelsen af en transaktion (fĂžr commit), kan database状态 vĂŠre inkonsistent, sĂ„ det er Ăžnskeligt, at andre transaktioner ikke har adgang til denne inkonsistente tilstand, hvilket opnĂ„s i relationelle databaser pĂ„ mange mĂ„der: gennem snapshot-dannelse, flere versioner af rĂŠkker osv.

NÄr transaktioner udfÞres parallelt, er det vigtigt, at de ikke forstyrrer hinanden. Det er essensen af isolationsprincippet.

SQL definerer 4 niveauer af isolering:

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

Lad os se pÄ hvert niveau enkeltvis. Omkostningerne ved implementeringen af hvert niveau stiger nÊsten eksponentielt.

READ UNCOMMITTED — det er det laveste isolationsniveau, men det er samtidig det hurtigste. Transaktioner kan lése éndringer foretaget af hinanden.

READ COMMITTED — det er det néste isolationsniveau, som er et kompromis. Transaktioner kan ikke lése éndringer foretaget af hinanden, fþr de er blevet committet, men kan lése alle éndringer foretaget efter commit.

Hvis vi har en lang transaktion T1, hvor der er blevet committet i transaktioner T2, T3 
 Tn, som arbejdede med de samme data som T1, sÄ vil vi hver gang fÄ forskellige resultater, nÄr vi anmoder om data i T1. Dette fÊnomen kaldes ikke-gentagelig lÊsning.

REPEATABLE READ — pĂ„ dette isolationsniveau har vi ikke fĂŠnomenet med ikke-gentagelig lĂŠsning, fordi der for hver lĂŠseforespĂžrgsel oprettes et snapshot af databesvarelsen, og ved gentagen brug i denne samme transaktion anvendes data fra snapshot. Dog er det muligt at lĂŠse phantomdata pĂ„ dette isolationsniveau. Dette refererer til at lĂŠse nye rĂŠkker, der er tilfĂžjet gennem parallelt bekrĂŠftede transaktioner.

SERIALIZABLE — det hĂžjeste isolationsniveau. Det er kendetegnet ved, at data, der pĂ„ en eller anden mĂ„de anvendes i transaktionen (lĂŠses eller ĂŠndres), fĂžrst bliver tilgĂŠngelige for andre transaktioner, efter den fĂžrste transaktion er afsluttet.

Lad os fÞrst finde ud af, om der er isolation af operationer i transaktionen fra hovedtrÄden. Vi Äbner 2 terminalvinduer.

Kill ^t

Write ^t(1)
2

TSTART
Set ^t(1)=2

Der er ingen isolation. En trÄd kan se, hvad den anden, der har Äbnet transaktionen, laver.

Lad os se, om transaktioner fra forskellige trÄde ser, hvad der sker inden i dem.

Vi Äbner 2 terminalvinduer og Äbner 2 transaktioner parallelt.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

Parallele transaktioner ser data om hinanden. SÄ vi har fÄet det enkleste, men hurtigste niveau af isolation, kaldet READ UNCOMMITTED.

Det var i princippet at forvente for globale variabler, hvor ydeevne altid er i fokus.

Hvad skal vi sÄ gÞre, hvis vi har brug for et hÞjere niveau af isolation i operationer pÄ globale variabler?

Her skal vi overveje, hvorfor vi overhovedet har brug for isolationsniveauer, og hvordan de fungerer.

Det hĂžjeste isolationsniveau SERIALIZE betyder, at resultatet af parallelt udfĂžrte transaktioner svarer til deres sekventielle udfĂžrelse, hvilket garanterer, at der ikke er nogle kollisioner.

Dette kan vi gÞre ved hjÊlp af intelligente lÄse i ObjectScript, som har mange forskellige anvendelsesmetoder: vi kan lave almindelig, inkrementel, multipel lÄsning med kommandoen LOCK.

Lavere isolationsniveauer er kompromiser, der sigter mod at Ăžge databasehastigheden.

Lad os se, hvordan vi kan opnÄ forskellige isolationsniveauer ved hjÊlp af lÄse.

Denne operator tillader ikke kun eksklusive lÄse, som er nÞdvendige for at Êndre data, men ogsÄ sÄkaldte delte, som kan tages af flere trÄde parallelt, nÄr de har brug for at lÊse data, der ikke bÞr Êndres af andre processer under lÊseprocessen.

Mere om to-fase lÄsemetoden pÄ russisk og engelsk:

→ To-fase lĂ„sning
→ Two-phase locking

Problemet er, at databasen kan vÊre i en inkonsistent tilstand under transaktionen, men disse inkonsistente data er synlige for andre processer. Hvordan undgÄr vi det?

Vi vil lave synlighedsvinduer ved hjÊlp af lÄse, hvor databasens tilstand vil vÊre konsistent. Og alle henvendelser til sÄdanne synlighedsvinduer med en konsistent tilstand vil blive kontrolleret af lÄse.

Shared-lĂ„se for de samme data er genanvendelige — flere processer kan tage dem. Disse lĂ„se forhindrer andre processer i at ĂŠndre data, dvs. de bruges til at danne vinduer af konsistent tilstand i databasen.

Eksklusive lĂ„se bruges til at ĂŠndre data — en sĂ„dan lĂ„s kan kun tages af Ă©n proces. En eksklusiv lĂ„s kan tages af:

  1. Enhver proces, hvis data er frie
  2. Kun den proces, der har en shared-lÄs pÄ disse data og fÞrst anmodede om en eksklusiv lÄs.

Transaktioner i globale InterSystems IRIS

Jo smallere synsvinduet er, desto lĂŠngere tid skal andre processer vente, men desto mere konsistent kan tilstanden i databasen vĂŠre i det.

READ_COMMITTED — essensen af dette niveau er, at vi kun ser bekréftede data fra andre strþmme. Hvis data i en anden transaktion endnu ikke er bekréftet, ser vi deres gamle version.

Dette giver os mulighed for at parallelisere arbejdet i stedet for at vente pÄ, at lÄsen bliver frigivet.

Uden specielle kneb vil vi ikke kunne se den gamle version af dataene i IRIS, sÄ vi mÄ nÞjes med lÄse.

Dermed skal vi ved hjÊlp af shared-lÄse tillade lÊsning af data kun pÄ tidspunkter med konsistens.

Antag, at vi har en brugerdatabase ^person, der overfĂžrer penge til hinanden.

OverfĂžrselsĂžjeblikket fra person 123 til person 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)

OverfÞrselsmomentet, hvor man anmoder om antallet af penge hos person 123 fÞr debitering, skal ledsages af en eksklusiv lÄs (som standard):

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

Og hvis man skal vise saldoen i kontrolpanelet, kan man bruge en shared-lÄs eller slet ikke bruge en:

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

Men hvis man antager, at operationerne med databasen udfÞres nÊsten Þjeblikkeligt (jeg minder om, at globale er en meget lavere strukturelt niveau end en relationel tabel), sÄ falder behovet for dette niveau.

REPEATABLE READ — pĂ„ dette isolationsniveau tillades det, at der kan vĂŠre flere lĂŠsninger af data, der kan ĂŠndres af parallelle transaktioner.

Dermed skal vi sÊtte shared-lÄse pÄ de data, vi Êndrer, og eksklusive lÄse pÄ de data, vi Êndrer.

LOCK-operatoren gÞr det muligt at angive alle nÞdvendige blokeringer detaljeret i én operator, hvilket kan blive ret omfattende.

LOCK +^person(123, amount)#”S”
lĂŠsning ^person(123, amount)

andre operationer (i denne periode forsÞger parallelle trÄde at Êndre ^person(123, amount), men kan ikke)

LOCK +^person(123, amount)
ĂŠndring ^person(123, amount)
LOCK -^person(123, amount)

lĂŠsning ^person(123, amount)
LOCK -^person(123, amount)#”S”

NÄr blokeringerne angives med komma, tages de sekventielt, og hvis det gÞres sÄdan:

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

tages de atomÊrt alle pÄ én gang.

SERIALIZE — vi skal sikre, at blokeringerne sĂŠttes, sĂ„ i sidste ende udfĂžres alle transaktioner, der har fĂŠlles data sekventielt. Til denne tilgang skal de fleste blokeringer vĂŠre eksklusive og tages pĂ„ de mindste omrĂ„der af globalen for ydeevne.

Hvis vi taler om udtrĂŠk af midler i globalen ^person, er kun isolationniveauet SERIALIZE acceptabelt, da penge skal bruges strengt sekventielt, ellers er det muligt at bruge det samme belĂžb flere gange.

4. Varighed

Jeg har udfÞrt tests med hÄrd nedlukning af containeren ved hjÊlp af

docker kill my-iris

Databasen klarede det godt. Der blev ikke identificeret problemer.

Konklusion

For globaler i InterSystems IRIS er der stÞtte til transaktioner. De er virkelig atomare, pÄlidelige. For at sikre databasens konsistens pÄ globaler krÊves det, at programmÞren gÞr en indsats og bruger transaktioner, da der ikke er komplekse indbyggede konstruktioner som fremmede nÞgler.

Isolationniveauet for globaler uden brug af blokeringer er READ UNCOMMITED, og ved brug af blokeringer kan det opretholdes helt op til niveauet SERIALIZE.

Korrektheden og hastigheden af transaktioner pÄ globaler afhÊnger i hÞj grad af programmÞrens dygtighed: jo mere bredt der anvendes shared-blokeringer ved lÊsning, jo hÞjere er isolationniveauet, og jo strammere eksklusive blokeringer tages, jo bedre bliver ydeevnen.

Kilde: habr.com

KĂžb pĂ„lidelig webhosting med DDoS-beskyttelse, VPS VDS-servere đŸ”„ KĂžb pĂ„lidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster