Réplication de haut niveau dans le SGBD Tarantool

Bonjour, je crĂ©e des applications pour le SGBD Tarantool - il s'agit d'une plateforme dĂ©veloppĂ©e par Mail.ru Group qui combine un SGBD hautes performances et un serveur d'applications Ă  Lua. La vitesse Ă©levĂ©e des solutions basĂ©es sur Tarantool est obtenue, en particulier, en prenant en charge le mode SGBD en mĂ©moire et la possibilitĂ© d'exĂ©cuter la logique d'application mĂ©tier dans un espace d'adressage unique avec des donnĂ©es. Cela garantit la persistance des donnĂ©es Ă  l'aide des transactions ACID (un journal WAL est conservĂ© sur le disque). Tarantool a un support de rĂ©plication et de partitionnement intĂ©grĂ©. À partir de la version 2.1, les requĂȘtes SQL sont prises en charge. Tarantool est open source et sous licence BSD simplifiĂ©e. Il existe Ă©galement une version commerciale d'entreprise.


Ressentez le pouvoir! (... aka profitez de la performance)

Tout cela fait de Tarantool une plate-forme attrayante pour créer des applications de base de données trÚs chargées. Dans de telles applications, la réplication des données devient souvent nécessaire.

Comme mentionnĂ© ci-dessus, Tarantool a une rĂ©plication de donnĂ©es intĂ©grĂ©e. Le principe de son travail est l'exĂ©cution sĂ©quentielle sur des rĂ©pliques de toutes les transactions contenues dans le journal de l'assistant (WAL). Typiquement, une telle rĂ©plication (nous l'appellerons bas niveau ci - dessous) est utilisĂ©e pour fournir une tolĂ©rance aux pannes de l'application et / ou pour rĂ©partir la charge de lecture entre les nƓuds du cluster.


Fig. 1. Réplication au sein du cluster

Un exemple de scĂ©nario alternatif est le transfert de donnĂ©es créées dans une base de donnĂ©es vers une autre base de donnĂ©es pour traitement / surveillance. Dans ce dernier cas, une solution plus pratique peut ĂȘtre d'utiliser la rĂ©plication de haut niveau - rĂ©plication des donnĂ©es au niveau de la logique mĂ©tier de l'application. C'est-Ă -dire Nous n'utilisons pas de solution prĂȘte Ă  l'emploi intĂ©grĂ©e au SGBD, mais nous implĂ©mentons nous-mĂȘmes la rĂ©plication dans l'application que nous dĂ©veloppons. Cette approche prĂ©sente Ă  la fois des avantages et des inconvĂ©nients. Nous listons les avantages.

1. Économisez du trafic:

  • vous ne pouvez pas transfĂ©rer toutes les donnĂ©es, mais seulement une partie de celles-ci (par exemple, vous ne pouvez transfĂ©rer que certaines tables, certaines de leurs colonnes ou enregistrements qui rĂ©pondent Ă  un certain critĂšre);
  • contrairement Ă  la rĂ©plication de bas niveau, qui est effectuĂ©e en continu en mode asynchrone (implĂ©mentĂ© dans la version actuelle de Tarantool - 1.10) ou synchrone (Ă  implĂ©menter dans les futures versions de Tarantool), la rĂ©plication de haut niveau peut ĂȘtre effectuĂ©e par sessions (c'est-Ă -dire que l'application effectue d'abord la synchronisation des donnĂ©es - session d'Ă©change donnĂ©es, puis il y a une pause dans la rĂ©plication, aprĂšs quoi la prochaine session d'Ă©change a lieu, etc.);
  • si l'enregistrement a changĂ© plusieurs fois, vous ne pouvez transfĂ©rer que sa derniĂšre version (contrairement Ă  la rĂ©plication de bas niveau, dans laquelle toutes les modifications apportĂ©es Ă  l'assistant seront lues sĂ©quentiellement sur les rĂ©pliques).

2. La mise en Ɠuvre de l'Ă©change via HTTP ne pose aucune difficultĂ©, ce qui vous permet de synchroniser des bases de donnĂ©es distantes.


Fig. 2. Réplication HTTP

3. Les structures de base de donnĂ©es entre lesquelles les donnĂ©es sont transmises ne doivent pas ĂȘtre les mĂȘmes (en outre, dans le cas gĂ©nĂ©ral, il est mĂȘme possible d'utiliser diffĂ©rents SGBD, langages de programmation, plates-formes, etc.).


Fig. 3. Réplication dans des systÚmes hétérogÚnes

L'inconvénient est qu'en moyenne, la programmation est plus compliquée / plus chÚre que la configuration, et au lieu de configurer la fonctionnalité intégrée, vous devrez implémenter la vÎtre.

Si dans votre situation, les avantages ci-dessus jouent un rÎle décisif (ou sont une condition nécessaire), il est alors judicieux d'utiliser la réplication de haut niveau. Examinons plusieurs façons d'implémenter la réplication de données de haut niveau dans le SGBD Tarantool.

Minimisation du trafic


Ainsi, l'un des avantages de la rĂ©plication de haut niveau est la rĂ©duction du trafic. Pour que cet avantage se manifeste pleinement, il est nĂ©cessaire de minimiser la quantitĂ© de donnĂ©es transmises lors de chaque session d'Ă©change. Bien entendu, il ne faut pas oublier qu'Ă  la fin de la session le rĂ©cepteur de donnĂ©es doit ĂȘtre synchronisĂ© avec la source (au moins pour la partie des donnĂ©es impliquĂ©e dans la rĂ©plication).

Comment minimiser la quantitĂ© de donnĂ©es transfĂ©rĂ©es lors de la rĂ©plication de haut niveau? La solution "au front" peut ĂȘtre la sĂ©lection des donnĂ©es par date-heure. Pour ce faire, vous pouvez utiliser le champ date-heure dĂ©jĂ  dans le tableau (le cas Ă©chĂ©ant). Par exemple, un document «ordre» peut avoir un champ «temps requis pour l'exĂ©cution de l'ordre» - delivery_time . Le problĂšme avec cette solution est que les valeurs de ce champ ne doivent pas nĂ©cessairement ĂȘtre dans la sĂ©quence correspondant Ă  la crĂ©ation des commandes. Ainsi, nous ne pouvons pas nous souvenir de la valeur maximale du champ delivery_time transmise lors de la session d'Ă©change prĂ©cĂ©dente, et lors de la session d'Ă©change suivante, sĂ©lectionnez tous les enregistrements avec une valeur plus Ă©levĂ©e du champ delivery_time . Dans l'intervalle entre les sessions d'Ă©change, des enregistrements avec une valeur plus petite du champ delivery_time peuvent ĂȘtre ajoutĂ©s. De plus, la commande pourrait subir des modifications, ce qui n'affectait nĂ©anmoins pas le champ delivery_time . Dans les deux cas, les modifications ne seront pas transmises de la source au rĂ©cepteur. Pour rĂ©soudre ces problĂšmes, nous devrons transmettre des donnĂ©es "en chevauchement". C'est-Ă -dire au cours de chaque session d'Ă©change, nous transfĂ©rerons toutes les donnĂ©es avec une valeur de champ delivery_time qui dĂ©passe un certain point dans le passĂ© (par exemple, N heures Ă  partir du moment actuel). Cependant, il est Ă©vident que pour les grands systĂšmes, cette approche est trĂšs redondante et peut rĂ©duire les Ă©conomies de trafic que nous visons. De plus, la table transmise peut ne pas avoir de champ date-heure.

Une autre solution, plus complexe en termes de mise en Ɠuvre, consiste Ă  accuser rĂ©ception des donnĂ©es. Dans ce cas, Ă  chaque session d'Ă©change, toutes les donnĂ©es sont transmises, dont la rĂ©ception n'est pas confirmĂ©e par le destinataire. Pour l'implĂ©mentation, vous devez ajouter une colonne boolĂ©enne Ă  la table source (par exemple, is_transferred ). Si le destinataire confirme la rĂ©ception de l'enregistrement, le champ correspondant est dĂ©fini sur true , aprĂšs quoi l'enregistrement n'est plus impliquĂ© dans les Ă©changes. Cette option de mise en Ɠuvre prĂ©sente les inconvĂ©nients suivants. Tout d'abord, pour chaque enregistrement transfĂ©rĂ©, il est nĂ©cessaire de gĂ©nĂ©rer et d'envoyer une confirmation. En gros, cela peut ĂȘtre comparable Ă  doubler la quantitĂ© de donnĂ©es transfĂ©rĂ©es et Ă  doubler le nombre de voyages aller-retour. DeuxiĂšmement, il n'est pas possible d'envoyer le mĂȘme enregistrement Ă  plusieurs rĂ©cepteurs (le premier rĂ©cepteur confirmera la rĂ©ception pour lui-mĂȘme et pour tout le monde).

La mĂ©thode, dĂ©pourvue des inconvĂ©nients ci-dessus, consiste Ă  ajouter des colonnes au tableau Ă  transmettre pour suivre les modifications de ses lignes. Une telle colonne peut ĂȘtre de type date-heure et doit ĂȘtre dĂ©finie / mise Ă  jour par l'application pour l'heure actuelle Ă  chaque fois en ajoutant / changeant des enregistrements (atomiquement avec en ajoutant / changeant). À titre d'exemple, appelons la colonne update_time . AprĂšs avoir enregistrĂ© la valeur maximale du champ de cette colonne pour les enregistrements transfĂ©rĂ©s, nous pouvons dĂ©marrer la prochaine session d'Ă©change Ă  partir de cette valeur (sĂ©lectionnez les enregistrements dont la valeur du champ update_time dĂ©passe la valeur prĂ©cĂ©demment enregistrĂ©e). Le problĂšme avec cette derniĂšre approche est que les changements de donnĂ©es peuvent se produire en mode batch. Par consĂ©quent, les valeurs de champ dans la colonne update_time ne pas ĂȘtre uniques. Par consĂ©quent, cette colonne ne peut pas ĂȘtre utilisĂ©e pour la sortie de donnĂ©es par lots (page). Pour la sortie des donnĂ©es page par page, il sera nĂ©cessaire d'inventer des mĂ©canismes supplĂ©mentaires susceptibles d'avoir une trĂšs faible efficacitĂ© (par exemple, extraire de la base de donnĂ©es tous les enregistrements avec update_time au-dessus de la valeur spĂ©cifiĂ©e et Ă©mettre un certain nombre d'enregistrements, en commençant Ă  un certain dĂ©calage par rapport au dĂ©but de l'Ă©chantillon).

Vous pouvez augmenter l'efficacitĂ© du transfert de donnĂ©es en amĂ©liorant lĂ©gĂšrement l'approche prĂ©cĂ©dente. Pour ce faire, nous utiliserons un type entier (entier long) comme valeurs des champs de colonne pour suivre les modifications. row_ver colonne row_ver . La valeur de champ de cette colonne doit toujours ĂȘtre dĂ©finie / mise Ă  jour chaque fois qu'un enregistrement est créé / modifiĂ©. Mais dans ce cas, le champ sera attribuĂ© non pas la date-heure actuelle, mais la valeur d'un compteur augmentĂ©e d'une unitĂ©. Par consĂ©quent, la colonne row_ver contiendra des valeurs uniques et peut ĂȘtre utilisĂ©e non seulement pour sortir des donnĂ©es «delta» (donnĂ©es ajoutĂ©es / modifiĂ©es aprĂšs la fin de la session d'Ă©change prĂ©cĂ©dente), mais aussi pour une pagination simple et efficace.

La derniĂšre mĂ©thode proposĂ©e pour minimiser la quantitĂ© de donnĂ©es transfĂ©rĂ©es dans le cadre d'une rĂ©plication de haut niveau me semble la plus optimale et la plus universelle. ArrĂȘtons-nous dessus plus en dĂ©tail.

Transfert de données à l'aide du compteur de version de ligne


Implémentation serveur / maßtre


Dans MS SQL Server, pour implémenter cette approche, il existe un type de colonne spécial - rowversion . Chaque base de données a un compteur, qui augmente d'une unité chaque fois que vous ajoutez / modifiez un enregistrement dans une table qui a une colonne de type rowversion . La valeur de ce compteur est automatiquement affectée au champ de cette colonne dans l'enregistrement ajouté / modifié. Tarantool DBMS n'a pas de mécanisme intégré similaire. Cependant, dans Tarantool, il n'est pas difficile de l'implémenter manuellement. Considérez comment cela se fait.

Tout d'abord, un peu de terminologie: les tables de Tarantool sont appelées espace et les enregistrements sont appelés tuple. Dans Tarantool, vous pouvez créer des séquences. Les séquences ne sont rien de plus que des générateurs nommés de valeurs ordonnées d'entiers. C'est-à-dire c'est exactement ce dont nous avons besoin pour nos besoins. Ci-dessous, nous allons créer une telle séquence.

Avant d'effectuer une opération de base de données dans Tarantool, vous devez exécuter la commande suivante:

 box.cfg{} 

Par conséquent, Tarantool commencera à écrire des instantanés et un journal des transactions dans le répertoire actuel.

Créez une séquence row_version :

 box.schema.sequence.create('row_version', { if_not_exists = true }) 

L'option if_not_exists permet d'exécuter le script de création plusieurs fois: si l'objet existe, Tarantool n'essaiera pas de le recréer. Cette option sera utilisée dans toutes les commandes DDL suivantes.

Créons un espace pour un exemple.

 box.schema.space.create('goods', { format = { { name = 'id', type = 'unsigned' }, { name = 'name', type = 'string' }, { name = 'code', type = 'unsigned' }, { name = 'row_ver', type = 'unsigned' } }, if_not_exists = true }) 

Ici, nous définissons le nom de l'espace ( goods ), les noms des champs et leurs types.

Les champs d'incrémentation automatique de Tarantool sont également créés à l'aide de séquences. Créez une clé primaire à incrémentation automatique pour le champ id :

 box.schema.sequence.create('goods_id', { if_not_exists = true }) box.space.goods:create_index('primary', { parts = { 'id' }, sequence = 'goods_id', unique = true, type = 'HASH', if_not_exists = true }) 

Tarantool prend en charge plusieurs types d'index. Le plus souvent, des index des types TREE et HASH sont utilisés, qui sont basés sur les structures correspondant au nom. TREE est le type d'index le plus polyvalent. Il vous permet de récupérer des données de maniÚre ordonnée. Mais pour le choix de l'égalité, HASH est plus adapté. En conséquence, il est conseillé d'utiliser HASH pour la clé primaire (ce que nous avons fait).

Pour utiliser la colonne row_ver pour transmettre des données modifiées, vous devez lier les valeurs de séquence row_ver aux champs de cette colonne. Mais contrairement à la clé primaire, la valeur du champ dans la colonne row_ver doit augmenter d'une row_ver , non seulement lors de l'ajout de nouveaux enregistrements, mais également lors de la modification des enregistrements existants. Pour ce faire, vous pouvez utiliser des déclencheurs. Tarantool a deux types de déclencheurs pour les espaces: before_replace et on_replace . Les déclencheurs sont déclenchés chaque fois que les données de l'espace sont modifiées (pour chaque tuple affecté par les modifications, la fonction de déclenchement est déclenchée). Contrairement à on_replace , les déclencheurs before_replace vous permettent de modifier les données du tuple pour lequel le déclencheur est exécuté. En conséquence, le dernier type de déclencheurs nous convient.

 box.space.goods:before_replace(function(old, new) return box.tuple.new({new[1], new[2], new[3], box.sequence.row_version:next()}) end) 

Ce déclencheur remplace la valeur du champ row_ver du tuple stocké par la row_version séquence row_version suivante.

Afin de pouvoir extraire des données de l'espace goods sur la colonne row_ver , créez un index:

 box.space.goods:create_index('row_ver', { parts = { 'row_ver' }, unique = true, type = 'TREE', if_not_exists = true }) 

Le type d'index est un arbre ( TREE ), car nous devons récupérer les données dans l'ordre croissant des valeurs dans la colonne row_ver .

Ajoutez des données à l'espace:

 box.space.goods:insert{nil, 'pen', 123} box.space.goods:insert{nil, 'pencil', 321} box.space.goods:insert{nil, 'brush', 100} box.space.goods:insert{nil, 'watercolour', 456} box.space.goods:insert{nil, 'album', 101} box.space.goods:insert{nil, 'notebook', 800} box.space.goods:insert{nil, 'rubber', 531} box.space.goods:insert{nil, 'ruler', 135} 

Parce que le premier champ est un compteur d'incrĂ©mentation automatique, nous passons nil Ă  la place. Tarantool remplacera automatiquement la valeur suivante. De mĂȘme, vous pouvez passer nil comme valeur des champs dans la colonne row_ver - ou ne pas spĂ©cifier la valeur du tout, car cette colonne prend la derniĂšre position dans l'espace.

Vérifiez le résultat de l'insert:

 tarantool> box.space.goods:select() --- - - [1, 'pen', 123, 1] - [2, 'pencil', 321, 2] - [3, 'brush', 100, 3] - [4, 'watercolour', 456, 4] - [5, 'album', 101, 5] - [6, 'notebook', 800, 6] - [7, 'rubber', 531, 7] - [8, 'ruler', 135, 8] ... 

Comme vous pouvez le voir, le premier et le dernier champ ont été remplis automatiquement. Maintenant, il sera facile d'écrire une fonction pour paginer le déchargement des goods :

 local page_size = 5 local function get_goods(row_ver) local index = box.space.goods.index.row_ver local goods = {} local counter = 0 for _, tuple in index:pairs(row_ver, { iterator = 'GT' }) do local obj = tuple:tomap({ names_only = true }) table.insert(goods, obj) counter = counter + 1 if counter >= page_size then break end end return goods end 

La fonction prend en paramÚtre la valeur row_ver du dernier enregistrement reçu (0 pour le premier appel) et renvoie le prochain lot de données modifiées (s'il y en a un, sinon un tableau vide).

La récupération des données dans Tarantool se fait via des index. La fonction get_goods utilise l' row_ver index row_ver pour récupérer les données modifiées. Le type d'itérateur est GT (supérieur à, supérieur à). Cela signifie que l'itérateur parcourra séquentiellement les valeurs d'index à partir de la valeur suivante aprÚs la clé transmise.

L'itérateur renvoie les tuples. Afin de pouvoir ultérieurement transférer des données via HTTP, il est nécessaire de convertir les tuples en une structure pratique pour une sérialisation ultérieure. Dans l'exemple, la fonction tomap standard est utilisée pour cela. Au lieu d'utiliser tomap vous pouvez écrire votre propre fonction. Par exemple, nous pourrions vouloir renommer le champ de name , ne pas passer le champ de code et ajouter le champ de comment :

 local function unflatten_goods(tuple) local obj = {} obj.id = tuple.id obj.goods_name = tuple.name obj.comment = 'some comment' obj.row_ver = tuple.row_ver return obj end 

La taille de page des donnĂ©es de sortie (le nombre d'enregistrements dans une partie) est dĂ©terminĂ©e par la variable page_size . Dans l'exemple, la valeur de page_size est 5. Dans un programme rĂ©el, la taille de la page est gĂ©nĂ©ralement plus importante. Cela dĂ©pend de la taille moyenne du tuple spatial. La taille de page optimale peut ĂȘtre sĂ©lectionnĂ©e empiriquement en mesurant le temps de transfert des donnĂ©es. Plus la page est grande, plus le nombre d'allers-retours entre les cĂŽtĂ©s Ă©metteur et rĂ©cepteur est faible. Vous pouvez donc rĂ©duire le temps total de tĂ©lĂ©chargement des modifications. Cependant, si la taille de la page est trop grande, nous prendrons trop de temps au serveur pour sĂ©rialiser la sĂ©lection. Par consĂ©quent, il peut y avoir des retards dans le traitement des autres demandes qui sont parvenues au serveur. Le paramĂštre page_size peut ĂȘtre chargĂ© Ă  partir du fichier de configuration. Pour chaque espace transmis, vous pouvez dĂ©finir votre propre valeur. Cependant, pour la plupart des espaces, la valeur par dĂ©faut (par exemple, 100) peut convenir.

get_goods fonction get_goods dans le module. CrĂ©ez un fichier repl.lua contenant la description de la variable page_size et la fonction get_goods . À la fin du fichier, ajoutez la fonction d'exportation:

 return { get_goods = get_goods } 

Pour charger le module, exécutez:

 tarantool> repl = require('repl') --- ... 

get_goods fonction get_goods :

 tarantool> repl.get_goods(0) --- - - row_ver: 1 code: 123 name: pen id: 1 - row_ver: 2 code: 321 name: pencil id: 2 - row_ver: 3 code: 100 name: brush id: 3 - row_ver: 4 code: 456 name: watercolour id: 4 - row_ver: 5 code: 101 name: album id: 5 ... 

Prenez la valeur du champ row_ver de la derniĂšre ligne et appelez Ă  nouveau la fonction:

 tarantool> repl.get_goods(5) --- - - row_ver: 6 code: 800 name: notebook id: 6 - row_ver: 7 code: 531 name: rubber id: 7 - row_ver: 8 code: 135 name: ruler id: 8 ... 

Et encore:

 tarantool> repl.get_goods(8) --- - [] ... 

Comme vous pouvez le voir, avec cette utilisation, la fonction page par page renvoie tous les enregistrements de l'espace goods . La derniÚre page est suivie d'une sélection vide.

Nous apporterons des modifications Ă  l'espace:

 box.space.goods:update(4, {{'=', 6, 'copybook'}}) box.space.goods:insert{nil, 'clip', 234} box.space.goods:insert{nil, 'folder', 432} 

Nous avons modifié la valeur du champ de name pour un enregistrement et ajouté deux nouveaux enregistrements.

Répétez le dernier appel de fonction:

 tarantool> repl.get_goods(8) --- - - row_ver: 9 code: 800 name: copybook id: 6 - row_ver: 10 code: 234 name: clip id: 9 - row_ver: 11 code: 432 name: folder id: 10 ... 

La fonction a renvoyé les enregistrements modifiés et ajoutés. Ainsi, la fonction get_goods permet d'obtenir des données qui ont changé depuis son dernier appel, qui est la base de la méthode de réplication considérée.

Nous laissons la sortie des résultats via HTTP sous forme de JSON au-delà de la portée de cet article. Vous pouvez en lire plus ici: https://habr.com/ru/company/mailru/blog/272141/

Réalisation de la partie client / esclave


ConsidĂ©rez Ă  quoi ressemble la mise en Ɠuvre du cĂŽtĂ© rĂ©cepteur. CrĂ©ez un espace cĂŽtĂ© rĂ©ception pour stocker les donnĂ©es tĂ©lĂ©chargĂ©es:

 box.schema.space.create('goods', { format = { { name = 'id', type = 'unsigned' }, { name = 'name', type = 'string' }, { name = 'code', type = 'unsigned' } }, if_not_exists = true }) box.space.goods:create_index('primary', { parts = { 'id' }, sequence = 'goods_id', unique = true, type = 'HASH', if_not_exists = true }) 

La structure de l'espace ressemble à la structure de l'espace dans la source. Mais comme nous row_ver pas transférer les données reçues ailleurs, la colonne row_ver est row_ver dans l'espace du récepteur. Dans le champ id seront inscrits les identifiants de la source. Par conséquent, cÎté récepteur, il n'est pas nécessaire de l'incrémenter automatiquement.

De plus, nous avons besoin d'un espace pour enregistrer les valeurs row_ver :

 box.schema.space.create('row_ver', { format = { { name = 'space_name', type = 'string' }, { name = 'value', type = 'string' } }, if_not_exists = true }) box.space.row_ver:create_index('primary', { parts = { 'space_name' }, unique = true, type = 'HASH', if_not_exists = true }) 

Pour chaque espace chargé (champ space_name ), nous enregistrerons ici la derniÚre valeur chargée row_ver ( value champ). La clé primaire est la colonne space_name .

Créons une fonction pour charger les données de l'espace goods via HTTP. Pour ce faire, nous avons besoin d'une bibliothÚque qui implémente un client HTTP. La ligne suivante charge la bibliothÚque et instancie le client HTTP:

 local http_client = require('http.client').new() 

Nous avons également besoin d'une bibliothÚque pour la désérialisation json:

 local json = require('json') 

Cela suffit pour créer une fonction de chargement des données:

 local function load_data(url, row_ver) local url = ('%s?rowVer=%s'):format(url, tostring(row_ver)) local body = nil local data = http_client:request('GET', url, body, { keepalive_idle = 1, keepalive_interval = 1 }) return json.decode(data.body) end 

La fonction exĂ©cute une requĂȘte HTTP Ă  l'URL, lui transmet row_ver comme paramĂštre et renvoie le rĂ©sultat dĂ©sĂ©rialisĂ© de la requĂȘte.

La fonction de sauvegarde des données reçues est la suivante:

 local function save_goods(goods) local n = #goods box.atomic(function() for i = 1, n do local obj = goods[i] box.space.goods:put( obj.id, obj.name, obj.code) end end) end 

Le cycle de stockage des données dans l'espace goods est placé dans une transaction (la fonction box.atomic est utilisée pour cela) afin de réduire le nombre d'opérations sur disque.

Enfin, la fonction de synchronisation des goods spatiaux locaux avec la source peut ĂȘtre implĂ©mentĂ©e comme suit:

 local function sync_goods() local tuple = box.space.row_ver:get('goods') local row_ver = tuple and tuple.value or 0 -- set your url here: local url = 'http://127.0.0.1:81/test/goods/list' while true do local goods = load_goods(url, row_ver) local count = #goods if count == 0 then return end save_goods(goods) row_ver = goods[count].rowVer box.space.row_ver:put({'goods', row_ver}) end end 

Tout d'abord, nous lisons la valeur row_ver prĂ©cĂ©demment enregistrĂ©e pour l'espace des goods . S'il est absent (la premiĂšre session d'Ă©change), alors nous prenons zĂ©ro comme row_ver . Ensuite, dans la boucle, nous paginons les donnĂ©es modifiĂ©es de la source vers l'URL spĂ©cifiĂ©e. À chaque itĂ©ration, nous enregistrons les donnĂ©es reçues dans l'espace local correspondant et row_ver jour la valeur row_ver (dans l' row_ver row_ver et dans la variable row_ver ) - nous prenons la valeur row_ver de la derniĂšre ligne des donnĂ©es chargĂ©es.

Pour se protĂ©ger contre les boucles accidentelles (en cas d'erreur dans le programme), la while peut ĂȘtre remplacĂ©e par for :

 for _ = 1, max_req do ... 

Grùce à la fonction sync_goods , les goods dans le récepteur contiendront les derniÚres versions de tous les enregistrements d'espace goods dans la source.

De toute Ă©vidence, la suppression des donnĂ©es ne peut pas ĂȘtre diffusĂ©e de cette maniĂšre. Si un tel besoin existe, vous pouvez utiliser la marque de suppression. Ajoutez le champ boolĂ©en is_deleted espace des goods et utilisez la suppression logique au lieu de supprimer physiquement l'enregistrement - dĂ©finissez la valeur du champ is_deleted sur true . Parfois, au lieu du champ boolĂ©en is_deleted , is_deleted plus pratique d'utiliser le champ deleted , qui stocke la date-heure de la suppression logique de l'enregistrement. AprĂšs avoir effectuĂ© une suppression logique, l'enregistrement marquĂ© pour suppression sera transfĂ©rĂ© de la source au rĂ©cepteur (selon la logique dĂ©crite ci-dessus).

La sĂ©quence row_ver peut ĂȘtre utilisĂ©e pour transfĂ©rer des donnĂ©es depuis d'autres espaces: il n'est pas nĂ©cessaire de crĂ©er une sĂ©quence distincte pour chaque espace transmis.

Nous avons examiné un moyen efficace de réplication de données de haut niveau dans les applications utilisant le SGBD Tarantool.

Conclusions


  1. Tarantool DBMS est un produit attrayant et prometteur pour la création d'applications trÚs chargées.
  2. La réplication de haut niveau offre une approche plus flexible du transfert de données par rapport à la réplication de bas niveau.
  3. La méthode de réplication de haut niveau envisagée dans l'article permet de minimiser la quantité de données transmises en transférant uniquement les enregistrements qui ont changé depuis la derniÚre session d'échange.

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


All Articles