Bonjour, je crĂ©e des applications pour les bases de donnĂ©es. â c'est une plateforme dĂ©veloppĂ©e par Mail.ru Group, combinant une base de donnĂ©es hautes performances et un serveur d'applications en Lua. La rapiditĂ© des solutions basĂ©es sur Tarantool est notamment due Ă la prise en charge du mode in-memory de la base de donnĂ©es et Ă la possibilitĂ© d'exĂ©cuter la logique mĂ©tier de l'application dans le mĂȘme espace d'adresses que les donnĂ©es. La persistance des donnĂ©es est garantie par des transactions ACID (un journal WAL est maintenu sur disque). Tarantool intĂšgre un support de rĂ©plication et de sharding. Ă partir de la version 2.1, des requĂȘtes en SQL sont prises en charge. Tarantool est open source et distribuĂ© sous une licence BSD simplifiĂ©e. Il existe Ă©galement une version commerciale Enterprise.

Ressentez la puissance ! (âŠou plutĂŽt, profitez de la performance)
Tout cela fait de Tarantool une plateforme attrayante pour la création d'applications fortement sollicitées fonctionnant avec des bases de données. Dans de telles applications, il est souvent nécessaire de répliquer des données.
Comme mentionnĂ© prĂ©cĂ©demment, Tarantool dispose d'une rĂ©plication de donnĂ©es intĂ©grĂ©e. Son principe de fonctionnement consiste Ă exĂ©cuter sĂ©quentiellement sur les rĂ©pliques toutes les transactions contenues dans le journal principal (WAL). En gĂ©nĂ©ral, cette rĂ©plication (que nous appellerons de bas niveau) est utilisĂ©e pour assurer la rĂ©silience de l'application et/ou pour rĂ©partir la charge de lecture entre les nĆuds du cluster.

Fig. 1. Réplication à l'intérieur du cluster
Un exemple de scĂ©nario alternatif pourrait ĂȘtre le transfert de donnĂ©es créées dans une base de donnĂ©es vers une autre base de donnĂ©es pour traitement/suivi. Dans ce dernier cas, une solution plus pratique pourrait ĂȘtre d'utiliser de haut niveau la rĂ©plication â la rĂ©plication de donnĂ©es au niveau de la logique mĂ©tier de l'application. C'est-Ă -dire que nous n'utilisons pas une solution prĂȘte Ă l'emploi intĂ©grĂ©e Ă la base de donnĂ©es, mais que nous mettons en Ćuvre nous-mĂȘmes la rĂ©plication Ă l'intĂ©rieur de l'application que nous dĂ©veloppons. Cette approche a ses avantages et ses inconvĂ©nients. ĂnumĂ©rons les points positifs.
1. Ăconomie de trafic :
- il est possible de transmettre non pas toutes les données, mais seulement une partie (par exemple, on peut transmettre uniquement certaines tables, certains de leurs colonnes ou des enregistrements correspondant à certains critÚres);
- Contrairement Ă la rĂ©plication de bas niveau, qui s'effectue en continu en mode asynchrone (rĂ©alisĂ© dans la version actuelle de Tarantool - 1.10) ou synchrone (qui sera rĂ©alisĂ© dans les versions ultĂ©rieures de Tarantool), la rĂ©plication de haut niveau peut ĂȘtre exĂ©cutĂ©e par sessions (c'est-Ă -dire que l'application effectue d'abord la synchronisation des donnĂ©es - session d'Ă©change de 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 Ă©tĂ© modifiĂ© plusieurs fois, seule sa derniĂšre version peut ĂȘtre transmise (contrairement Ă la rĂ©plication de bas niveau, oĂč les modifications effectuĂ©es sur le maĂźtre seront reproduites successivement sur les rĂ©pliques).
2. Aucune complexitĂ© dans la mise en Ćuvre de l'Ă©change par HTTP, ce qui permet de synchroniser des bases de donnĂ©es distantes.

