InterSystems IRIS Global Transactions

InterSystems IRIS et transaction InterSystems IRIS DBMS prend en charge les curieuses structures de stockage de données - globales. En fait, ce sont des clés à plusieurs niveaux avec divers avantages supplémentaires sous la forme de transactions, des fonctions rapides pour parcourir les arbres de données, des verrous et leur propre langage ObjectScript.

Plus d'informations sur les globaux dans la série d'articles «Globals - Swords-Masons for Data Storage»:

Les arbres. Partie 1
Les arbres. 2e partie
Tableaux clairsemés. 3e partie

Il est devenu intĂ©ressant pour moi de savoir comment les transactions sont mises en Ɠuvre dans le monde, quelles sont les fonctionnalitĂ©s. AprĂšs tout, il s'agit d'une structure de stockage des donnĂ©es complĂštement diffĂ©rente de celle des tables habituelles. Niveau beaucoup plus bas.

Comme vous le savez d'aprÚs la théorie des bases de données relationnelles, une bonne implémentation de transaction doit satisfaire aux exigences ACID :

A - Atomique (atomicité). Toutes les modifications apportées à la transaction ou aucune du tout 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 s'applique au programmeur, mais dans le cas des bases de donnĂ©es SQL, elle s'applique Ă©galement aux clĂ©s Ă©trangĂšres.

I - Isoler (isolement). Les transactions parallùles ne devraient pas s’affecter.

D - Durable. Une fois la transaction terminée avec succÚs, 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é créés pour un travail ultrarapide sur un matériel trÚs limité. Voyons comment les transactions globales sont implémentées à l'aide de l' image Docker IRIS officielle .

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 l'atomicité. Vérification à partir de la console de base de données.

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

Nous concluons ensuite:

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

Nous obtenons:

 1 2 3 

Tout va bien. Atomicité observée: tous les changements sont enregistrés.

Nous 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, arrĂȘtez de force le conteneur, dĂ©marrez et voyez.

 docker kill my-iris 

Cette commande est presque Ă©quivalente Ă  une mise hors tension forcĂ©e, car elle envoie un signal pour arrĂȘter immĂ©diatement le processus SIGKILL.

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

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

- Non, pas conservé.

Testez la commande de restauration:

 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 été conservé non plus.

2. Cohérence


Étant donnĂ© que dans les bases de donnĂ©es sur les globaux, les clĂ©s sont Ă©galement créées sur les globaux (je rappelle qu'un global est une structure de niveau infĂ©rieur pour stocker des donnĂ©es qu'une table relationnelle), pour rĂ©pondre Ă  l'exigence de cohĂ©rence, vous devez inclure le changement de clĂ© dans la mĂȘme transaction que le changement global.

Par exemple, nous avons une personne globale ^ dans laquelle nous stockons des personnalités et nous utilisons le NIF 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 fait la clé ^ index.

 ^index('Kamenev', 'Sergey', 1234567) = 1 

Pour que la base soit convenue, nous devons ajouter des personnalités comme celle-ci:

 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 la transaction:

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

En d'autres termes, la satisfaction de l'exigence de cohérence incombe entiÚrement au programmeur. Mais en ce qui concerne les globaux, c'est normal, en raison de leur nature de bas niveau.

3. Isolement


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

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

La base de donnĂ©es devrait rĂ©soudre ce problĂšme en temps rĂ©el. Étant donnĂ© que dans les entreprises sĂ©rieuses, il y a mĂȘme une personne spĂ©ciale qui est responsable du contrĂŽle de version (pour la fusion des succursales, la rĂ©solution des conflits, etc.), et la base de donnĂ©es devrait faire tout cela en temps rĂ©el, la complexitĂ© de la tĂąche et la conception correcte de la base de donnĂ©es et le code qui le sert.

La base de donnĂ©es ne peut pas comprendre la signification des actions effectuĂ©es par les utilisateurs afin de prĂ©venir les conflits s'ils travaillent sur les mĂȘmes donnĂ©es. Il ne peut annuler qu'une transaction contrairement Ă  une autre ou les exĂ©cuter sĂ©quentiellement.

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

Dans l'exécution parallÚle de transactions, il est important pour nous qu'elles n'interfÚrent pas entre elles. 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 presque exponentiellement.

LIRE NON ENGAGÉ est le niveau d'isolement le plus bas, mais le plus rapide. Les transactions peuvent lire les modifications apportĂ©es par l'autre.

READ COMMITTED est le prochain niveau d'isolement, qui est un compromis. Les transactions ne peuvent pas lire les modifications apportées par l'autre avant un commit, mais peuvent lire toutes les modifications apportées aprÚs un commit.

Si nous avons une longue transaction T1, au cours de laquelle il y a eu des validations dans les transactions T2, T3 ... Tn qui fonctionnaient avec les mĂȘmes donnĂ©es que T1, alors lorsque nous demandons des donnĂ©es dans T1, nous obtiendrons des rĂ©sultats diffĂ©rents Ă  chaque fois. Ce phĂ©nomĂšne est appelĂ© lecture non rĂ©pĂ©table.

REPEATABLE READ - dans ce niveau d'isolement, nous n'avons pas le phĂ©nomĂšne de lecture non rĂ©pĂ©table, car pour chaque demande de lecture de donnĂ©es, un instantanĂ© des donnĂ©es de rĂ©sultat est créé et lorsqu'elles sont rĂ©utilisĂ©es dans la mĂȘme transaction, les donnĂ©es de l'instantanĂ© sont utilisĂ©es. Cependant, Ă  ce niveau d'isolement, les donnĂ©es fantĂŽmes peuvent ĂȘtre lues. Cela fait rĂ©fĂ©rence Ă  la lecture de nouvelles lignes ajoutĂ©es par des transactions validĂ©es simultanĂ©es.

