Transacties in InterSystems IRIS Globals

Transacties in InterSystems IRIS GlobalsHet InterSystems IRIS DBMS ondersteunt interessante structuren voor het opslaan van gegevens - globals. In wezen zijn dit sleutels met meerdere niveaus met verschillende extraatjes in de vorm van transacties, snelle functies voor het doorkruisen van databomen, vergrendelingen en een eigen ObjectScript-taal.

Lees meer over globals in de serie artikelen “Globals are treasure-swords for storage data”:

Bomen. Deel 1
Bomen. Deel 2
Schaarse arrays. Deel 3

Ik raakte geïnteresseerd in hoe transacties in globals worden geïmplementeerd, welke functies er zijn. Dit is immers een heel andere structuur voor het opslaan van gegevens dan de gebruikelijke tabellen. Veel lager niveau.

Zoals bekend uit de theorie van relationele databases moet een goede implementatie van transacties aan de eisen voldoen ACID:

A - Atomair (atomiciteit). Alle wijzigingen die in de transactie worden aangebracht of helemaal geen wijzigingen worden geregistreerd.

C - Consistentie. Nadat een transactie is voltooid, moet de logische status van de database intern consistent zijn. In veel opzichten betreft deze eis de programmeur, maar in het geval van SQL-databases gaat het ook om externe sleutels.

Ik - Isoleer. Parallel lopende transacties mogen elkaar niet beïnvloeden.

D - Duurzaam. Na succesvolle voltooiing van een transactie zouden problemen op lagere niveaus (bijvoorbeeld stroomuitval) geen invloed moeten hebben op de gegevens die door de transactie zijn gewijzigd.

Globals zijn niet-relationele datastructuren. Ze zijn ontworpen om supersnel te werken op zeer beperkte hardware. Laten we eens kijken naar de implementatie van transacties in globals met behulp van officiële IRIS docker-afbeelding.

Om transacties in IRIS te ondersteunen, worden de volgende commando's gebruikt: TSTART, TCOMMIT, TROLLBACK.

1. Atomiciteit

De eenvoudigste manier om dit te controleren is atomiciteit. We controleren vanuit de databaseconsole.

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

Dan concluderen wij:

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

We krijgen:

1 2 3

Alles is in orde. De atomiciteit blijft behouden: alle veranderingen worden vastgelegd.

Laten we de taak ingewikkelder maken, een fout introduceren en kijken hoe de transactie gedeeltelijk of helemaal niet wordt opgeslagen.

Laten we de atomiciteit opnieuw controleren:

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

Dan zullen we de container met geweld tegenhouden, lanceren en kijken.

docker kill my-iris

Dit commando is bijna gelijk aan een geforceerde uitschakeling, omdat het een SIGKILL-signaal verzendt om het proces onmiddellijk te stoppen.

Misschien is de transactie gedeeltelijk opgeslagen?

WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)

- Nee, het heeft het niet overleefd.

Laten we de rollback-opdracht proberen:

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

WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)

Er is ook niets bewaard gebleven.

2. Consistentie:

Omdat in databases die op globale waarden zijn gebaseerd ook sleutels op globale waarden worden gemaakt (laat me u eraan herinneren dat een globale structuur een structuur op een lager niveau is voor het opslaan van gegevens dan een relationele tabel), moet er, om aan de consistentievereiste te voldoen, een wijziging in de sleutel worden opgenomen. in dezelfde transactie als een verandering in de wereld.

We hebben bijvoorbeeld een global ^person, waarin we persoonlijkheden opslaan en het TIN als sleutel gebruiken.

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

Om snel op achternaam en voornaam te kunnen zoeken, hebben we de ^index-sleutel gemaakt.

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

Om ervoor te zorgen dat de database consistent is, moeten we de persona als volgt toevoegen:

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

Dienovereenkomstig moeten we bij het verwijderen ook een transactie gebruiken:

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

Met andere woorden: het voldoen aan de consistentievereiste ligt volledig op de schouders van de programmeur. Maar als het om mondiale economieën gaat, is dit normaal, vanwege hun laagdrempelige karakter.

3. Isolatie

Dit is waar de wildernis begint. Veel gebruikers werken tegelijkertijd aan dezelfde database en wijzigen dezelfde gegevens.

De situatie is vergelijkbaar met wanneer veel gebruikers tegelijkertijd met dezelfde coderepository werken en tegelijkertijd wijzigingen in veel bestanden tegelijk proberen door te voeren.

De database zou het allemaal in realtime moeten uitzoeken. Gezien het feit dat er in serieuze bedrijven zelfs een speciale persoon is die verantwoordelijk is voor versiebeheer (voor het samenvoegen van branches, het oplossen van conflicten, enz.), en de database dit allemaal in realtime moet doen, is de complexiteit van de taak en de juistheid van de databaseontwerp en code die daarbij hoort.

De database kan de betekenis van de acties die door gebruikers worden uitgevoerd niet begrijpen om conflicten te voorkomen als ze met dezelfde gegevens werken. Het kan slechts één transactie ongedaan maken die conflicteert met een andere, of deze opeenvolgend uitvoeren.

