Hög nivÄ av replikation i databashanteraren Tarantool

Hej, jag arbetar med att skapa applikationer för databashanteringssystem. Tarantool — det Ă€r en plattform utvecklad av Mail.ru Group som kombinerar en högpresterande databashanteringssystem med en applikationsserver skriven i Lua. Den höga hastigheten hos lösningar baserade pĂ„ Tarantool uppnĂ„s bland annat genom stödet för in-memory-databashantering och möjligheten att köra affĂ€rslogik i samma adressutrymme som data. Samtidigt garanteras datapersistering med hjĂ€lp av ACID-transaktioner (WAL-loggning hĂ„lls pĂ„ disk). Tarantool har inbyggt stöd för replikering och sharding. FrĂ„n och med version 2.1 stöds SQL-frĂ„gor. Tarantool har öppen kĂ€llkod och distribueras under en förenklad BSD-licens. Det finns ocksĂ„ en kommersiell Enterprise-version.

Hög nivÄ av replikation i databashanteraren Tarantool
KĂ€nn kraften! (
akkurat njut av prestandan)

Allt detta gör Tarantool till en attraktiv plattform för att skapa högbelastade applikationer som arbetar med databaser. I sÄdana applikationer uppstÄr ofta behovet av datoreplikering.

Som nÀmnts ovan har Tarantool inbyggd datoreplikering. Principen för dess arbete Àr att alla transaktioner som finns i masterlogg (WAL) utförs sekventiellt pÄ replikerna. Vanligtvis kallas en sÄdan replikering för lÄgnivÄ) och anvÀnds för att sÀkerstÀlla applikationens motstÄndskraft och/eller för att fördela lÀsbelastningen mellan klusternoder.

Hög nivÄ av replikation i databashanteraren Tarantool
Fig. 1. Replikering inom klustret

Ett exempel pĂ„ ett alternativt scenario kan vara överföring av data skapade i en databas till en annan databas för bearbetning/övervakning. I det senare fallet kan det vara mer praktiskt att anvĂ€nda högnivĂ„ replikering — datoreplikering pĂ„ affĂ€rslogikens nivĂ„ i applikationen. Det vill sĂ€ga, vi anvĂ€nder inte en fĂ€rdig lösning inbyggd i databashanteringssystemet, utan implementerar replikering i den applikation vi utvecklar sjĂ€lva. Denna metod har bĂ„de fördelar och nackdelar. LĂ„t oss lista fördelarna.

1. Minska trafik:

  • du kan överföra inte alla data, utan bara en del av dem (till exempel kan du överföra endast vissa tabeller, vissa av deras kolumner eller poster som uppfyller vissa kriterier);
  • till skillnad frĂ„n lĂ„gnivĂ„replikation som kontinuerligt körs i asynkront (implementerat i nuvarande version av Tarantool - 1.10) eller synkront (kommer att implementeras i kommande versioner av Tarantool) lĂ€ge, kan högnivĂ„replikation utföras i sessioner (dvs. applikationen synkroniserar först data - en databyte-session, sedan följer ett uppehĂ„ll i replikationen, efter vilket nĂ€sta databyte-session Ă€ger rum, och sĂ„ vidare);
  • om posten har Ă€ndrats flera gĂ„nger kan endast den senaste versionen av den överföras (till skillnad frĂ„n lĂ„gnivĂ„replikation dĂ€r alla Ă€ndringar som gjorts pĂ„ master kommer att spelas upp i ordning pĂ„ replikerna).

2. Inga svÄrigheter med att implementera överföring via HTTP, vilket möjliggör synkronisering av fjÀrr-DB.

Hög nivÄ av replikation i databashanteraren Tarantool
Fig. 2. Replikation via HTTP

3. Databastrukturerna mellan vilka data överförs behöver inte vara identiska (faktiskt Àr det i allmÀnhet möjligt att till och med anvÀnda olika DBS, programmeringssprÄk, plattformar etc.).

Hög nivÄ av replikation i databashanteraren Tarantool
Fig. 3. Replikation i heterogena system

Nackdelen Àr att programmering i genomsnitt Àr svÄrare/kostsammare Àn konfiguration, och istÀllet för att stÀlla in inbyggd funktionalitet mÄste man implementera sin egen.

