Transactions dans les globaux InterSystems IRIS

Transactions dans les globaux InterSystems IRISLe SGBD InterSystems IRIS prend en charge des structures intéressantes pour stocker des données - globales. Il s'agit essentiellement de clés à plusieurs niveaux avec divers avantages supplémentaires sous forme de transactions, de fonctions rapides pour parcourir les arborescences de données, de verrous et de son propre langage ObjectScript.

Apprenez-en davantage sur les globals dans la sĂ©rie d’articles « Les globals sont des Ă©pĂ©es au trĂ©sor pour stocker des donnĂ©es » :

Des arbres. Partie 1
Des arbres. Partie 2
Tableaux clairsemés. Partie 3

Je me suis intĂ©ressĂ© Ă  la maniĂšre dont les transactions sont implĂ©mentĂ©es dans les globaux et Ă  leurs fonctionnalitĂ©s. AprĂšs tout, il s’agit d’une structure de stockage de donnĂ©es complĂštement diffĂ©rente de celle des tables habituelles. Niveau bien infĂ©rieur.

Comme le montre la théorie des bases de données relationnelles, une bonne implémentation des transactions doit satisfaire aux exigences ACID:

A - Atomique (atomicité). Toutes les modifications apportées à la transaction, voire aucune, sont enregistrées.

C - CohĂ©rence. Une fois la transaction terminĂ©e, l'Ă©tat logique de la base de donnĂ©es doit ĂȘtre cohĂ©rent en interne. À bien des Ă©gards, cette exigence concerne le programmeur, mais dans le cas des bases de donnĂ©es SQL, elle concerne Ă©galement les clĂ©s Ă©trangĂšres.

I - Isoler. Les transactions exĂ©cutĂ©es en parallĂšle ne doivent pas s’influencer mutuellement.

D-Durable. AprÚs la réussite d'une transaction, les problÚmes aux niveaux inférieurs (panne de courant, par exemple) ne devraient pas affecter les données modifiées par la transaction.

Les globaux sont des structures de données non relationnelles. Ils ont été conçus pour fonctionner trÚs rapidement sur un matériel trÚs limité. Regardons l'implémentation des transactions dans les globales en utilisant image officielle du menu fixe IRIS.

Pour prendre en charge les transactions dans IRIS, les commandes suivantes sont utilisĂ©es : TSTART, TCOMMIT, TROLLBACK.

1. Atomicité

Le moyen le plus simple de vĂ©rifier est l’atomicitĂ©. Nous vĂ©rifions depuis la console de la base de donnĂ©es.

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

Nous concluons alors :

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

Nous obtenons:

1 2 3

Tout va bien. L'atomicité est maintenue : tous les changements sont enregistrés.

Compliquons la tùche, introduisons une erreur et voyons comment la transaction est enregistrée, partiellement ou pas du tout.

VĂ©rifions Ă  nouveau l'atomicitĂ© :

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

Ensuite, nous arrĂȘterons de force le conteneur, le lancerons et verrons.

docker kill my-iris

Cette commande Ă©quivaut presque Ă  un arrĂȘt forcĂ©, car elle envoie un signal SIGKILL pour arrĂȘter immĂ©diatement le processus.

Peut-ĂȘtre que la transaction a Ă©tĂ© partiellement enregistrĂ©e ?

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

- Non, il n'a pas survécu.

Essayons la commande rollback :

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)

Rien n’a survĂ©cu non plus.

2. Cohérence

Puisque dans les bases de donnĂ©es basĂ©es sur des globales, les clĂ©s sont Ă©galement créées sur des globales (rappelons qu'une globale est une structure de niveau infĂ©rieur pour stocker des donnĂ©es qu'une table relationnelle), pour rĂ©pondre Ă  l'exigence de cohĂ©rence, une modification de la clĂ© doit ĂȘtre incluse dans la mĂȘme transaction qu'un changement dans le global.

Par exemple, nous avons un ^person global, dans lequel nous stockons des personnalités et nous utilisons le TIN comme clé.

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

Afin d'avoir une recherche rapide par nom et prénom, nous avons créé la clé ^index.

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

Pour que la base de données soit cohérente, il faut ajouter le persona comme ceci :

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

Par consĂ©quent, lors de la suppression, nous devons Ă©galement utiliser une transaction :

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