Een ander probleem is dat tijdens de uitvoering van een transactie (vóór een commit) de status van de database inconsistent kan zijn. Het is dus wenselijk dat andere transacties geen toegang hebben tot de inconsistente status van de database, wat wordt bereikt in relationele databases. op veel manieren: snapshots maken, rijen met meerdere versies maken, enz.

Bij het parallel uitvoeren van transacties is het voor ons belangrijk dat ze elkaar niet hinderen. Dit is de eigenschap van isolatie.

SQL definieert 4 isolatieniveaus:

  • LEES NIET AANGEVRAAGD
  • LEES TOEGEWIJD
  • HERHAALBAAR LEZEN
  • SERIALISEERBAAR

Laten we elk niveau afzonderlijk bekijken. De kosten voor het implementeren van elk niveau groeien bijna exponentieel.

LEES NIET AANGEVRAAGD - dit is het laagste isolatieniveau, maar tegelijkertijd het snelste. Transacties kunnen door elkaar aangebrachte wijzigingen lezen.

LEES TOEGEWIJD is het volgende niveau van isolatie, wat een compromis is. Transacties kunnen elkaars wijzigingen vóór de commit niet lezen, maar wel alle wijzigingen die na de commit zijn aangebracht.

Als we een lange transactie T1 hebben, waarbij commits plaatsvonden in transacties T2, T3 ... Tn, die met dezelfde gegevens werkten als T1, dan krijgen we bij het opvragen van gegevens in T1 elke keer een ander resultaat. Dit fenomeen wordt niet-herhaalbaar lezen genoemd.

HERHAALBAAR LEZEN — op dit isolatieniveau is er geen sprake van het fenomeen van niet-herhaalbaar lezen, vanwege het feit dat voor elk verzoek om gegevens te lezen een momentopname van de resultaatgegevens wordt gemaakt en bij hergebruik in dezelfde transactie de gegevens uit de momentopname is gebruikt. Op dit isolatieniveau is het echter mogelijk om fantoomgegevens te lezen. Dit verwijst naar het lezen van nieuwe rijen die zijn toegevoegd door parallelle vastgelegde transacties.

SERIALISEERBAAR — het hoogste isolatieniveau. Het wordt gekenmerkt door het feit dat gegevens die op enigerlei wijze bij een transactie worden gebruikt (lezen of wijzigen) pas na voltooiing van de eerste transactie beschikbaar komen voor andere transacties.

Laten we eerst eens kijken of er sprake is van isolatie van bewerkingen in een transactie van de hoofdthread. Laten we 2 terminalvensters openen.

Kill ^t

Write ^t(1)
2

TSTART
Set ^t(1)=2

Er is geen isolatie. Eén thread ziet wat de tweede die de transactie heeft geopend, doet.

Laten we eens kijken of transacties van verschillende threads zien wat er binnenin gebeurt.

Laten we 2 terminalvensters openen en 2 transacties parallel openen.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

Parallelle transacties zien elkaars gegevens. We hebben dus het eenvoudigste, maar ook het snelste isolatieniveau, LEES ONBETROKKEN.

In principe zou dit kunnen worden verwacht voor de mondiale economieën, voor wie prestaties altijd een prioriteit zijn geweest.

Wat als we een hoger niveau van isolatie nodig hebben bij operaties op mondiale schaal?

Hier moet je nadenken over waarom isolatieniveaus überhaupt nodig zijn en hoe ze werken.

Het hoogste isolatieniveau, SERALIZE, betekent dat het resultaat van parallel uitgevoerde transacties gelijkwaardig is aan de sequentiële uitvoering ervan, wat de afwezigheid van botsingen garandeert.

We kunnen dit doen met behulp van slimme vergrendelingen in ObjectScript, die veel verschillende toepassingen hebben: u kunt reguliere, incrementele en meervoudige vergrendelingen uitvoeren met de opdracht LOCK.

Lagere isolatieniveaus zijn compromissen die zijn ontworpen om de databasesnelheid te verhogen.

Laten we eens kijken hoe we verschillende niveaus van isolatie kunnen bereiken met behulp van sloten.

Met deze operator kunt u niet alleen exclusieve vergrendelingen gebruiken die nodig zijn om gegevens te wijzigen, maar ook zogenaamde gedeelde vergrendelingen, die meerdere threads parallel kunnen gebruiken wanneer ze gegevens moeten lezen die tijdens het leesproces niet door andere processen mogen worden gewijzigd.

Meer informatie over de tweefasige blokkeermethode in het Russisch en Engels:

Tweefasige blokkering
Tweefasige vergrendeling

De moeilijkheid is dat tijdens een transactie de status van de database inconsistent kan zijn, maar deze inconsistente gegevens zichtbaar zijn voor andere processen. Hoe kunt u dit vermijden?

Met behulp van vergrendelingen creëren we zichtbaarheidsvensters waarin de status van de database consistent zal zijn. En alle toegang tot dergelijke vensters van zichtbaarheid van de overeengekomen staat zal worden gecontroleerd door sloten.