Om fördelarna som anges i din situation Àr avgörande (eller Àr nödvÀndiga villkor), kan det vara vÀrt att anvÀnda högnivÄreplikation. LÄt oss övervÀga flera sÀtt att implementera högnivÄreplikation av data i Tarantool DB.

Minimering av trafik

SÄ ett av fördelarna med högnivÄreplikation Àr att spara trafik. För att detta fördel ska kunna realiseras fullt ut mÄste mÀngden data som överförs vid varje databyte-session minimeras. SjÀlvklart Àr det viktigt att komma ihÄg att vid slutet av sessionen mÄste datamottagaren vara synkroniserad med kÀllan (Ätminstone för den del av data som Àr involverad i replikationen).

Hur kan man minimera mÀngden data som överförs vid högnivÄreplikation? En direkt lösning kan vara att vÀlja data baserat pÄ datum-tid. För detta kan ett befintligt databasfÀlt med datum-tid anvÀndas (om det finns). Till exempel kan ett "order"-dokument ha ett fÀlt för "önskad leveranstid" - leveranstid. Problemet med denna lösning Àr att vÀrdena i detta fÀlt inte nödvÀndigtvis behöver vara i den ordning de bestÀllningar skapades. SÄledes kan vi inte komma ihÄg det maximala vÀrdet för fÀltet leveranstid, som överfördes vid föregÄende utbytestillfÀlle, och vid nÀsta utbytestillfÀlle vÀlja alla poster med ett högre vÀrde för fÀltet leveranstid. Under tiden mellan utbytestillfÀllen kan poster med ett lÀgre vÀrde ha lagts till leveranstid. Dessutom kan bestÀllningen ha Àndrats utan att pÄverka fÀltet leveranstid. I bÄda fallen kommer Àndringarna inte att överföras frÄn kÀllan till mottagaren. För att lösa dessa problem behöver vi överföra data "överlappar". Det vill sÀga, vid varje utbytestillfÀlle kommer vi att överföra all data med ett vÀrde för fÀltet leveranstid, som överstiger en viss punkt i det förflutna (till exempel N timmar frÄn nu). Men det Àr uppenbart att denna metod Àr starkt överflödig för stora system och kan upphÀva den trafikbesparing vi strÀvar efter. Dessutom kanske det inte finns nÄgot fÀlt kopplat till datum och tid i den överförda tabellen.

En annan lösning, som Àr mer komplex ur implementeringssynpunkt, Àr att bekrÀfta mottagandet av data. I det hÀr fallet överförs all data vid varje utbytestillfÀlle som inte har bekrÀftats av mottagaren. För att genomföra detta krÀvs det att vi lÀgger till en boolean-kolumn i kÀlltabellen (till exempel, is_transferred). Om mottagaren bekrÀftar mottagandet av posten, kommer det aktuella fÀltet att anta vÀrdet true, varefter posten inte lÀngre deltar i utbyten. Denna implementeringsvariant har följande nackdelar. För det första, för varje överförd post mÄste ett bekrÀftelse skapas och skickas. Grovt sett kan detta jÀmstÀllas med att dubba mÀngden överförda data och leda till en fördubbling av antalet rundresor. För det andra, det saknas möjlighet att skicka samma post till flera mottagare (den första mottagaren som fÄtt posten bekrÀftar mottagandet för sig sjÀlv och för alla andra).

En metod utan de ovan nÀmnda bristerna bestÄr av att lÀgga till en kolumn i den överförda tabellen för att spÄra Àndringar i dess rader. Denna kolumn kan ha datetime-typ och bör sÀttas/uppdateras av applikationen till den aktuella tiden varje gÄng en post lÀggs till/Àndras (atomÀrt med tillÀgg/Àndring). LÄt oss kalla kolumnen update_time. Genom att spara det maximala vÀrdet av detta kolumnfÀlt för de överförda posterna kan vi börja nÀsta utbytespass frÄn detta vÀrde (vÀlj poster med vÀrdet i fÀltet update_time, som överstiger det tidigare sparade vÀrdet). Problemet med den senaste metoden Àr att datamodifikationer kan ske i paketform. Som ett resultat kan vÀrdena i fÀltet i kolumnen update_time inte vara unika. DÀrför kan denna kolumn inte anvÀndas för partiell (sidan för sida) datautdelning. För sidvis utdelning av data mÄste ytterligare mekanismer uppfinnas, som troligen kommer att ha mycket lÄg effektivitet (till exempel att hÀmta alla poster frÄn databasen med ett vÀrde update_time över ett visst angivet och ge ett visst antal poster, med början frÄn en viss förskjutning frÄn början av urvalet).