SERIALIZABLE est le plus haut niveau d'isolement. Il se caractérise par le fait que les données utilisées de quelque maniÚre que ce soit dans la transaction (lecture ou modification) ne deviennent disponibles pour d'autres transactions qu'aprÚs la fin de la premiÚre transaction.

Commençons par dĂ©terminer 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 thread voit ce que fait le second qui a ouvert la transaction.

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

Nous 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 simultanĂ©es voient les donnĂ©es des autres. Nous avons donc obtenu le niveau d'isolement le plus simple, mais aussi le plus rapide LIRE NON COMMITÉ.

En principe, cela pourrait ĂȘtre prĂ©vu pour les mondiaux, pour qui la vitesse a toujours Ă©tĂ© primordiale.

Mais que faire si nous avons besoin d'un niveau d'isolement plus élevé dans les opérations mondiales?

Ici, vous devez vous demander pourquoi les niveaux d'isolation sont nécessaires et comment ils fonctionnent.

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

Nous pouvons le faire à l'aide de verrous compétents en ObjectScript, qui ont beaucoup de façons différentes d'appliquer: vous pouvez faire des verrous multiples réguliers et incrémentiels avec la commande LOCK .

Des niveaux d'isolement plus faibles 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 pour modifier les donnĂ©es, mais aussi les verrous dits partagĂ©s, qui peuvent prendre plusieurs threads Ă  la fois, lorsqu'ils ont besoin de lire des donnĂ©es qui ne devraient pas ĂȘtre modifiĂ©es par d'autres processus pendant la lecture.

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

→ Verrouillage biphasĂ©
→ Verrouillage biphasĂ©

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

En utilisant des verrous, nous ferons de telles fenĂȘtres de visibilitĂ© dans lesquelles l'Ă©tat de la base de donnĂ©es sera convenu. Et tous les appels Ă  de telles fenĂȘtres de visibilitĂ© de l'Ă©tat convenu seront contrĂŽlĂ©s par des verrous.

Les verrous partagĂ©s des 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 les fenĂȘtres de l'Ă©tat coordonnĂ© de la base de donnĂ©es.

Des verrous exclusifs sont utilisés pour modifier les données - un seul processus peut prendre un tel verrou. Le blocage exclusif peut prendre:

  1. Tout processus si les données sont libres
  2. Seul le processus qui a un verrou partagé sur ces données et le premier a demandé un verrou exclusif.



Plus la fenĂȘtre de visibilitĂ© est Ă©troite, plus il faut de temps aux autres processus pour attendre, mais plus l'Ă©tat de la base de donnĂ©es peut ĂȘtre cohĂ©rent.

READ_COMMITED - l'essence de ce niveau est que nous ne voyons que les données d'autres flux qui sont verrouillés. Si les données d'une autre transaction ne sont pas encore validées, alors nous voyons leur ancienne version.

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

Sans astuces spéciales, nous ne pourrons pas voir l'ancienne version des données dans IRIS, nous devons donc faire avec les verrous.

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

Supposons que nous ayons une base d'utilisateurs ^ personne qui se transfĂšre 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 verrou exclusif (par dĂ©faut):

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

Et si vous devez afficher l'état du compte dans votre compte, vous pouvez utiliser le verrouillage partagé ou ne pas l'utiliser du tout:

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

Cependant, si nous supposons que les opérations de base de données sont effectuées presque instantanément (je me souviens que les globaux sont une structure de niveau beaucoup plus faible qu'une table relationnelle), alors le besoin de ce niveau diminue.

REPEATABLE READ - Dans ce niveau d'isolement, il est supposĂ© qu'il peut y avoir 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 permet Ă  un opĂ©rateur de rĂ©pertorier en dĂ©tail tous les verrous nĂ©cessaires, qui peuvent ĂȘtre trĂšs nombreux.

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

autres opérations (pour le moment, les threads parallÚles tentent de changer ^ personne (123, montant), mais ne 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 des verrous séparés par des virgules, ils sont pris de maniÚre séquentielle, et si vous le faites:

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

puis ils sont pris atomiquement en une seule fois.

SÉRIALISER - nous devrons dĂ©finir les verrous de sorte que finalement toutes les transactions qui ont des donnĂ©es communes soient exĂ©cutĂ©es sĂ©quentiellement. Pour cette approche, la plupart des serrures doivent ĂȘtre exclusives et prises dans les plus petites zones du monde pour des performances.

Si nous parlons de radiations dans la personne globale, alors seul le niveau d'isolement SERIALIZE lui est acceptable, car l'argent doit ĂȘtre dĂ©pensĂ© strictement sĂ©quentiellement, sinon il est possible de dĂ©penser le mĂȘme montant plusieurs fois.

4. Durabilité


J'ai effectué des tests avec une découpe difficile du conteneur à travers

 docker kill my-iris 

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

Conclusion


Pour les pays du monde entier, InterSystems IRIS prend en charge les transactions. Ils sont vraiment atomiques, fiables. Pour garantir la cohérence de la base de données sur les globaux, les efforts du programmeur et l'utilisation des transactions sont nécessaires, car il n'y a pas de constructions intégrées complexes telles que des clés étrangÚres.

Le niveau d'isolement des globaux sans utilisation de verrous est LIRE NON COMMIS, et lors de l'utilisation de verrous, il peut ĂȘtre assurĂ© jusqu'au niveau SERIALIZE.

L'exactitude et la rapidité des transactions sur les globaux dépendent beaucoup de la compétence du programmeur: plus les verrous partagés sont largement utilisés lors de la lecture, plus le niveau d'isolement est élevé et plus les verrous exclusifs sont pris, plus la vitesse est élevée.

Source: https://habr.com/ru/post/fr461753/


All Articles