Hallo, ich entwickle Anwendungen für Datenbanken — ist eine von Mail.ru Group entwickelte Plattform, die eine hochperformante Datenbank und einen Anwendungsserver in der Programmiersprache Lua vereint. Die hohe Geschwindigkeit von auf Tarantool basierenden Lösungen wird insbesondere durch die Unterstützung des In-Memory-Datenbankmodus und die Möglichkeit, die Geschäftslogik der Anwendung im selben Adressraum wie die Daten auszuführen, erreicht. Dabei wird die Persistenz der Daten durch die Nutzung von ACID-Transaktionen sichergestellt (WAL-Log wird auf der Festplatte geführt). Tarantool bietet integrierte Unterstützung für Replikation und Sharding. Ab Version 2.1 werden Anfragen in SQL unterstützt. Tarantool hat einen offenen Quellcode und wird unter der Lizenz Simplified BSD vertrieben. Es existiert auch eine kommerzielle Enterprise-Version.

Spüre die Kraft! (…aka genieße die Leistung)
All dies macht Tarantool zu einer attraktiven Plattform für die Entwicklung hochbelasteter Anwendungen, die mit Datenbanken arbeiten. In solchen Anwendungen besteht häufig die Notwendigkeit der Datenreplikation.
Wie bereits erwähnt, gibt es in Tarantool eine eingebaute Datenreplikation. Ihr Prinzip besteht darin, dass auf den Replikaten alle Transaktionen, die im Master-Log (WAL) enthalten sind, sequenziell ausgeführt werden. Üblicherweise wird eine solche Replikation (die wir im Folgenden niedrigstufige) nennen werden, verwendet, um die Fehlertoleranz der Anwendung sicherzustellen und/oder die Leseauslastung zwischen den Knoten des Clusters zu verteilen.

Abb. 1. Replikation innerhalb des Clusters
Ein Beispiel für ein alternatives Szenario könnte die Übertragung von Daten sein, die in einer Datenbank erstellt wurden, in eine andere Datenbank zur Verarbeitung/Überwachung. In letztem Fall kann eine hochgradige Replikation — die Replikation von Daten auf der Ebene der Geschäftslogik der Anwendung — die angenehmere Lösung darstellen. D.h. wir nutzen nicht die eingebaute Lösung der Datenbank, sondern setzen die Replikation innerhalb der von uns entwickelten Anwendung selbst um. Dieser Ansatz hat sowohl Vor- als auch Nachteile. Lassen Sie uns die Vorteile aufzählen.
1. Einsparung von Datenverkehr:
- Es ist möglich, nicht alle Daten, sondern nur einen Teil davon zu übertragen (zum Beispiel können nur bestimmte Tabellen, einige ihrer Spalten oder Datensätze, die bestimmten Kriterien entsprechen, übertragen werden);
- Im Gegensatz zur niedertiefen Replikation, die kontinuierlich im asynchronen (in der aktuellen Version Tarantool - 1.10 implementiert) oder synchronen (wird in zukünftigen Versionen von Tarantool implementiert) Modus durchgeführt wird, kann die hochgradige Replikation in Sitzungen durchgeführt werden (d.h. die Anwendung führt zuerst die Datensynchronisation durch - Sitzung des Datenaustausches, dann folgt eine Pause in der Replikation, nach der die nächste Sitzung des Austausches stattfindet usw.);
- Wenn sich ein Datensatz mehrere Male geändert hat, kann nur die letzte Version übergeben werden (im Gegensatz zur niedertiefen Replikation, bei der alle Änderungen, die am Master vorgenommen worden sind, sequenziell auf den Replikaten abgespielt werden).
2. Es gibt keine Komplikationen bei der Implementierung des Austauschs über HTTP, was die Synchronisation entfernter Datenbanken ermöglicht.

Abb. 2. Replikation über HTTP
3. Die Datenbankstrukturen, zwischen denen die Daten übermittelt werden, müssen nicht identisch sein (es ist sogar in der Regel möglich, verschiedene DBMS, Programmiersprachen, Plattformen usw. zu verwenden).