Gedeelde vergrendelingen op dezelfde gegevens zijn herbruikbaar; verschillende processen kunnen ze gebruiken. Deze vergrendelingen voorkomen dat andere processen gegevens wijzigen, d.w.z. ze worden gebruikt om vensters met een consistente databasestatus te vormen.

Er worden exclusieve vergrendelingen gebruikt voor gegevenswijzigingen; slechts één proces kan zo'n vergrendeling aan. Een exclusief slot kan worden afgenomen door:

  1. Elk proces als de gegevens gratis zijn
  2. Alleen het proces dat een gedeelde vergrendeling op deze gegevens heeft en als eerste om een ​​exclusieve vergrendeling heeft verzocht.

Transacties in InterSystems IRIS Globals

Hoe smaller het zichtbaarheidsvenster, hoe langer andere processen erop moeten wachten, maar hoe consistenter de status van de database daarin kan zijn.

READ_COMMITTED — de essentie van dit niveau is dat we alleen vastgelegde gegevens uit andere threads zien. Als de gegevens in een andere transactie nog niet zijn vastgelegd, zien we de oude versie ervan.

Hierdoor kunnen we de werkzaamheden parallelliseren in plaats van te wachten tot de sluis wordt vrijgegeven.

Zonder speciale trucs kunnen we de oude versie van de gegevens in IRIS niet zien, dus zullen we het moeten doen met vergrendelingen.

Dienovereenkomstig zullen we gedeelde vergrendelingen moeten gebruiken om ervoor te zorgen dat gegevens alleen kunnen worden gelezen op momenten van consistentie.

Laten we zeggen dat we een gebruikersbestand ^persoon hebben die geld naar elkaar overmaken.

Moment van overdracht van persoon 123 naar persoon 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)

Het moment van het opvragen van het geldbedrag bij persoon 123 vóór het afschrijven moet (standaard) gepaard gaan met een exclusieve blokkering:

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

En als u de accountstatus in uw persoonlijke account wilt weergeven, kunt u een gedeeld slot gebruiken of helemaal niet gebruiken:

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

Als we er echter van uitgaan dat databasebewerkingen vrijwel onmiddellijk worden uitgevoerd (laat me u eraan herinneren dat globals een structuur op een veel lager niveau hebben dan een relationele tabel), dan neemt de behoefte aan dit niveau af.

HERHAALBAAR LEZEN - Dit isolatieniveau maakt meerdere lezingen van gegevens mogelijk die kunnen worden gewijzigd door gelijktijdige transacties.

Dienovereenkomstig zullen we een gedeelde vergrendeling moeten instellen op het lezen van de gegevens die we wijzigen, en exclusieve vergrendelingen op de gegevens die we wijzigen.

Gelukkig kunt u met de LOCK-operator alle benodigde sloten, en dat kunnen er veel zijn, gedetailleerd in één overzicht opsommen.

LOCK +^person(123, amount)#”S”
чтение ^person(123, amount)

andere bewerkingen (op dit moment proberen parallelle threads ^person(123, amount) te wijzigen, maar dat lukt niet)

LOCK +^person(123, amount)
изменение ^person(123, amount)
LOCK -^person(123, amount)

чтение ^person(123, amount)
LOCK -^person(123, amount)#”S”

Wanneer u sloten opsomt, gescheiden door komma's, worden ze opeenvolgend genomen, maar als u dit doet:

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

vervolgens worden ze in één keer atomair genomen.

SERIALISEREN – we zullen vergrendelingen moeten instellen zodat uiteindelijk alle transacties met gemeenschappelijke gegevens opeenvolgend worden uitgevoerd. Voor deze aanpak moeten de meeste sloten exclusief zijn en voor prestaties in de kleinste delen van de wereld worden gebruikt.

Als we het hebben over het afschrijven van geld in de mondiale ^persoon, dan is alleen het SRIALIZE-isolatieniveau daarvoor acceptabel, omdat geld strikt opeenvolgend moet worden uitgegeven, anders is het mogelijk om hetzelfde bedrag meerdere keren uit te geven.

4. Duurzaamheid

Ik heb tests uitgevoerd met het hard snijden van de container met behulp van

docker kill my-iris

De basis verdroeg ze goed. Er zijn geen problemen vastgesteld.

Conclusie

Voor globals biedt InterSystems IRIS transactieondersteuning. Ze zijn echt atomair en betrouwbaar. Om de consistentie van een op globale gegevens gebaseerde database te garanderen, zijn inspanningen van programmeurs en het gebruik van transacties vereist, aangezien deze geen complexe ingebouwde constructies heeft, zoals externe sleutels.

Het isolatieniveau van globals zonder het gebruik van vergrendelingen is READ UNCOMMITED, en bij het gebruik van vergrendelingen kan dit worden gegarandeerd tot aan het SERALIZE-niveau.

De juistheid en snelheid van transacties op globals hangt sterk af van de vaardigheid van de programmeur: hoe breder gedeelde vergrendelingen worden gebruikt bij het lezen, hoe hoger het isolatieniveau, en hoe nauwer exclusieve vergrendelingen worden gebruikt, hoe sneller de prestaties.

Bron: www.habr.com

Voeg een reactie