En d’autres termes, le respect de l’exigence de cohĂ©rence repose entiĂšrement sur les Ă©paules du programmeur. Mais lorsqu’il s’agit de variables globales, cela est normal, en raison de leur nature de bas niveau.

3. Isolement

C'est lĂ  que commencent les Ă©tendues sauvages. De nombreux utilisateurs travaillent simultanĂ©ment sur la mĂȘme base de donnĂ©es et modifient les mĂȘmes donnĂ©es.

La situation est comparable Ă  celle oĂč de nombreux utilisateurs travaillent simultanĂ©ment avec le mĂȘme rĂ©fĂ©rentiel de code et tentent de valider simultanĂ©ment les modifications apportĂ©es Ă  plusieurs fichiers Ă  la fois.

La base de donnĂ©es devrait tout trier en temps rĂ©el. ConsidĂ©rant que dans les entreprises sĂ©rieuses, il y a mĂȘme une personne spĂ©ciale qui est responsable du contrĂŽle des versions (pour la fusion des branches, la rĂ©solution des conflits, etc.), et que la base de donnĂ©es doit faire tout cela en temps rĂ©el, la complexitĂ© de la tĂąche et l'exactitude du conception de la base de donnĂ©es et code qui la sert.

La base de donnĂ©es ne peut pas comprendre le sens des actions effectuĂ©es par les utilisateurs afin d'Ă©viter les conflits s'ils travaillent sur les mĂȘmes donnĂ©es. Il ne peut annuler qu'une transaction en conflit avec une autre ou les exĂ©cuter sĂ©quentiellement.

Un autre problĂšme est que lors de l'exĂ©cution d'une transaction (avant une validation), l'Ă©tat de la base de donnĂ©es peut ĂȘtre incohĂ©rent, il est donc souhaitable que les autres transactions n'aient pas accĂšs Ă  l'Ă©tat incohĂ©rent de la base de donnĂ©es, ce qui est obtenu dans les bases de donnĂ©es relationnelles. de plusieurs maniĂšres : crĂ©ation d'instantanĂ©s, lignes multi-versions, etc.

Lors de l'exécution de transactions en parallÚle, il est important pour nous qu'elles n'interfÚrent pas les unes avec les autres. C'est la propriété de l'isolement.

SQL dĂ©finit 4 niveaux d'isolement :

  • LIRE NON ENGAGÉ
  • LIRE ENGAGÉ
  • LECTURE RÉPÉTABLE
  • SÉRIALISABLE

Examinons chaque niveau sĂ©parĂ©ment. Les coĂ»ts de mise en Ɠuvre de chaque niveau augmentent de maniĂšre presque exponentielle.

LIRE NON ENGAGÉ - c'est le niveau d'isolement le plus bas, mais en mĂȘme temps le plus rapide. Les transactions peuvent lire les modifications apportĂ©es les unes par les autres.

LIRE ENGAGÉ est le prochain niveau d’isolement, qui est un compromis. Les transactions ne peuvent pas lire les modifications des autres avant la validation, mais elles peuvent lire toutes les modifications apportĂ©es aprĂšs la validation.

Si nous avons une longue transaction T1, au cours de laquelle des validations ont eu lieu dans les transactions T2, T3 ... Tn, qui travaillaient avec les mĂȘmes donnĂ©es que T1, alors lors de la demande de donnĂ©es dans T1, nous obtiendrons un rĂ©sultat diffĂ©rent Ă  chaque fois. Ce phĂ©nomĂšne est appelĂ© lecture non rĂ©pĂ©table.

LECTURE RÉPÉTABLE — dans ce niveau d'isolement, nous n'avons pas de phĂ©nomĂšne de lecture non rĂ©pĂ©table, du fait que pour chaque demande de lecture de donnĂ©es, un instantanĂ© des donnĂ©es de rĂ©sultat est créé et lorsqu'il est rĂ©utilisĂ© dans la mĂȘme transaction, les donnĂ©es de l'instantanĂ© est utilisĂ©. Cependant, il est possible de lire des donnĂ©es fantĂŽmes Ă  ce niveau d'isolement. Cela fait rĂ©fĂ©rence Ă  la lecture de nouvelles lignes ajoutĂ©es par des transactions validĂ©es parallĂšles.

SÉRIALISABLE — le plus haut niveau d'isolation. Elle se caractĂ©rise par le fait que les donnĂ©es utilisĂ©es de quelque maniĂšre que ce soit dans une transaction (lecture ou modification) ne deviennent disponibles pour d'autres transactions qu'aprĂšs la rĂ©alisation de la premiĂšre transaction.