Effektiviteten av dataöverföringen kan ökas nÄgot genom att förbÀttra den föregÄende metoden. För detta syfte kommer vi att anvÀnda heltalsformat (long integer) som vÀrden i fÀltet för att spÄra Àndringar. Vi kallar kolumnen row_ver. VÀrdet i detta kolumnfÀlt bör fortfarande anges/uppdateras varje gÄng en post skapas/Àndras. Men i det hÀr fallet kommer fÀltet att tilldelas ett vÀrde av en rÀknare som ökas med ett. Som ett resultat kommer kolumnen row_ver att innehÄlla unika vÀrden och kan anvÀndas inte bara för att ge "delta" data (data som lagts till/Àndrats efter att det föregÄende utbytet avslutats), utan Àven för en enkel och effektiv uppdelning av dessa i sidor.

Den sista föreslagna metoden för att minimera mÀngden data som överförs inom högre nivÄ av replikering framstÄr som den mest optimala och allsidiga. LÄt oss stanna vid den lite mer detaljerat.

Överföring av data med hjĂ€lp av en radversionsrĂ€knare

Implementering av server-/masterdel

I MS SQL Server finns det en speciell kolumntyp för att genomföra en liknande metod - rowversion. Varje databas har en rÀknare som ökar med ett varje gÄng en post lÀggs till/Àndras i en tabell som har en kolumn av typen rowversion. VÀrdet av denna rÀknare tilldelas automatiskt fÀltet i den nyaddedda/Àndrade posten. DBMS Tarantool har ingen liknande inbyggd mekanism. Emellertid kan det enkelt implementeras manuellt i Tarantool. LÄt oss se hur det görs.

Först lite terminologi: tabeller i Tarantool kallas utrymmen (space), och poster kallas tupler (tuple). I Tarantool kan man skapa sekvenser (sequence). Sekvenser Àr inget annat Àn namngivna generatorer av ordnade heltalsvÀrden. Det vill sÀga, det Àr precis vad vi behöver för vÄra syften. Nedan kommer vi att skapa en sÄdan sekvens.

Innan nÄgon operation med databasen i Tarantool kan utföras, mÄste följande kommando köras:

box.cfg{}

Som ett resultat kommer Tarantool att börja spara databasens snapshots och transaktionslogg i den aktuella katalogen.

LÄt oss skapa en sekvens row_version:

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

Alternativ if_not_exists möjliggör att skapa skriptet flera gÄnger: om objektet finns, kommer Tarantool inte att försöka skapa det igen. Detta alternativ kommer att anvÀndas i alla kommande DDL-kommandon.

LÄt oss skapa ett utrymme som exempel.

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
})

HÀr har vi angett namnet pÄ utrymmet (goods), fÀltnamn och deras typer.

Autoincrement-fÀlt i Tarantool skapas ocksÄ med hjÀlp av sekvenser. LÄt oss skapa en autoincrement primÀrnyckel pÄ fÀltet 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 stöder flera typer av index. Vanligast anvÀnds index av typerna TREE och HASH, baserade pÄ deras respektive struktur. TREE Àr den mest mÄngsidiga typen av index. Den gör det möjligt att hÀmta data i ordnad form. För att vÀlja pÄ lika vÀrde passar dÀremot HASH bÀttre. DÀrför Àr det lÀmpligt att anvÀnda HASH för primÀrnyckeln (vilket vi har gjort).

För att anvÀnda en kolumn row_ver för att överföra Àndrade data, mÄste vÀrdena i sekvensen kopplas till fÀlten i denna kolumn row_ver. Men till skillnad frÄn primÀrnyckeln mÄste vÀrdet i kolumnens fÀlt row_ver öka med ett enhet, inte bara vid tillÀgg av nya poster, utan ocksÄ vid Àndring av befintliga. För detta kan man anvÀnda triggers. I Tarantool finns det tvÄ typer av triggers för spaces: before_replace och on_replace. Triggers aktiveras vid varje databasÀndring i space (för varje tuple som berörs av Àndringar aktiveras triggerfunktionen). Till skillnad frÄn on_replace, before_replace-triggers tillÄter att modifiera data i tuple som trigget utförs för. DÀrför passar den sista typen av triggers oss.

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