Fig. 2. Réplication par HTTP
3. Les structures des bases de donnĂ©es entre lesquelles les donnĂ©es sont transmises n'ont pas besoin d'ĂȘtre identiques (de plus, en gĂ©nĂ©ral, il est mĂȘme possible d'utiliser diffĂ©rentes 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 complexe/coûteuse que la configuration, et au lieu de configurer une fonctionnalité intégrée, il faudra réaliser la sienne.
Si dans votre situation, les avantages mentionnés sont décisifs (ou sont une condition nécessaire), il est judicieux d'utiliser la réplication de haut niveau. Examinons plusieurs méthodes pour réaliser la réplication de données de haut niveau dans le SGBD Tarantool.
Minimiser le trafic
Ainsi, l'un des avantages de la rĂ©plication de haut niveau est l'Ă©conomie de trafic. Pour que cet avantage se manifeste pleinement, il est nĂ©cessaire de minimiser la quantitĂ© de donnĂ©es transmises Ă chaque session d'Ă©change. Ăvidemment, il ne faut pas oublier qu'Ă la fin de la session, le rĂ©cepteur des donnĂ©es doit ĂȘtre synchronisĂ© avec la source (au moins pour la partie des donnĂ©es qui participe Ă la rĂ©plication).
Comment minimiser la quantitĂ© de donnĂ©es transmises lors de la rĂ©plication de haut niveau ? Une solution « directe » pourrait ĂȘtre de sĂ©lectionner des donnĂ©es par date-heure. Pour cela, on peut utiliser le champ date-heure dĂ©jĂ prĂ©sent dans la table (s'il existe). Par exemple, un document « commande » peut avoir un champ « temps d'exĂ©cution requis de la commande » - temps_de_livraison. Le problĂšme avec cette solution rĂ©side dans le fait que les valeurs de ce champ ne doivent pas nĂ©cessairement ĂȘtre organisĂ©es dans un ordre correspondant Ă la crĂ©ation des commandes. Ainsi, nous ne pouvons pas mĂ©moriser la valeur maximale du champ temps_de_livraison, transmise lors de la session prĂ©cĂ©dente d'Ă©change, et lors de la prochaine session d'Ă©change, sĂ©lectionner tous les enregistrements avec une valeur de champ plus Ă©levĂ©e temps_de_livraison. Entre les sessions d'Ă©change, des enregistrements avec une valeur de champ infĂ©rieure peuvent avoir Ă©tĂ© ajoutĂ©s temps_de_livraison. De plus, la commande a pu subir des modifications, qui nĂ©anmoins n'ont pas affectĂ© le champ temps_de_livraison. Dans les deux cas, les modifications ne seront pas transmises de la source vers le rĂ©cepteur. Pour rĂ©soudre ces problĂšmes, nous devrons transmettre les donnĂ©es « en superposition ». C'est-Ă -dire qu'Ă chaque session d'Ă©change, nous allons transmettre toutes les donnĂ©es ayant une valeur de champ temps_de_livraison, dĂ©passant un certain moment dans le passĂ© (par exemple, N heures Ă partir du moment actuel). Toutefois, il est Ă©vident que pour les systĂšmes de grande taille, cette approche est considĂ©rablement redondante et peut annuler l'Ă©conomie de trafic que nous visons. En outre, il se peut qu'il n'y ait pas de champ associĂ© Ă la date-heure dans le tableau transmis.
Une autre solution, plus complexe en termes de mise en Ćuvre, consiste Ă confirmer la rĂ©ception des donnĂ©es. Dans ce cas, Ă chaque session d'Ă©change, toutes les donnĂ©es dont la rĂ©ception n'est pas confirmĂ©e par le rĂ©cepteur sont transmises. Pour cela, il sera nĂ©cessaire d'ajouter une colonne boolĂ©enne dans la table source (par exemple, est_transfĂ©rĂ©). Si le rĂ©cepteur confirme la rĂ©ception de l'enregistrement, le champ correspondant prend la valeur true, aprĂšs quoi l'enregistrement ne participe plus aux Ă©changes. Cette variante de mise en Ćuvre prĂ©sente les inconvĂ©nients suivants. Tout d'abord, il est nĂ©cessaire de gĂ©nĂ©rer et d'envoyer une confirmation pour chaque enregistrement transmis. Grosso modo, cela pourrait ĂȘtre comparable Ă un doublement de la quantitĂ© de donnĂ©es transmises et entraĂźner un doublement du nombre de tours aller-retour. DeuxiĂšmement, il n'est pas possible d'envoyer le mĂȘme enregistrement Ă plusieurs rĂ©cepteurs (le premier rĂ©cepteur ayant reçu l'enregistrement confirmera la rĂ©ception pour lui-mĂȘme et pour tous les autres).
Une mĂ©thode, dĂ©pourvue des dĂ©fauts mentionnĂ©s ci-dessus, consiste Ă ajouter une colonne dans la table transfĂ©rĂ©e pour suivre les modifications de ses lignes. Cette colonne peut avoir un type date-heure et doit ĂȘtre dĂ©finie/mise Ă jour par l'application Ă l'heure actuelle chaque fois qu'une entrĂ©e est ajoutĂ©e/modifiĂ©e (de maniĂšre atomique avec l'ajout/modification). Prenons par exemple la colonne update_time. En conservant la valeur maximale de ce champ pour les enregistrements transmis, nous pourrons commencer la prochaine session d'Ă©change Ă partir de cette valeur (sĂ©lectionner les enregistrements avec une valeur de champ update_time, supĂ©rieure Ă la valeur prĂ©cĂ©demment conservĂ©e). Le problĂšme liĂ© Ă la derniĂšre approche est que les modifications de donnĂ©es peuvent se produire en mode batch. Par consĂ©quent, les valeurs des champs dans la colonne update_time peuvent ne pas ĂȘtre uniques. Ainsi, cette colonne ne peut pas ĂȘtre utilisĂ©e pour une remise de donnĂ©es par lots (pagination). Pour la remise de donnĂ©es par lots, il sera nĂ©cessaire d'inventer des mĂ©canismes supplĂ©mentaires, qui, trĂšs probablement, auront une efficacitĂ© trĂšs faible (par exemple, extraire de la base de donnĂ©es tous les enregistrements avec une valeur update_time supĂ©rieure Ă un seuil donnĂ© et fournir un certain nombre d'enregistrements, Ă partir d'un certain dĂ©calage depuis le dĂ©but de l'Ă©chantillon).
Nous pouvons amĂ©liorer l'efficacitĂ© du transfert de donnĂ©es en perfectionnant lĂ©gĂšrement l'approche prĂ©cĂ©dente. Pour cela, nous utiliserons un type entier (long entier) comme valeur des champs de la colonne pour suivre les modifications. Appelons cette colonne row_ver. La valeur de ce champ doit encore ĂȘtre dĂ©finie/mise Ă jour chaque fois qu'un enregistrement est créé/modifiĂ©. Mais dans ce cas, le champ ne sera pas attribuĂ© Ă la date-heure actuelle, mais plutĂŽt Ă la valeur d'un certain compteur, augmentĂ©e de un. En consĂ©quence, la colonne row_ver contiendra des valeurs uniques et pourra ĂȘtre utilisĂ©e non seulement pour fournir un "delta" de donnĂ©es (donnĂ©es ajoutĂ©es/modifiĂ©es aprĂšs la fin de la session d'Ă©change prĂ©cĂ©dente), mais aussi pour une division simple et efficace en pages.
La derniĂšre mĂ©thode proposĂ©e pour minimiser la quantitĂ© de donnĂ©es transfĂ©rĂ©es dans le cadre de la rĂ©plication de haut niveau me semble la plus optimale et universelle. ArrĂȘtons-nous plus en dĂ©tail sur ce point.
Transfert de données utilisant un compteur de versions de lignes.
Mise en Ćuvre de la partie serveur/master
Dans MS SQL Server, il existe un type de colonne spĂ©cial pour mettre en Ćuvre une approche similaire â rowversion. Chaque base de donnĂ©es a un compteur qui s'incrĂ©mente Ă chaque ajout/modification d'enregistrement dans une table ayant une colonne de type rowversion. La valeur de ce compteur est automatiquement attribuĂ©e au champ de cette colonne dans l'enregistrement ajoutĂ©/modifiĂ©. La base de donnĂ©es Tarantool n'a pas de mĂ©canisme intĂ©grĂ© similaire. Cependant, il est facile de le mettre en Ćuvre manuellement dans Tarantool. Voyons comment faire.
Pour commencer, un peu de terminologie : les tables dans Tarantool sont appelées espaces (space), et les enregistrements sont des tuples (tuple). Dans Tarantool, on peut créer des séquences (sequence). Les séquences ne sont rien d'autre que des générateurs nommés de valeurs entiÚres ordonnées. C'est exactement ce dont nous avons besoin pour nos objectifs. Ci-dessous, nous allons créer une telle séquence.
Avant d'effectuer une opération sur la base de données dans Tarantool, il faut exécuter la commande suivante :
box.cfg{}à la suite de cela, Tarantool commencera à enregistrer des instantanés de la base de données (snapshot) et des journaux de transactions dans le répertoire actuel.
Créons 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 ne tentera pas de le créer à nouveau. Cette option sera utilisée dans toutes les prochaines commandes DDL.
Créons un espace pour l'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 avons défini le nom de l'espace (goods), les noms des champs et leurs types.
Les champs auto-incrémentés dans Tarantool sont également créés à l'aide de séquences. Créons une clé primaire auto-incrémentée basée sur 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. Les types d'index les plus couramment utilisés sont les index de type TREE et HASH, basés sur les structures correspondant à leur nom. TREE est le type d'index le plus polyvalent. Il permet d'extraire des données de maniÚre ordonnée. En revanche, pour les sélections par égalité, HASH est plus approprié. Par conséquent, il est judicieux d'utiliser HASH pour la clé primaire (ce que nous avons fait).
Pour utiliser la colonne row_ver pour transmettre les donnĂ©es modifiĂ©es, il est nĂ©cessaire de lier aux champs de cette colonne les valeurs de la sĂ©quence row_ver. Mais contrairement Ă la clĂ© primaire, la valeur du champ de la colonne row_ver doit augmenter d'une unitĂ© non seulement lors de l'ajout de nouveaux enregistrements, mais aussi lors de la modification des enregistrements existants. Pour cela, des dĂ©clencheurs peuvent ĂȘtre utilisĂ©s. Dans Tarantool, il existe deux types de dĂ©clencheurs pour les espaces : before_replace et on_replace. Les dĂ©clencheurs s'exĂ©cutent Ă chaque modification des donnĂ©es dans l'espace (pour chaque tuple affectĂ© par les modifications, la fonction du dĂ©clencheur est exĂ©cutĂ©e). Contrairement Ă on_replace, before_replace- les dĂ©clencheurs permettent de modifier les donnĂ©es du tuple pour lequel le dĂ©clencheur est exĂ©cutĂ©. Par consĂ©quent, 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) Le déclencheur fourni remplace la valeur du champ row_ver du tuple enregistré par la valeur suivante de la séquence row_version.
Pour pouvoir extraire des données de l'espace goods par la colonne row_ver, créons 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 devrons extraire les données par ordre croissant des valeurs de la colonne. row_ver.
Ajoutons quelques données dans 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} Comme le premier champ est un compteur auto-increment, nous passons nil Ă sa place. Tarantool substituera automatiquement la valeur suivante. De mĂȘme, pour les valeurs des champs de la colonne row_ver , il est possible de passer nil, ou de ne pas spĂ©cifier de valeur du tout, car cette colonne occupe la derniĂšre position dans l'espace.
Vérifions le résultat de l'insertion :
tarantool> box.space.goods:select()
---
- - [1, 'stylo', 123, 1]
- [2, 'crayon', 321, 2]
- [3, 'pinceau', 100, 3]
- [4, 'aquarelle', 456, 4]
- [5, 'album', 101, 5]
- [6, 'cahier', 800, 6]
- [7, 'gomme', 531, 7]
- [8, 'rĂšgle', 135, 8]
... Comme nous le voyons, le premier et le dernier champ se sont remplis automatiquement. Il sera donc facile d'écrire une fonction pour l'extraction par page des modifications de l'espace. 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 une valeur row_ver, à partir de laquelle il faut réaliser l'extraction des modifications, et renvoie un lot de données modifiées.
L'extraction des donnĂ©es dans Tarantool se fait par le biais d'index. La fonction get_goods utilise un itĂ©rateur sur l'index row_ver pour obtenir les donnĂ©es modifiĂ©es. Le type d'itĂ©rateur â GT (Greater Than, plus grand que). Cela signifie que l'itĂ©rateur effectuera un parcours sĂ©quentiel des valeurs de l'index Ă partir de la clĂ© (valeur du champ) transmise. row_ver).
L'itérateur renvoie des tuples. Pour pouvoir ensuite transmettre les données par HTTP, il est nécessaire de transformer les tuples en une structure qui soit pratique pour la sérialisation ultérieure. Dans l'exemple, on utilise la fonction standard tomap. Au lieu d'utiliser tomap vous pouvez écrire votre propre fonction. Par exemple, nous pouvons vouloir renommer le champ nom, ne pas transmettre le champ code et ajouter le champ comment:
local function unflatten_goods(tuple)
local obj = {}
obj.id = tuple.id
obj.goods_name = tuple.name
obj.comment = 'un commentaire'
obj.row_ver = tuple.row_ver
return obj
end La taille de la page des donnĂ©es renvoyĂ©es (nombre d'enregistrements par portion) est dĂ©finie par la variable page_size. Dans l'exemple, la valeur page_size Ă©gal Ă 5. Dans un programme rĂ©el, la taille de la page a gĂ©nĂ©ralement plus d'importance. Elle dĂ©pend de la taille moyenne des tuples de l'espace. La taille de la page optimale peut ĂȘtre dĂ©terminĂ©e empiriquement en mesurant le temps de transmission des donnĂ©es. Plus la taille de la page est grande, moins il y a de round-trips entre l'expĂ©diteur et le rĂ©cepteur. Cela peut rĂ©duire le temps total de dĂ©chargement des modifications. Cependant, si la taille de la page est trop grande, le serveur prendra trop de temps pour sĂ©rialiser la sĂ©lection. En consĂ©quence, des retards peuvent survenir lors du traitement d'autres requĂȘtes envoyĂ©es au serveur. Le paramĂštre page_size peut ĂȘtre chargĂ© Ă partir du fichier de configuration. Pour chaque espace transmis, une valeur propre peut ĂȘtre dĂ©finie. Cependant, pour la plupart des espaces, une valeur par dĂ©faut (par exemple, 100) peut convenir.
Exécutons la fonction get_goods:
tarantool> get_goods(0)
---
- - row_ver: 1
code: 123
name: stylo
id: 1
- row_ver: 2
code: 321
name: crayon
id: 2
- row_ver: 3
code: 100
name: pinceau
id: 3
- row_ver: 4
code: 456
name: aquarelle
id: 4
- row_ver: 5
code: 101
name: album
id: 5
... Prenons la valeur du champ row_ver de la derniĂšre ligne et rappelons la fonction :
tarantool> get_goods(5)
---
- - row_ver: 6
code: 800
name: carnet
id: 6
- row_ver: 7
code: 531
name: gomme
id: 7
- row_ver: 8
code: 135
name: rĂšgle
id: 8
...Et encore une fois :
tarantool> get_goods(8)
---
- []
... Comme nous le voyons, avec cette utilisation, la fonction retourne toutes les entrées de l'espace de maniÚre paginée goods. AprÚs la derniÚre page, il y a un résultat vide.
Faisons des modifications dans l'espace :
box.space.goods:update(4, {{'=', 6, 'carnet'}})
box.space.goods:insert{nil, 'clip', 234}
box.space.goods:insert{nil, 'dossier', 432} Nous avons modifié la valeur du champ nom pour une entrée et ajouté deux nouvelles entrées.
Répétons le dernier appel de fonction :
tarantool> get_goods(8)
---
- - row_ver: 9
code: 800
name: carnet
id: 6
- row_ver: 10
code: 234
name: clip
id: 9
- row_ver: 11
code: 432
name: dossier
id: 10
... La fonction a retourné les enregistrements modifiés et ajoutés. Ainsi, la fonction get_goods permet d'obtenir les données qui ont changé depuis son dernier appel, ce qui est la base de la méthode de réplication discutée.
Nous laisserons la sortie des résultats via HTTP au format JSON en dehors du cadre de cet article. Vous pouvez lire à ce sujet ici :
Implémentation de la partie client/slave
Voyons à quoi ressemble l'implémentation du cÎté récepteur. Créons, du cÎté récepteur, un espace pour stocker les données 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 à celle de l'espace dans la source. Mais comme nous ne prévoyons pas de transmettre les données reçues ailleurs, la colonne row_ver est absente dans l'espace du destinataire. Dans le champ id seront enregistrés les identifiants de la source. Par conséquent, du cÎté du récepteur, il n'est pas nécessaire de le rendre auto-incrémental.
En outre, nous aurons besoin d'un espace pour stocker 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 à charger (champ space_name) nous allons conserver ici la derniÚre valeur chargée row_ver (champ value). En tant que clé primaire, la colonne space_name.
Créons une fonction pour charger les données de l'espace goods via HTTP. Pour cela, nous aurons besoin d'une bibliothÚque réalisant un client HTTP. La ligne suivante charge la bibliothÚque et crée une instance du client HTTP :
local http_client = require('http.client').new()Nous aurons également besoin d'une bibliothÚque pour désérialiser le json :
local json = require('json')Cela suffit pour créer la 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 effectue une requĂȘte HTTP Ă l'adresse url, transmet le row_ver comme paramĂštre et retourne 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 La boucle de sauvegarde des données dans l'espace goods est placée dans une transaction (pour cela, la fonction box.atomic) est utilisée pour réduire le nombre d'opérations sur le disque.
Enfin, la fonction de synchronisation de l'espace local goods 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, lisons la valeur prĂ©cĂ©demment enregistrĂ©e row_ver pour l'espace goods. Si elle est absente (premiĂšre session d'Ă©change), nous prendrons comme row_ver zĂ©ro. Ensuite, dans la boucle, nous effectuons un chargement page par page des donnĂ©es modifiĂ©es Ă partir de la source selon l'URL spĂ©cifiĂ©e. Ă chaque itĂ©ration, nous sauvegardons les donnĂ©es reçues dans l'espace local correspondant et mettons Ă jour la valeur row_ver (dans l'espace 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 Ă©viter les boucles infinies accidentelles (en cas d'erreur dans le programme), la boucle while peut ĂȘtre remplacĂ©e par for:
for _ = 1, max_req do ... En conséquence de l'exécution de la fonction sync_goods l'espace goods dans le récepteur contiendra les derniÚres versions de toutes les entrées de l'espace goods dans la source.
Il est Ă©vident qu'avec cette mĂ©thode, il n'est pas possible de transcrire la suppression de donnĂ©es. Si cela s'avĂšre nĂ©cessaire, on peut utiliser une indication de suppression. Nous ajoutons Ă l'espace goods un champ boolĂ©en is_deleted et au lieu de la suppression physique de l'entrĂ©e, nous utilisons la suppression logique â nous dĂ©finissons la valeur du champ is_deleted Ă la valeur true. Parfois, au lieu d'un champ boolĂ©en, is_deleted il est plus pratique d'utiliser un champ supprimĂ©, qui contient la date et l'heure de la suppression logique de l'enregistrement. AprĂšs avoir effectuĂ© la 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 les donnĂ©es d'autres espaces : il n'est pas nĂ©cessaire de crĂ©er une sĂ©quence distincte pour chaque espace transfĂ©rĂ©.
Nous avons examiné une méthode efficace de réplication de données de haut niveau dans les applications utilisant la base de données Tarantool.
Conclusions
- La base de données Tarantool est un produit attrayant et prometteur pour la création d'applications à forte charge.
- La réplication de données de haut niveau présente plusieurs avantages par rapport à la réplication de bas niveau.
- La méthode de réplication de haut niveau examinée dans cet article permet de minimiser la quantité de données transférées en ne transmettant que les enregistrements qui ont changé depuis la derniÚre session d'échange.
Source : habr.com