Tout d’abord, voyons s’il existe une isolation des opĂ©rations dans une transaction par rapport au thread principal. Ouvrons 2 fenĂȘtres de terminal.

Kill ^t

Write ^t(1)
2

TSTART
Set ^t(1)=2

Il n'y a pas d'isolement. Un fil voit ce que fait le deuxiĂšme qui a ouvert la transaction.

Voyons si les transactions de différents threads voient ce qui se passe à l'intérieur d'eux.

Ouvrons 2 fenĂȘtres de terminal et ouvrons 2 transactions en parallĂšle.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

Les transactions parallÚles voient les données des autres. Nous avons donc obtenu le niveau d'isolement le plus simple, mais aussi le plus rapide, READ UNCOMMITED.

En principe, on pourrait s'attendre à cela pour les mondiaux, pour lesquels la performance a toujours été une priorité.

Et si nous avions besoin d’un niveau plus Ă©levĂ© d’isolement dans les opĂ©rations sur les marchĂ©s mondiaux ?

Ici, vous devez rĂ©flĂ©chir aux raisons pour lesquelles des niveaux d’isolement sont nĂ©cessaires et Ă  la maniĂšre dont ils fonctionnent.

Le niveau d'isolement le plus élevé, SERIALIZE, signifie que le résultat des transactions exécutées en parallÚle est équivalent à leur exécution séquentielle, ce qui garantit l'absence de collisions.

Nous pouvons le faire en utilisant les verrous intelligents dans ObjectScript, qui ont de nombreuses utilisations diffĂ©rentes : vous pouvez effectuer un verrouillage multiple rĂ©gulier, incrĂ©mentiel avec la commande VERROUILLAGE.

Des niveaux d'isolement inférieurs sont des compromis conçus pour augmenter la vitesse de la base de données.

Voyons comment nous pouvons atteindre différents niveaux d'isolement à l'aide de verrous.

Cet opĂ©rateur vous permet de prendre non seulement les verrous exclusifs nĂ©cessaires Ă  la modification des donnĂ©es, mais Ă©galement les verrous dits partagĂ©s, qui peuvent prendre plusieurs threads en parallĂšle lorsqu'ils doivent lire des donnĂ©es qui ne doivent pas ĂȘtre modifiĂ©es par d'autres processus pendant le processus de lecture.

Plus d'informations sur la méthode de blocage en deux phases en russe et en anglais :

→ Blocage biphasĂ©
→ Verrouillage biphasĂ©

La difficultĂ© est que lors d'une transaction, l'Ă©tat de la base de donnĂ©es peut ĂȘtre incohĂ©rent, mais ces donnĂ©es incohĂ©rentes sont visibles par les autres processus. Comment Ă©viter cela ?

A l'aide de verrous, nous allons crĂ©er des fenĂȘtres de visibilitĂ© dans lesquelles l'Ă©tat de la base de donnĂ©es sera cohĂ©rent. Et tous les accĂšs Ă  ces fenĂȘtres de visibilitĂ© de l'État convenu seront contrĂŽlĂ©s par des serrures.

Les verrous partagĂ©s sur les mĂȘmes donnĂ©es sont rĂ©utilisables : plusieurs processus peuvent les prendre. Ces verrous empĂȘchent d'autres processus de modifier les donnĂ©es, c'est-Ă -dire ils sont utilisĂ©s pour former des fenĂȘtres d'Ă©tat de base de donnĂ©es cohĂ©rent.

Des verrous exclusifs sont utilisĂ©s pour les modifications de donnĂ©es - un seul processus peut prendre un tel verrou. Un cadenas exclusif peut ĂȘtre pris par :

  1. Tout processus si les données sont gratuites
  2. Seul le processus qui dispose d'un verrou partagé sur ces données et qui a été le premier à demander un verrou exclusif.

Transactions dans les globaux InterSystems IRIS

Plus la fenĂȘtre de visibilitĂ© est Ă©troite, plus les autres processus doivent attendre longtemps, mais plus l'Ă©tat de la base de donnĂ©es qu'elle contient peut ĂȘtre cohĂ©rent.

READ_COMMITTED — l'essence de ce niveau est que nous ne voyons que les donnĂ©es validĂ©es provenant d'autres threads. Si les donnĂ©es d'une autre transaction n'ont pas encore Ă©tĂ© validĂ©es, nous voyons alors son ancienne version.