Den angivna triggern ersÀtter vÀrdet i fÀltet row_ver i den bevarade tupeln med nÀsta vÀrde frÄn sekvensen row_version.

För att kunna hÀmta data frÄn space goods baserat pÄ kolumnen row_ver, skapar vi ett index:

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

Typ av index Àr trÀd (TREE), eftersom vi behöver hÀmta data i stigande ordning av vÀrden i kolumnen. row_ver.

LÄt oss lÀgga till nÄgra data i space:

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}

Eftersom det första fÀltet Àr en autoinkrementerande rÀknare, skickar vi nil istÀllet för det. Tarantool sÀtter automatiskt in nÀsta vÀrde. PÄ samma sÀtt kan man ange nil för kolumnens fÀlt row_ver eller helt enkelt utelÀmna vÀrdet, eftersom denna kolumn Àr den sista i space.

LÄt oss kontrollera resultatet av insÀttningen:

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]
...

Som vi ser, har det första och sista fÀltet fyllts i automatiskt. Nu blir det inte svÄrt att skriva en funktion för paginering av Àndringar i rymden. 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

Funktionen tar ett parametervÀrde row_ver, vilket anger frÄn vilket vÀrde Àndringarna ska hÀmtas, och returnerar en portion av de Àndrade uppgifterna.

Dataval Àr i Tarantool baserat pÄ index. Funktionen get_goods anvÀnder en iterator över indexet row_ver för att hÀmta de Àndrade uppgifterna. Typen av iteratorn Àr GT (Större Àn). Detta innebÀr att iteratorn kommer att gÄ igenom indexvÀrdena startande frÄn den angivna nyckeln (vÀrdet av fÀltet row_ver).

Iteratorn returnerar tuples. För att kunna överföra data via HTTP senare, mÄste tupler konverteras till en struktur som Àr bekvÀm för efterföljande serialisering. I det hÀr exemplet anvÀnds standardfunktionen tomap. IstÀllet för att anvÀnda tomap kan man skriva en egen funktion. Till exempel kan vi vilja döpa om fÀltet name, inte skicka fÀltet code och lÀgga till fÀltet comment:

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

Storleken pÄ sidan av de returnerade uppgifterna (antal poster per portion) definieras av variabeln page_size. I exemplet Àr vÀrdet page_size Det Àr lika med 5. I ett riktigt program har sidstorleken vanligtvis större betydelse. Den beror pÄ den genomsnittliga storleken av ett tuple-space. Den optimala sidstorleken kan bestÀmmas empiriskt genom att mÀta dataöverföringstiden. Ju större sidstorlek, desto fÀrre rundresor mellan avsÀndaren och mottagaren. Detta kan minska den totala tiden för att överföra Àndringar. Men med en alltför stor sidstorlek kan vi ta för lÄng tid för servern att serialisera resultaten. Det kan leda till förseningar i bearbetningen av andra förfrÄgningar som skickas till servern. Parametern page_size kan laddas frÄn konfigurationsfilen. För varje överfört space kan ett eget vÀrde anges. För de flesta spaces kan dock standardvÀrdet (till exempel 100) passa.

Vi utför funktionen get_goods:

tarantool> 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
...

LÄt oss ta vÀrdet av fÀltet row_ver frÄn den sista raden och Äterskalla funktionen:

tarantool> 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
...

Och en gÄng till:

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

Som vi ser returnerar funktionen samtliga poster i space pÄ detta sÀtt, goods. Efter den sista sidan följer en tom uppsÀttning.

Vi gör Àndringar i space:

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

Vi har Àndrat vÀrdet av fÀltet name för en post och lagt till tvÄ nya poster.

Vi upprepar det senaste funktionsanropet:

tarantool> 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
...

Funktionen returnerade de Àndrade och tillagda posterna. SÄledes gör funktionen get_goods möjligt att hÀmta data som har Àndrats sedan den senaste anropningen, vilket Àr grunden för den diskuterade replikeringsmetoden.

Vi lÀmnar HTTP-resultatets utdata i form av JSON utanför ramarna för denna artikel. Mer information kan lÀsas hÀr: https://habr.com/ru/company/mailru/blog/272141/

Implementeringen av klient-/slave-delen