Abb. 3. Replikation in heterogenen Systemen
Ein Nachteil ist, dass die Programmierung im Durchschnitt komplexer/aufwändiger ist als die Konfiguration, und anstelle der Einrichtung von eingebauter Funktionalität muss man seine eigene implementieren.
Wenn die genannten Vorteile in Ihrer Situation entscheidend sind (oder eine notwendige Bedingung darstellen), macht es Sinn, hochgradige Replikation zu nutzen. Lassen Sie uns einige Möglichkeiten zur Implementierung hochgradiger Datensynchronisation in der Datenbank Tarantool betrachten.
Minimierung des Datenverkehrs
Ein Vorteil der hochgradigen Replikation ist also die Einsparung von Datenverkehr. Damit dieses Vorteil zur Geltung kommt, müssen die bei jeder Sitzung des Datenaustausches übermittelten Daten minimiert werden. Dabei sollte jedoch nicht vergessen werden, dass am Ende der Sitzung der Datenempfänger mit der Quelle synchronisiert sein muss (mindestens hinsichtlich der Daten, die an der Replikation teilnehmen).
Wie kann die Menge der bei der hochgradigen Replikation übermittelten Daten minimiert werden? Eine „flächendeckende“ Lösung könnte die Auswahl der Daten nach Datum und Uhrzeit sein. Dazu kann das bereits vorhandene Datums- und Zeitfeld in der Tabelle verwendet werden (sofern vorhanden). Zum Beispiel könnte ein Dokument „Bestellung“ ein Feld für „benötigte Ausführungszeit der Bestellung“ haben - Lieferzeit. Das Problem dieser Lösung besteht darin, dass die Werte in diesem Feld nicht in einer Reihenfolge angeordnet sein müssen, die mit der Erstellung von Bestellungen übereinstimmt. Daher können wir den maximalen Wert des Feldes nicht speichern. Lieferzeit, der während der vorherigen Austauschsession übermittelt wurde, und bei der nächsten Austauschsession alle Datensätze mit einem höheren Wert des Feldes auswählen. Lieferzeit. Zwischen den Austauschsessions könnten Datensätze mit einem niedrigeren Wert des Feldes hinzugefügt worden sein. Lieferzeit. Außerdem könnte der Auftrag Änderungen unterzogen worden sein, die jedoch das Feld nicht betroffen haben. Lieferzeit. In beiden Fällen werden die Änderungen nicht vom Sender zum Empfänger übertragen. Um diese Probleme zu lösen, müssen wir die Daten "überlappend" übertragen. Das heißt, bei jeder Austauschsession werden wir alle Daten mit einem Wert des Feldes übermitteln, Lieferzeit, die einen bestimmten Zeitpunkt in der Vergangenheit überschreiten (zum Beispiel N Stunden von dem aktuellen Zeitpunkt). Es ist jedoch offensichtlich, dass dieser Ansatz für große Systeme erheblich übermäßig ist und die Einsparungen beim Datenverkehr, die wir anstreben, zunichte machen kann. Darüber hinaus könnte es in der übermittelten Tabelle kein feld geben, das mit dem Datum und der Uhrzeit verbunden ist.
Eine andere Lösung, die in Bezug auf die Implementierung komplexer ist, besteht darin, den Erhalt der Daten zu bestätigen. In diesem Fall werden bei jeder Austauschsession alle Daten übermittelt, deren Erhalt nicht vom Empfänger bestätigt wurde. Zur Implementierung muss eine boolesche Spalte in die Ursprungstabelle eingefügt werden (zum Beispiel, is_transferred). Wenn der Empfänger den Erhalt des Datensatzes bestätigt, nimmt das entsprechende Feld den Wert true, nach dem die Datensatz nicht mehr an den Austausch beteiligt ist. Diese Implementierungsvariante weist folgende Nachteile auf. Erstens muss für jeden übermittelten Datensatz eine Bestätigung erstellt und gesendet werden. Grob gesagt kann dies mit einer Verdopplung der übermittelten Daten und einer Verdopplung der Anzahl der Roundtrips vergleichbar sein. Zweitens besteht keine Möglichkeit, denselben Datensatz an mehrere Empfänger zu senden (der erste empfangende Empfänger bestätigt den Erhalt für sich und für alle anderen).
Die Methode, die frei von den oben genannten Mängeln ist, besteht darin, der übermittelten Tabelle eine Spalte zur Verfolgung von Änderungen ihrer Zeilen hinzuzufügen. Eine solche Spalte kann vom Datentyp Datum-Zeit sein und sollte von der Anwendung jedes Mal auf die aktuelle Zeit gesetzt oder aktualisiert werden, wenn Datensätze hinzugefügt oder geändert werden (atomar bei Hinzufügungen/Änderungen). Als Beispiel nennen wir die Spalte update_time. Wenn wir den maximalen Wert dieses Spaltenfelds für die übermittelten Datensätze speichern, können wir die nächste Austauschsession von diesem Wert aus starten (Datensätze mit einem Feldwert auszuwählen, der update_time, über dem zuvor gespeicherten Wert liegt). Das Problem mit diesem Ansatz besteht darin, dass Datenänderungen im Batch-Modus erfolgen können. Infolgedessen können die Werte in der Spalte update_time nicht eindeutig sein. Daher kann diese Spalte nicht für die paginierte Ausgabe von Daten verwendet werden. Für die paginierte Ausgabe von Daten müssen zusätzliche Mechanismen erfunden werden, die wahrscheinlich sehr ineffizient sein werden (zum Beispiel das Abrufen aller Datensätze aus der Datenbank mit einem Wert update_time , der über einem festgelegten Wert liegt, und die Ausgabe einer bestimmten Anzahl von Datensätzen, beginnend mit einem bestimmten Offset vom Anfang der Auswahl).
Die Effizienz der Datenübertragung kann gesteigert werden, indem der vorherige Ansatz leicht verbessert wird. Dazu verwenden wir für die Werte der Spaltenfelder zur Verfolgung von Änderungen einen ganzzahligen Typ (Long). Nennen wir die Spalte row_ver. Der Wert des Feldes dieser Spalte sollte weiterhin jedes Mal beim Erstellen oder Ändern eines Datensatzes gesetzt oder aktualisiert werden. In diesem Fall wird jedoch dem Feld nicht die aktuelle Datum-Zeit zugewiesen, sondern der Wert eines Zählers, der um eins erhöht wird. Infolgedessen wird die Spalte row_ver eindeutige Werte enthalten und kann nicht nur zur Ausgabe von "Delta"-Daten (Daten, die nach Abschluss der vorherigen Austauschsession hinzugefügt oder geändert wurden) verwendet werden, sondern auch zur einfachen und effizienten Paginierung.
Die zuletzt vorgeschlagene Methode zur Minimierung der Menge an Daten, die im Rahmen der hochgradigen Replikation übertragen werden, erscheint mir als die optimalste und universellste. Lassen Sie uns näher darauf eingehen.
Datenübertragung mit einem Zeilenzähler verwenden
Implementierung des Server-/Masterteils
In MS SQL Server gibt es für die Realisierung eines solchen Ansatzes einen speziellen Spaltentyp – rowversion. Jede DB hat einen Zähler, der bei jeder Hinzufügung/Änderung eines Datensatzes in einer Tabelle mit einer Spalte vom Typ rowversionum eins erhöht wird. Der Wert dieses Zählers wird automatisch dem Feld dieser Spalte im hinzugefügten/geänderten Datensatz zugewiesen. Die DBMS Tarantool hat keinen entsprechenden eingebauten Mechanismus. In Tarantool lässt es sich jedoch einfach manuell umsetzen. Lassen Sie uns ansehen, wie das funktioniert.
Zunächst einige Begriffe: Tabellen in Tarantool heißen Spaces (space) und Datensätze – Tupel (tuple). In Tarantool können Sequenzen (sequence) erstellt werden. Sequenzen sind nichts anderes als benannte Generatoren für sortierte Werte ganzer Zahlen. Das heißt, genau das, was wir für unsere Zwecke brauchen. Unten werden wir eine solche Sequenz erstellen.
Bevor Sie eine Datenbankoperation in Tarantool durchführen, müssen Sie den folgenden Befehl ausführen:
box.cfg{}Infolgedessen beginnt Tarantool, Schnappschüsse der DB (snapshot) und ein Transaktionsprotokoll im aktuellen Verzeichnis zu speichern.
Lassen Sie uns eine Sequenz erstellen row_version:
box.schema.sequence.create('row_version',
{ if_not_exists = true }) Option if_not_exists ermöglicht es, das Erstellungsskript mehrfach auszuführen: Wenn das Objekt existiert, versucht Tarantool nicht, es erneut zu erstellen. Diese Option wird in allen nachfolgenden DDL-Befehlen verwendet.
Erstellen wir einen Space zum Beispiel.
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
}) Hier haben wir den Namen des Spaces festgelegt (goods), die Feldnamen und deren Typen.
Autoinkrementale Felder in Tarantool werden ebenfalls mit Hilfe von Sequenzen erstellt. Lassen Sie uns einen autoinkrementalen Primärschlüssel für das Feld 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 unterstützt mehrere Indexarten. Am häufigsten werden die Indexarten TREE und HASH verwendet, die auf den entsprechenden Strukturen basieren. TREE ist der vielseitigste Indextyp. Er ermöglicht das Abrufen von Daten in geordneter Form. Für die Gleichheitsabfrage ist HASH jedoch besser geeignet. Dementsprechend ist es sinnvoll, für den Primärschlüssel HASH zu verwenden (was wir auch getan haben).
Um eine Spalte zu verwenden row_ver für die Übertragung der geänderten Daten, müssen die Werte einer Sequenz an die Felder dieser Spalte gebunden werden row_ver. Im Gegensatz zum Primärschlüssel muss der Wert des Spaltenfeldes row_ver bei der Hinzufügung neuer Datensätze und auch bei der Änderung vorhandener Datensätze um eins erhöht werden. Dazu können Trigger verwendet werden. In Tarantool gibt es zwei Typen von Triggern für Spaces: before_replace und on_replace. Trigger werden bei jeder Datenänderung im Space ausgelöst (für jedes Tuple, das von Änderungen betroffen ist, wird die Triggerfunktion aufgerufen). Im Gegensatz zu on_replace, before_replace-Triggern ermöglichen es, die Daten des Tuples, auf das der Trigger angewendet wird, zu modifizieren. Daher eignet sich für uns der letzte Typ der Trigger.
box.space.goods:before_replace(function(old, new)
return box.tuple.new({new[1], new[2], new[3],
box.sequence.row_version:next()})
end) Der angegebene Trigger ersetzt den Wert des Feldes row_ver des gespeicherten Tuples durch den nächsten Wert der Sequenz row_version.
Um Daten aus dem Space goods nach der Spalte row_verabzufragen, erstellen wir einen Index:
box.space.goods:create_index('row_ver', {
parts = { 'row_ver' },
unique = true,
type = 'TREE',
if_not_exists = true
}) Der Typ des Indexes ist Baum (TREE), da wir die Daten in aufsteigender Reihenfolge der Werte in der Spalte abrufen müssen. row_ver.
Fügen wir einige Daten in den Space ein:
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} Da das erste Feld ein Auto-Inkrement-Zähler ist, übergeben wir anstelle dessen nil. Tarantool setzt automatisch den nächsten Wert ein. Ebenso können für die Werte der Spaltenfelder row_ver nil übergeben werden — oder der Wert muss überhaupt nicht angegeben werden, da diese Spalte die letzte Position im Space einnimmt.
Überprüfen wir das Ergebnis der Einfügung:
tarantool> box.space.goods:select()
---
- - [1, 'Stift', 123, 1]
- [2, 'Bleistift', 321, 2]
- [3, 'Pinsel', 100, 3]
- [4, 'Aquarell', 456, 4]
- [5, 'Album', 101, 5]
- [6, 'Notizbuch', 800, 6]
- [7, 'Radiergummi', 531, 7]
- [8, 'Lineal', 135, 8]
... Wie wir sehen, wurden das erste und das letzte Feld automatisch ausgefüllt. Jetzt wird es nicht schwer sein, eine Funktion für die seitenweise Abfrage der Änderungen im Space zu schreiben. 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 Die Funktion akzeptiert einen Parameter, der den Wert angibt, row_ver, ab dem die Änderungen abgerufen werden sollen, und gibt einen Satz geänderter Daten zurück.
Die Datenauswahl in Tarantool erfolgt über Indizes. Die Funktion get_goods verwendet einen Iterator über den Index row_ver um die geänderten Daten abzurufen. Der Typ des Iterators ist GT (Greater Than, größer als). Das bedeutet, dass der Iterator eine sequenzielle Durchlauf des Indexwertes ab dem übergebenen Schlüssel (Wert des Feldes row_ver).
Durch den Iterator werden Tupel zurückgegeben. Um später die Daten über HTTP übertragen zu können, ist es erforderlich, die Tupel in eine für die spätere Serialisierung geeignete Struktur umzuwandeln. Im Beispiel wird hierfür die Standardfunktion tomapverwendet. Anstelle der Verwendung tomap kann eine eigene Funktion geschrieben werden. Zum Beispiel können wir das Feld umbenennen, name, das Feld code nicht übergeben und das Feld:
comment local function unflatten_goods(tuple) local obj = {} obj.id = tuple.id obj.goods_name = tuple.name obj.comment = 'ein Kommentar' obj.row_ver = tuple.row_ver return obj end Die Größe der Seite der ausgegebenen Daten (die Anzahl der Datensätze in einem Satz) wird durch die Variablepage_size Die Größe der Seite der ausgegebenen Daten (die Anzahl der Datensätze in einem Satz) wird durch die Variable Gleich 5. In einem echten Programm hat die Seitengröße normalerweise eine größere Bedeutung. Diese hängt von der durchschnittlichen Größe des Tuples im Space ab. Die optimale Seitengröße kann experimentell bestimmt werden, indem die Übertragungszeiten gemessen werden. Je größer die Seitengröße, desto weniger Roundtrips gibt es zwischen der sendenden und der empfangenden Seite. So kann die Gesamtdauer des Änderungs-Uploads reduziert werden. Bei zu großer Seitengröße kann es jedoch dazu kommen, dass der Server zu lange mit der Serialisierung der Auswahl beschäftigt ist. Infolgedessen kann es zu Verzögerungen bei der Bearbeitung anderer Anfragen auf dem Server kommen. Der Parameter Die Größe der Seite der ausgegebenen Daten (die Anzahl der Datensätze in einem Satz) wird durch die Variable kann aus der Konfigurationsdatei geladen werden. Für jeden übermittelten Space kann ein eigener Wert angegeben werden. Für die meisten Spaces ist der Standardwert (zum Beispiel 100) grundsätzlich geeignet.
Führen wir die Funktion get_goods:
tarantool> get_goods(0)
---
- - row_ver: 1
code: 123
name: Stift
id: 1
- row_ver: 2
code: 321
name: Bleistift
id: 2
- row_ver: 3
code: 100
name: Pinsel
id: 3
- row_ver: 4
code: 456
name: Aquarellfarben
id: 4
- row_ver: 5
code: 101
name: Album
id: 5
... Nehmen wir den Wert des Feldes row_ver aus der letzten Zeile und rufen die Funktion erneut auf:
tarantool> get_goods(5)
---
- - row_ver: 6
code: 800
name: Notizbuch
id: 6
- row_ver: 7
code: 531
name: Radiergummi
id: 7
- row_ver: 8
code: 135
name: Lineal
id: 8
...Und noch einmal:
tarantool> get_goods(8)
---
- []
... Wie wir sehen, gibt die Funktion bei dieser Verwendung alle Datensätze im Space seitenweise zurück. goodsNach der letzten Seite folgt eine leere Auswahl.
Ändern wir den Space:
box.space.goods:update(4, {{'=', 6, 'Sammelheft'}})
box.space.goods:insert{nil, 'Heftklammer', 234}
box.space.goods:insert{nil, 'Ordner', 432} Wir haben den Wert des Feldes name für einen Datensatz geändert und zwei neue Datensätze hinzugefügt.
Lassen Sie uns den letzten Funktionsaufruf wiederholen:
tarantool> get_goods(8)
---
- - row_ver: 9
code: 800
name: Sammelheft
id: 6
- row_ver: 10
code: 234
name: Heftklammer
id: 9
- row_ver: 11
code: 432
name: Ordner
id: 10
... Die Funktion hat die geänderten und neu hinzugefügten Datensätze zurückgegeben. Somit erlaubt die Funktion get_goods das Abrufen von Daten, die sich seit ihrem letzten Aufruf geändert haben, was die Grundlage des betrachteten Replikationsverfahrens bildet.
Die Ausgabe der Ergebnisse über HTTP im JSON-Format bleibt in diesem Artikel unberücksichtigt. Dazu kann hier gelesen werden:
Implementierung des Client-/Slave-Teils
Betrachten wir, wie die Implementierung der empfangenden Seite aussieht. Erstellen wir auf der empfangenden Seite einen Space zur Speicherung der hochgeladenen Daten:
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
}) Die Struktur des Spaces ähnelt der Struktur des Ausgangs-Space. Da wir die erhaltenen Daten jedoch nicht irgendwoanders übertragen, fehlt die Spalte row_ver im Empfängerspace. Im Feld id werden die Identifikatoren aus der Quelle gespeichert. Daher ist es auf der Seite des Empfängers nicht erforderlich, es automatisch zu inkrementieren.
Darüber hinaus benötigen wir einen Space zur Speicherung der Werte 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
}) Für jeden geladenen Space (Feld space_name) werden wir hier den zuletzt geladenen Wert speichern row_ver (Feld value). Die Spalte space_name.
wird als Primärschlüssel verwendet. goods Wir erstellen eine Funktion zum Laden von Daten aus dem Space
über HTTP. Dazu benötigen wir eine Bibliothek, die einen HTTP-Client implementiert. Die folgende Zeile lädt die Bibliothek und erstellt eine Instanz des HTTP-Clients:local http_client = require('http.client').new()
Außerdem benötigen wir eine Bibliothek zur Deserialisierung von JSON:local json = require('json')
Das ist ausreichend, um die Funktion zum Laden von Daten zu erstellen: 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 row_ver Die Funktion führt eine HTTP-Anfrage an die URL aus, übergibt sie als Parameter und gibt das deserialisierte Ergebnis der Anfrage zurück.
Die Funktion zum Speichern der erhaltenen Daten sieht wie folgt aus:
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 Die Schleife zum Speichern der Daten im Space goods ist in einer Transaktion (dazu wird die Funktion box.atomic) untergebracht, um die Anzahl der Festplattenschreibvorgänge zu reduzieren.
Schließlich kann die Funktion zur Synchronisierung des lokalen Spaces goods mit der Quelle so realisiert werden:
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 Zuerst lesen wir den zuvor gespeicherten Wert row_ver für den Space goods. Falls dieser fehlt (beim ersten Austausch), nehmen wir ihn als row_ver Null. Weiter im Zyklus laden wir die geänderten Daten seitenweise aus der Quelle über die angegebene URL. Bei jeder Iteration speichern wir die erhaltenen Daten im entsprechenden lokalen Space und aktualisieren den Wert. row_ver (im Space row_ver und in der Variablen row_ver) — wir nehmen den Wert row_ver aus der letzten Zeile der geladenen Daten.
Um uns vor versehentlichem Endlosschleifen (im Falle eines Fehlers im Programm) zu schützen, kann die Schleife while durch for:
for _ = 1, max_req do ... Als Ergebnis der Ausführung der Funktion sync_goods Space goods im Empfänger wird die neuesten Versionen aller Datensätze des Spaces goods in der Quelle enthalten.
Offensichtlich ist es auf diese Weise nicht möglich, die Löschung von Daten zu übertragen. Wenn eine solche Notwendigkeit besteht, kann ein Löschmarker verwendet werden. Wir fügen dem Space goods ein boolesches Feld is_deleted hinzu und verwenden anstelle der physischen Löschung des Datensatzes die logische Löschung — wir setzen den Wert des Feldes is_deleted auf den Wert true. Manchmal ist es anstelle des booleschen Feldes is_deleted praktischer, das Feld zu verwenden, in dem das Datum und die Uhrzeit der logischen Löschung des Eintrags gespeichert sind. Nach der logischen Löschung wird der markierte Eintrag von der Quelle in den Empfänger übertragen (entsprechend der oben beschriebenen Logik)., in dem das Datum und die Uhrzeit der logischen Löschung des Datensatzes gespeichert sind, zu verwenden. Nach der logischen Löschung wird der markierte Datensatz aus der Quelle an den Empfänger übertragen (gemäß der oben genannten Logik).
Die Sequenz row_ver kann zur Übertragung von Daten anderer Spaces verwendet werden: Es besteht keine Notwendigkeit, für jeden übertragenen Space eine separate Sequenz zu erstellen.
Wir haben einen effektiven Weg der hochgradigen Datenreplikation in Anwendungen, die die DBMS Tarantool verwenden, betrachtet.
Das DBMS Tarantool ist ein attraktives, zukunftsträchtiges Produkt zur Erstellung von hochbelasteten Anwendungen.
- Die DBMS Tarantool ist ein attraktives, vielversprechendes Produkt zur Erstellung von hochbelasteten Anwendungen.
- Die hochgradige Replikation von Daten bietet eine Reihe von Vorteilen im Vergleich zur niedriggradigen Replikation.
- Der in dem Artikel behandelte Weg der hochgradigen Replikation ermöglicht es, die übertragene Datenmenge zu minimieren, indem nur die Datensätze übertragen werden, die sich nach dem letzten Austausch geändert haben.
Quelle: habr.com