Cela nous permet de paralléliser le travail au lieu d'attendre que le verrou soit libéré.

Sans astuces particuliÚres, nous ne pourrons pas voir l'ancienne version des données dans IRIS, nous devrons donc nous contenter de verrous.

En conséquence, nous devrons utiliser des verrous partagés pour permettre la lecture des données uniquement à des moments de cohérence.

Disons que nous avons une base d'utilisateurs ^personnes qui se transfĂšrent de l'argent.

Moment du transfert de la personne 123 Ă  la personne 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)

Le moment de demander le montant d'argent Ă  la personne 123 avant le dĂ©bit doit ĂȘtre accompagnĂ© d'un blocage exclusif (par dĂ©faut) :

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

Et si vous devez afficher l'état du compte dans votre compte personnel, vous pouvez alors utiliser un verrou partagé ou ne pas l'utiliser du tout :

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

Cependant, si nous supposons que les opérations sur la base de données sont effectuées presque instantanément (permettez-moi de vous rappeler que les globales sont une structure de niveau bien inférieur à celle d'une table relationnelle), alors la nécessité de ce niveau diminue.

LECTURE RÉPÉTABLE - Ce niveau d'isolement permet plusieurs lectures de donnĂ©es qui peuvent ĂȘtre modifiĂ©es par des transactions simultanĂ©es.

En conséquence, nous devrons mettre un verrou partagé sur la lecture des données que nous modifions et des verrous exclusifs sur les données que nous modifions.

Heureusement, l'opĂ©rateur LOCK vous permet de lister en dĂ©tail tous les verrous nĂ©cessaires, qui peuvent ĂȘtre nombreux, dans une seule instruction.

LOCK +^person(123, amount)#”S”
Ń‡Ń‚Đ”ĐœĐžĐ” ^person(123, amount)

d'autres opérations (à ce moment, les threads parallÚles essaient de changer ^person(123, montant), mais ne le peuvent pas)

LOCK +^person(123, amount)
ĐžĐ·ĐŒĐ”ĐœĐ”ĐœĐžĐ” ^person(123, amount)
LOCK -^person(123, amount)

Ń‡Ń‚Đ”ĐœĐžĐ” ^person(123, amount)
LOCK -^person(123, amount)#”S”

Lorsque vous rĂ©pertoriez les verrous sĂ©parĂ©s par des virgules, ils sont pris sĂ©quentiellement, mais si vous faites ceci :

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

puis ils sont pris atomiquement en une seule fois.

SÉRIALISER — nous devrons dĂ©finir des verrous pour qu'Ă  terme, toutes les transactions ayant des donnĂ©es communes soient exĂ©cutĂ©es sĂ©quentiellement. Pour cette approche, la plupart des verrous doivent ĂȘtre exclusifs et appliquĂ©s aux plus petites zones du monde pour des raisons de performance.

Si nous parlons de dĂ©biter des fonds chez la personne globale, alors seul le niveau d'isolement SERIALIZE est acceptable, car l'argent doit ĂȘtre dĂ©pensĂ© de maniĂšre strictement sĂ©quentielle, sinon il est possible de dĂ©penser le mĂȘme montant plusieurs fois.

4. Durabilité

J'ai effectué des tests avec découpe dure du récipient en utilisant

docker kill my-iris

La base les a bien tolérés. Aucun problÚme n'a été identifié.

Conclusion

Pour les globaux, InterSystems IRIS prend en charge les transactions. Ils sont vraiment atomiques et fiables. Pour assurer la cohérence d'une base de données basée sur des valeurs globales, des efforts de programmation et l'utilisation de transactions sont nécessaires, car elle n'a pas de constructions intégrées complexes telles que des clés étrangÚres.

Le niveau d'isolement des globals sans utiliser de verrous est READ UNCOMMITED, et lors de l'utilisation de verrous, il peut ĂȘtre assurĂ© jusqu'au niveau SERIALIZE.

L'exactitude et la rapidité des transactions sur les transactions globales dépendent beaucoup des compétences du programmeur : plus les verrous partagés sont utilisés lors de la lecture, plus le niveau d'isolement est élevé, et plus les verrous étroitement exclusifs sont pris, plus les performances sont rapides.

Source: habr.com

Ajouter un commentaire