LÄt oss se hur implementeringen av mottagarsidan ser ut. Vi skapar en space pÄ mottagarsidan för att lagra de uppladdade data:

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
})

Strukturen av utrymmet liknar strukturen av utrymmet i kÀllan. Men eftersom vi inte planerar att överföra de erhÄllna uppgifterna nÄgon annanstans, finns inte kolumnen row_ver i mottagarens utrymme. I fÀltet id kommer identifierarna frÄn kÀllan att sparas. DÀrför behöver inte mottagarsidan göra den autoinkrementell.

Dessutom behöver vi ett utrymme för att spara vÀrdena 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 varje laddat utrymme (fÀltet space_name) kommer vi hÀr att spara det senaste laddade vÀrdet row_ver (fÀltet value). Den primÀra nyckeln Àr kolumnen space_name.

Vi kommer att skapa en funktion för att ladda data frÄn utrymmet goods via HTTP. För detta behöver vi ett bibliotek som implementerar en HTTP-klient. Följande rad laddar biblioteket och skapar en instans av HTTP-klienten:

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

Vi behöver ocksÄ ett bibliotek för deserialisering av json:

local json = require('json')

Det Àr tillrÀckligt för att skapa en funktion för att ladda data:

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

Funktionen utför en HTTP-begÀran till den angivna url:en, skickar med row_ver som parameter och returnerar det deserialiserade resultatet av begÀran.

Funktionen för att spara de mottagna uppgifterna ser ut sÄ hÀr:

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

Loopen för att spara data i utrymmet goods placeras i en transaktion (detta görs med funktionen box.atomic) för att minska antalet operationer med disken.

Slutligen kan funktionen för att synkronisera det lokala utrymmet goods med kÀllan implementeras sÄ hÀr:

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

Först lÀser vi det tidigare sparade vÀrdet row_ver för utrymmet goods. Om den saknas (första utbytet), tar vi som row_ver noll. NÀsta steg i cykeln Àr att utföra sidladdning av Àndrade data frÄn kÀllan via angiven url. Vid varje iteration sparar vi de mottagna uppgifterna i motsvarande lokala utrymme och uppdaterar vÀrdet row_ver (i utrymmet row_ver och i variabeln row_ver) - vi tar vÀrdet row_ver frÄn den sista raden av de laddade uppgifterna.

För att skydda mot oavsiktlig loopning (i hÀndelse av ett programfel) kan cykeln while ersÀttas med for:

for _ = 1, max_req do ...

Som ett resultat av att funktionen sync_goods utrymmet goods i mottagaren kommer att innehÄlla de senaste versionerna av alla poster frÄn utrymmet goods i kÀllan.

Uppenbarligen kan denna metod inte anvÀndas för att överföra borttagning av data. Om det finns ett sÄdant behov kan vi anvÀnda en borttagningsmarkering. Vi lÀgger till i utrymmet goods ett booleskt fÀlt is_deleted och istÀllet för fysisk borttagning av poster anvÀnder vi logisk borttagning - vi sÀtter vÀrdet pÄ fÀltet is_deleted till vÀrdet true. Ibland kan det vara mer praktiskt att anvÀnda ett fÀlt is_deleted , dÀr datum och tid för den logiska borttagningen av posten lagras. Efter logisk borttagning kommer den markerade posten att överföras frÄn kÀllan till mottagaren (enligt den logik som diskuterades ovan). borttagenSekvensen

kan anvÀndas för att överföra data frÄn andra utrymmen: det finns inget behov av att skapa en separat sekvens för varje överfört utrymme. row_ver Vi har sett en effektiv metod för hög nivÄ av datareplication i applikationer som anvÀnder databashanteringssystemet Tarantool.

Databashanteringssystemet Tarantool Àr en attraktiv och lovande produkt för att skapa högbelastade applikationer.

Slutsatser

  1. Hög nivÄ av datareplication har flera fördelar jÀmfört med lÄg nivÄ av datareplication.
  2. Metoden för hög nivÄ av datareplication som beskrivs i artikeln gör det möjligt att minimera mÀngden överförda data genom att bara överföra de poster som har Àndrats efter den senaste utbytestillfÀllet.
  3. Hej, jag utvecklar applikationer för databashanteringssystemet Tarantool - en plattform utvecklad av Mail.ru Group som kombinerar högpresterande.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster