Let's Do It Romania - 24 Septembrie 2011



   

Originalul şi copia

   

Fundamentele replicării în domeniul bazelor de date


   

Replicarea este o tehnologie destul de veche, care a căpătat o popularitate nesperată odată cu succesul lui Lotus Notes. Ideea a fost preluată cu succes în domeniul bazelor de date şi, curând, a devenit un loc comun în SGBD-urile de vârf.

Mircea Sârbu


Un fenomen interesant în informatica actuală - şi cu precădere în domeniul bazelor de date - îl reprezintă manifestarea pregnantă a două tendinţe contradictorii: centralizarea şi distribuirea. Ambele tendinţe sunt animate de intenţia de a obţine anumite avantaje şi, desigur, preţul acestor avantaje îl reprezintă anumite dezavantaje. Dacă ne referim la baze de date, avantajul centralizării constă în posibilităţile mult mai simple de a asigura integritatea datelor şi consistenţa prelucrărilor, în timp ce descentralizarea aduce un spor de disponibilitate. Performanţa de ansamblu este un aspect care, în cele din urmă, "arbitrează" criteriile care se cer urmărite pentru un echilibru cât mai apropiat de optim între centralizare şi descentralizare. O discuţie mai amplă asupra bazelor de date distribuite a fost publicată în PC Report (iunie 1997).

Replicarea este o tehnologie care, în foarte multe situaţii, reprezintă un compromis acceptabil în această direcţie. Replicarea a devenit din ce în ce mai populară pe măsură ce posibilităţile de comunicaţii pentru date au devenit mai bogate, mai ales odată cu dezvoltarea Internet-ului. În ciuda unor aspecte relativ intuitive, tehnologia replicării este suficient de complexă pentru a invalida din start orice tentativă de a o explica în câteva pagini de revistă. Articolul acesta urmăreşte mai mult să sublinieze importanţa tehnologiei, să prezinte câteva domenii de aplicabilitate şi să schiţeze o taxonomie a metodelor şi tehnicilor. Cu concursul partenerilor noştri din industrie, subiectul poate fi continuat în articole tehnice mai specifice.

Ce este replicarea?

La modul intuitiv, replicarea este un proces care constă în realizarea şi distribuirea unor copii ale datelor. Distribuirea acestor copii (numite "replici") are ca scop procesarea datelor în mod local.

Dacă vom analiza câteva scenarii, vom descoperi imediat câteva dintre tipurile posibile de replicare şi vom putea detalia puţin definiţia.

  1. Să presupunem că o firmă comercială din Bucureşti are filiale judeţene. Nomenclatorul de produse pe care firma le vinde este administrat central. Pentru ca filialele locale să aibă acces la acest nomenclator există, în principiu, varianta accesului la nomenclatorul stocat pe serverul central al firmei. Aceasta ar însemna ca, practic, fiecare tranzacţie realizată local să acceseze serverul central. Avantajul este că se garantează astfel actualitatea informaţiei utilizate şi se asigură integritatea bazei de date, dar dezavantajele constau în costurile comunicaţiei, în viteza redusă de răspuns şi, mai ales, în dependenţa de accesul la server. O pană de comunicaţii sau indisponibilitatea temporară a serverului va pune filiala în imposibilitatea de a efectua orice tranzacţie. Alternativa o constituie distribuirea unor replici ale nomenclatorului la filialele judeţene. În acest caz, bazele de date locale vor lucra autonom, cu riscul de a nu dispune în orice moment de varianta actuală a informaţiilor din nomenclator.

  2. O firmă de asigurări utilizează agenţi care călătoresc în teritoriu şi încheie asigurări. Agenţii dispun de un calculator portabil şi de o bază de date autonomă în care consemnează fiecare asigurare încheiată. Din când în când, agenţii trimit la sediul central replici ale bazelor de date locale, pentru a fi centralizate şi procesate. De asemenea, pot să primească actualizări ale preţurilor şi clauzelor, noi oferte etc.

  3. Să considerăm o firmă de software care dispune de echipe de dezvoltare în mai multe oraşe. O bază de date în care sunt consemnate bug-urile semnalate de clienţi este întreţinută la sediul central, dar este replicată la toate punctele de lucru, pentru că o "scamă" într-un subsistem poate afecta (sau poate proveni din) alte subsisteme. Orice echipă poate să facă actualizări, care trebuie să poată fi văzute cât mai repede de celelalte echipe.

Pot fi imaginate numeroase alte scenarii, dar cele de mai sus reprezintă cazuri clasice. În toate cele trei cazuri se remarcă faptul că replicarea este utilizată pentru a oferi un nivel cât mai ridicat de autonomie bazelor de date locale. Pe de altă parte, se observă că acest nivel ridicat de autonomie (şi, implicit, de disponibilitate) implică anumite concesii în ceea ce priveşte actualitatea informaţiei utilizate. De asemenea, replicarea conduce la redundanţă, ceea ce atrage după sine pericolul apariţiei unor stări inconsistente.

O definiţie

Revenind acum la definiţie, am putea să spunem că replicarea este o tehnologie care permite ca informaţiile provenind de la una sau mai multe surse să fie distribuite către una sau mai multe ţinte şi, în plus, ca modificările intervenite în surse să fie propagate în mod consistent către ţintele corespunzătoare.

Într-adevăr, definiţia a devenit acum prea generală, dar putem să facem câteva precizări care să o facă utilizabilă.

În primul rând, "sursele" şi "ţintele" sunt, în cazul nostru, baze de date. Vom restrânge chiar mai mult domeniul, referindu-ne în cele ce urmează doar la baze de date relaţionale. Este însă bine să reţinem că replicarea este o noţiune mai generală, fiind prezentă în domenii diverse, cum ar fi administrarea documentelor, mesagerie electronică, Internet etc.

Faptul că atât sursele cât şi ţintele pot fi multiple permite stabilirea unor relaţii diverse între acestea, ansamblul acestor relaţii definind ceea ce se numeşte de obicei "topologia replicării". În scenariile evocate mai sus, avem de-a face cu topologii ierarhice în primele două scenarii ("one-to-many" în cazul (a), "many-to-one" în cel de-al doilea caz) şi o topologie de tip reţea ("many-to-many") în ultimul scenariu (c).

Un alt aspect important este faptul că replicarea nu implică, de obicei, întreaga bază de date (caz în care avem de-a face cu o replicare totală). Cazul cel mai comun îl reprezintă replicarea unei tabele, dar adesea se recurge la replicarea unor submulţimi a datelor unei tabele sau a mai multor tabele, ceea ce ridică şi mai mult nivelul de complexitate a replicării.

Procesul prin care se asigură capturarea, propagarea şi reproducerea la ţinte a actualizărilor din surse se numeşte sincronizare, fiind elementul central al întregii tehnologii. Sincronizarea asigură caracterul dinamic al întregului proces. Un termen alternativ pentru sincronizare este "reîmprospătare" (refresh), deşi în anumite contexte sensul său este mai limitat (se referă doar la anumite tipuri de sincronizare).

O ultimă precizare se referă la obiectul sincronizării. Din cele prezentate până acum s-ar putea deduce că sincronizarea se referă exclusiv la date. Cu toate acestea, expresia "propagarea actualizărilor" sugerează faptul că sincronizarea poate fi privită şi dintr-o perspectivă procedurală: este posibil ca ceea ce se transmite de la sursă către ţintă să nu fie de fapt noile valori ce trebuie memorate, ci chiar operaţiile de actualizare aplicate asupra sursei, astfel încât acestea să fie aplicate şi asupra replicilor. Cu alte cuvinte, replicarea nu se referă doar la distribuirea datelor, ci şi la distribuirea prelucrărilor. Aşa cum vom vedea în continuare, în practică se utilizează o combinaţie de tehnici.

Sincron şi asincron

O primă diviziune a tehnicilor de replicare se bazează pe momentul în care se realizează sincronizarea. Din acest punct de vedere, replicarea poate fi sincronă sau asincronă.

Replicarea sincronă implică faptul că o actualizare în sursă trebuie propagată şi aplicată imediat în toate replicile. Acest proces se realizează de obicei pe baza unui protocol numit "comitere în două faze" (two-phase commit) şi se referă la tranzacţia care realizează actualizarea. În prima fază, sursa (un server de baze de date) va solicita ţintelor (alte sisteme de baze de date) să se pregătească pentru efectuarea tranzacţiei şi va aştepta confirmările din partea acestora. Când toate ţintele sunt pregătite, sursa le va solicita comiterea tranzacţiei şi va aştepta ca fiecare ţintă să raporteze succesul. Când toate ţintele au comis cu succes tranzacţia, sursa declară tranzacţia încheiată şi consemnează acest lucru în jurnal (log). În cazul în care într-una dintre faze cel puţin o ţintă nu poate comite tranzacţia, orice modificări produse deja sunt anulate (rollback).

Este evident că replicarea sincronă impune un două cerinţe esenţiale: reţeaua (fie ea LAN sau WAN) trebuie să funcţioneze şi fiecare bază de date ţintă să poată să comită tranzacţia care realizează actualizarea. Aceste cerinţe pot fi îndeplinite doar în anumite medii, cu anumite costuri, iar balanţa este înclinată în favoarea integrităţii procesării şi în defavoarea disponibilităţii. Anumite aplicaţii, cum ar fi de exemplu cele financiare sau cele de rezervare a locurilor, sunt bine deservite de această tehnică.

Care este diferenţa între replicarea sincronă şi tranzacţiile distribuite? Este greu de trasat o linie de demarcaţie clară. În principiu, o tranzacţie distribuită nu lucrează (în mod deliberat) cu copii ale datelor, ci cu date care sunt distribuite în mai multe locaţii. Astfel, este posibil ca prelucrarea unei comenzi de la un client să implice atât date ale serviciului de vânzări (situat la o anumită locaţie) cât şi date ale serviciului financiar (situat într-o altă locaţie). Din punctul de vedere al aplicaţiei, localizarea datelor este un proces transparent.

În cazul replicării sincrone, un scenariu tipic este următorul: O tranzacţie T încearcă să actualizeze (printre altele) nişte date care reprezintă sursa unei replicări. Pentru ca această actualizare să se propage imediat, sistemul generează o tranzacţie specială P, care să actualizeze (independent de T) replicile datelor respective. Comiterea cu succes a tranzacţiei T este condiţionată de comiterea tranzacţiei P. Să remarcăm că T poate fi tranzacţie simplă (locală), în timp ce P este o tranzacţie distribuită (care este însă transparentă din perspectiva aplicaţiei).

Replicarea asincronă se bazează pe un mecanism de tip store and forward (adesea se foloseşte şi termenul message based replication). Informaţiile despre actualizările produse în sursa replicării sunt stocate într-o coadă de aşteptare, de unde sunt apoi trimise către locaţiile ţintelor, care se sincronizează pentru a reflecta starea sursei. Acest proces implică o anumită întârziere, care poate fi de câteva secunde sau de câteva zile, în funcţie de configurarea sistemului şi de disponibilitatea sistemelor implicate. În schimb, avantajele pot fi considerabile din punct de vedere al disponibilităţii (operarea într-o locaţie nu este afectată de indisponibilitatea unei alte locaţii) şi a costurilor legate de transferul informaţiilor (nu este nevoie de o conexiune permanentă).

Deoarece replicarea sincronă este adesea asimilată cu bazele de date distribuite şi implică un nivel de complexitate foarte ridicat, ceea ce se traduce în costuri adesea foarte mari (atât pentru comunicaţii cât şi pentru proiectare şi exploatare), replicarea asincronă - mult mai practică şi mai ieftină - a devenit alternativa cea mai populară în ultima vreme. În plus, replicarea sincronă este inaplicabilă într-o întreagă clasă de aplicaţii, din ce în ce mai răspândită, care implică utilizatori mobili (ca în cazul celui de-al doilea scenariu evocat). Din aceste motive, în foarte multe lucrări şi articole, termenul "replicare" se referă implicit la replicarea asincronă. În cele ce urmează mă voi referi în mod preponderent la replicarea asincronă.

Tranzacţional sau nu

Înainte de a detalia mai departe mecanismele care stau la baza replicării, merită să insistăm puţin asupra unor aspecte legate de consistenţa de ansamblu a unei baze de date distribuite prin replicare. Pentru aceasta, trebuie să revenim puţin la noţiunea de tranzacţie, tratată pe larg în mai multe articole apărute de-a lungul timpului în PC Report. (O excelentă prezentare a subiectului a fost făcută de Mihai Budiu în PC Report 67 - aprilie 1998, http://www. pcreport.ro/pcrep67/023.htm)

Proprietăţile ACID (vezi caseta "Tranzacţii") sunt interdependente. Implementarea acestora se face în majoritatea sistemelor comerciale prin mecanisme de blocare (lock - încuietoare, zăvor) care, pe baza unui protocol bine stabilit, interzic accesul altor tranzacţii la datele asupra cărora acţionează deja o tranzacţie. Blocarea este însă un mecanism care funcţionează în mod sincron, deci este inaplicabil în cazul replicării asincrone. Aceasta înseamnă că există oricând posibilitatea ca efectele unei tranzacţii să nu fie vizibile într-una sau mai multe replici, ceea ce conduce la faptul că, cel puţin între două sincronizări consecutive, ansamblul bazei de date (considerând aici şi replicile) să fie inconsistent.

Să considerăm scenariul (a) şi să ne imaginăm că se produce o tranzacţie care modifică preţul produsului P de la p1 la p2. Teoretic, tranzacţia poate fi considerată încheiată abia atunci când modificarea a fost efectuată în tabela "părinte" (de la sediul central) şi în toate replicile sale. Replicarea fiind asincronă, filialele vor continua să lucreze cu preţul p1 până la sincronizare. Mai mult, replicile nu se sincronizează simultan, deci este posibil ca anumite filiale să lucreze cu un preţ în timp ce altele lucrează cu alt preţ. Iar dacă această inconsistenţă nu pare suficient de "flagrantă", să ne imaginăm ce se întâmplă în situaţia în care produsul P nu mai este disponibil pentru vânzare şi este şters din baza de date centrală.

Există mai multe variante de a trata astfel de situaţii, în funcţie de posibilităţile tehnice şi, mai ales, de activitatea modelată (business rules).

O primă posibilitate este de a accepta posibilitatea de a avea stări inconsistente. Dacă firma consideră că nu-i grav ca într-un interval rezonabil de timp (să zicem, 2-3 ore) produsul să fie vândut cu preţuri diferite de diverse filiale, e OK. În acest caz, o replicare non-tranzacţională este acceptabilă. Aceasta înseamnă că modificările sunt transmise către replici fără nici o informaţie despre tranzacţiile care le-au produs (se foloseşte şi termenul de replicare linie cu linie - row by row replication).

O variantă "forte" este de a combina replicarea asincronă cu replicarea sincronă. Anumite operaţii critice (în cazul cărora compromisul este inacceptabil) ar putea fi forţate la o replicare sincronă. De pildă, la ştergerea unui produs din nomenclatorul central, baza de date va încerca să utilizeze canalele de comunicaţie disponibile pentru a lua legătura cu toate filialele şi a aplica protocolul two phase commit, chiar dacă în acest fel tranzacţia în cauză va dura câteva minute (sau chiar ore). Desigur, trebuie să existe posibilităţile tehnice (măcar conexiuni dial-up) şi să fie acceptabil din perspectiva activităţii respective. Dacă, de exemplu, se tranzacţionează intens iar baza de date nu permite blocarea la nivel de linie ci doar la nivel de tabelă, s-ar putea ca o astfel de tranzacţie lungă să oprească toată activitatea firmei.

În fine, o altă variantă o reprezintă "tranzacţiile întârziate". Aceasta înseamnă că replicarea va ţine seama nu doar de modificările în sursă, ci şi de tranzacţiile care le-au produs. Deocamdată, este suficient de menţionat că se transmit către ţinte doar modificările produse de tranzacţii deja comise în sursă şi că aceste modificări sunt trimise în ordinea în care s-au produs, însoţite de mărci de timp (timestamps). Este vorba, în principiu, de un fel de replicare a tranzacţiilor. E importat de notat faptul că se recurge la o serializare a actualizărilor. Dacă, de pildă, în sursă o tranzacţie T1 a modificat preţul produsului P de la p la p+1, după care o tranzacţie T2 a modificat preţul aceluiaşi produs la p*2, este esenţial ca aceste modificări să fie efectuate în ţintă în aceeaşi ordine.

Există şi alte modalităţi de tratare a acestor probleme, dar nu există nici o garanţie că ansamblul de date va fi în permanenţă consistent. Este un compromis obligatoriu pe care replicarea asincronă îl impune. Vom vedea pe parcurs şi care sunt tehnicile prin care se pot obţine variante cât mai avantajoase.

Unidirecţional sau bidirecţional

Cea mai simplă formă de replicare o reprezintă replicarea unidirecţională. Altfel spus, replica este disponibilă doar pentru citire. Deoarece o astfel de replică read-only reprezintă doar o imagine a datelor din sursă aşa cum se prezentau ele la un anumit moment (un instantaneu), se foloseşte adesea termenul snapshot.

Este interesantă istoria acestui termen. Iniţial, el a fost utilizat în unele baze de date relaţionale centralizate (Oracle), pentru a "îngheţa" anumite seturi de date. Un snapshot se defineşte exact ca un view (deci prin intermediul unei selecţii) dar, spre deosebire de acesta, este o tabela stocată - nu virtuală, ca în cazul unui view. Un snapshot este accesibil doar pentru citire şi este "reîmprospătat" la anumite intervale de timp. Rolul unui snapshot într-o bază centralizată era (şi încă este) de a permite efectuarea unor operaţii de analiză bazate pe interogări complexe, fără ca acestea să interfereze (prin blocări) cu procesările "pe viu", rezultând astfel performanţe superioare atât pentru procesările tranzacţionale (OLTP) cât şi pentru cele de analiză (OLAP).

Când a păşit în lumea procesării distribuite, Oracle s-a folosit de acest mecanism, furnizând astfel "prima generaţie" de instrumente de replicare. Termenul s-a impus, aşa că atunci când Oracle a introdus o tehnologie de replicare bidirecţională ("simetrică") a păstrat termenul, introducând noţiunea de "updatable snapshot", deşi este o flagrantă contradicţie de termeni.

Atenţie deci la terminologie: Oracle foloseşte termenul snapshot ca sinonim pentru replică, în timp ce restul lumii înţelege prin snapshot doar "replică read-only" (mai mult, Sybase numeşte snapshot o replică read-only non-tranzacţională).

Replicarea bidirecţională implică faptul că replica este la rândul ei actualizabilă şi că modificările din replică vor fi reflectate în sursă. Observaţi că în acest caz noţiunile de "sursă" şi "replică" îşi cam pierd sensul: o sursă poate fi considerată replică şi invers, motiv pentru care se foloseşte adesea termenul de "replicare simetrică" (introdus tot de Oracle). De altfel, trebuie spus că atunci când vom lua în considerare diferitele topologii posibile pentru replicare, nici termenul "bidirecţional" nu este foarte corect (de fapt, procesul este multidirecţional) şi nici "simetric" (câteodată una dintre locaţii este "mai simetrică" decât celelalte).

Totuşi, cine-i "şeful"? Cum se defineşte şi se iniţiază replicare? Ajungem astfel la câteva noţiuni importante legate de metodologia replicării.

Configuraţii

În discuţia care urmează voi încerca să prezint, pe baza scenariilor de la începutul articolului, principalele tipuri de aplicaţii în care se utilizează replicarea asincronă. Voi introduce totodată, la modul intuitiv, câteva noţiuni care vor fi explicitate în continuarea articolului.

O primă aplicaţie tipică o constituie distribuirea (sau diseminarea) informaţiilor. În scenariul (a) firma de comerţ utilizează mecanismele replicării pentru a distribui către filiale nomenclatorul de produse pe care filialele judeţene le vând. Deoarece filialele judeţene nu au voie să actualizeze nomenclatorul, replicarea este unidirecţională, deci copiile sunt disponibile doar pentru citire. Replicarea poate fi tranzacţională sau la nivel de linie, în funcţie de volumul şi frecvenţa modificărilor (altfel spus: de volatilitatea datelor). Tot acest factor este cel care determină frecvenţa sincronizărilor. O situaţie tipică este cea în care replicarea se produce în fiecare noapte, astfel încât modificările să nu interfereze cu activitatea cotidiană. Avantajul este sporit şi de costul (de obicei) mai mic al liniilor de telecomunicaţii (dacă e cazul).

O chestiune extrem de importantă în acest context o reprezintă relaţia de proprietate asupra datelor. În acest caz, nomenclatorul de produse aparţine locaţiei centrale, care este singura abilitată să efectueze actualizări. Termenul generic pentru această configuraţie este master/slave, unde locaţia centrală este nodul master (numit uneori nodul "primar") iar locaţiile slave (numite uneori "secundare") sunt bazele de date locale.

În privinţa topologiei, se observă că este vorba de o structură arborescentă cu două niveluri. Este însă posibilă şi o structură pe mai multe niveluri. Să ne imaginăm că în fiecare judeţ pot exista mai multe magazine, fiecare cu o bază de date locală. În acest caz, replicarea se poate face în continuare, de la filiale către magazine, ierarhia fiind pe trei niveluri. Este de notat însă că nomenclatorul este în continuare deţinut doar de către locaţia centrală.

Scenariul (b) pune în evidenţă tot o structură arborescentă, de tip master/slave, cu diferenţa că datele aparţin în acest caz utilizatorilor mobili, fiind replicate la locaţia centrală doar pentru centralizare şi analiză, în regim read-only. În acest caz avem mai multe noduri primare şi un singur nod secundar, o situaţie tipică pentru aplicaţiile de centralizare (sau consolidare) a informaţiilor. Acest gen de replicare este frecvent utilizat şi în depozitele de date, astfel încât locaţia centrală să fie alimentată automat, la intervale regulate cu informaţii extrase din sistemele operative (în acest caz apare şi o fază de "curăţire" şi transformare a datelor, dar replicarea rămâne în esenţă unidirecţională). Chiar dacă nu se lucrează cu un depozit de date, acest tip de replicare poate fi utilizat pentru a izola aplicaţiile de analiză (care pot lucra pe o copie read-only) de cele tranzacţionale.

Rămânând în sfera configuraţiilor master/slave, să simplificăm puţin scenariul (a), considerând că firma este compusă doar din trei sucursale: una la Bucureşti (acţionând în Muntenia, Oltenia şi Dobrogea), una la Cluj (acţionând în Transilvania) şi un la Iaşi (acţionând în Moldova). Dacă vom considera informaţiile despre clienţii companiei, alegerea cea mai corectă este ca fiecare sucursală să aibă la dispoziţie informaţii despre toţi clienţii companiei. Pe de altă parte, este normal ca fiecare client să "aparţină" unei singure sucursale, pentru a evita actualizările multiple, care pot crea probleme de consistenţă. În această situaţie se poate recurge la o partiţionare a informaţiilor despre clienţi, astfel încât actualizările privind un anumit client pot fi făcute de o singură sucursală, modificările fiind replicate apoi către celelalte două (care vor avea acces doar pentru citire). Configuraţia este tot de tip master/slave, în acest caz fiecare nod fiind master pentru anumite informaţii şi slave pentru altele.

Scenariul (c) este tipic pentru configuraţiile reţea, numite "peer to peer" sau "update anywhere" (Oracle numeşte această configuraţie "multiple master"). Fiecare locaţie poate să facă actualizări asupra datelor, iar modificările sunt replicate la toate celelalte noduri. Este evident că această configuraţie este cea mai expusă la pericolul pierderii integrităţii, datorită unor actualizări contradictorii, numite în continuare "conflicte". Pentru a detecta şi rezolva aceste conflicte se folosesc diverse tehnici automate (replicarea tranzacţională este obligatorie în aceste configuraţii), dar nu se exclude nici posibilitatea unor soluţii "manuale".

Există şi câteva versiuni ale acestei variante de replicare, care au darul de a simplifica oarecum administrarea. Una dintre ele este "proprietatea dinamică" asupra datelor. Ideea de bază este că procesarea datelor se constituie într-un proces format din etape, proprietatea asupra datelor schimbându-se de la o etapă la alta (motiv pentru care configuraţia mai este numită "workflow ownership"). În exemplul (c), ne-am putea imagina că bug-urile detectate ar putea avea un traiect prestabilit, pornind de la echipa care se ocupă de interfaţa cu utilizatorul, trecând apoi la echipa care dezvoltă logica aplicativă şi, în cele din urmă, la echipa care proiectează bazele de date. În fiecare pas al procesului, informaţiile asupra respectivului bug aparţin echipei respective, care este sigura care poate face actualizări, actualizări care vor fi replicate către celelalte locaţii. În felul acesta, problema se reduce la una de tip master/slave. Poate exemplul nu este cel mai sugestiv. Aplicarea tipică a acestei tehnici găsim în cazul procesărilor comenzilor de la clienţi.

O altă variantă (promovată de IBM) este folosirea unui aşa-numit "nod de referinţă", astfel încât configuraţia va fi transformată din reţea într-o ierarhie, având ca rădăcină nodul de referinţă. Toate actualizările realizate de noduri vor fi replicate către nodul de referinţă, care apoi le va replica la rândul său către celelalte noduri. Desigur, în acest fel nu se împiedică apariţia conflictelor (modificările pot fi efectuate în continuare de către oricare nod), dar se pot implementa mai simplu metode tranzacţionale de depistare şi rezolvare.

Combinaţii

În lumea reală situaţiile nu sunt atât de "pure" cum apar prezentate în secţiunea precedentă. În cele mai multe cazuri, avem de-a face cu configuraţii combinate sau cu configuraţii paralele în cadrul aceleiaşi aplicaţii. În scenariul (a) nomenclatorul central aparţine locaţiei centrale, dar datele despre clienţi pot aparţine filialelor (replicate apoi la celelalte filiale, eventual prin intermediul unui nod de referinţă situat la nivel central), în timp ce procesarea comenzilor se poate face în regim de workflow ownership. De pildă, preluarea comenzilor se face la nivelul magazinelor, livrarea se face la nivelul filialei, facturarea se efectuează la nivelul central etc.

În plus, mecanismul relativ simplu de tip publish and subscribe pe care se bazează administrarea replicării se complică şi mai mult când luăm în considerare faptul că nu întotdeauna replicarea implică doar tabele întregi (aşa cum, oarecum implicit, am considerat până acum), cu atât mai puţin baze de date întregi. În multe cazuri, datele care trebuie replicate constituie o submulţime a liniilor şi/sau a coloanelor unei tabele, dar pot fi şi date compuse printr-o definiţie de tipul celor folosite pentru crearea unui view, implicând mai multe tabele legate prin asocieri complexe. Uneori trebuie replicat un grup de obiecte ale bazei de date.

Dacă la aceasta mai adăugăm faptul că există şansa ca sistemul de baze de date să nu fie omogen, implicând diverse SGBD-uri (de pildă, utilizatorii mobili nu vor putea purta într-un notebook un server SQL complet), uneori de la producători diferiţi, obţinem un tablou destul de bogat al nivelului de complexitate la care se poate ajunge.

Cu toate acestea, replicarea poate fi stăpânită. Există astăzi o pleiadă de produse dedicate configurării, administrării şi monitorizării procesului de administrare. Pentru a putea evalua avantajele şi dezavantajele acestora în funcţie de problema pe care o aveţi de rezolvat, este necesar să cunoaşteţi la modul general etapele procesului de replicare şi câteva dintre variantele tehnologice ale fiecărei etape. În cele ce urmează voi încerca să schiţez principalele elementele ale acestei problematici.

Înainte de a începe, iată etapele procesului:

  • Stabilirea surselor şi a ţintelor;

  • Capturarea actualizărilor şi evaluarea acestora în vederea propagării;

  • Propagarea actualizărilor de la sursă la ţintă;

  • Aplicarea actualizărilor la baza de date ţintă (cu detectarea şi rezolvarea eventualelor conflicte).

Configuraţii, topologii, proprietate

Diverse produse utilizează diverse terminologii pentru a descrie modul de configurare a replicării, oferind desigur şi posibilităţi diferite. Una dintre cele mai simple şi evocatoare terminologii este cea bazată pe metafora publish and subscribe (publicare şi abonare). Deşi utilizată ca atare doar de Microsoft, cu anumite adaptări este valabilă la majoritatea produselor.

Pe scurt, baza de date (serverul) care va servi ca sursă pentru replicare va fi numită "editor" (publisher). În cadrul acestei baze de date se definesc aşa-numite "publicaţii" (publications), care sunt de fapt seturile de date disponibile pentru replicare. Alte baze de date se pot "abona" (subscribe) la aceste publicaţii. Pentru a defini procesul de replicare se specifică editorul, publicaţia şi abonatul, precum şi nişte informaţii adiţionale, cum ar fi tipul replicării (unidirecţională sau bidirecţională), intervalul de timp între sincronizări, modul de rezolvare a conflictelor etc. În principiu, orice server poate să fie editor al anumitor publicaţii şi totodată abonat la publicaţii editate de alte servere. Mai mult, se poate ajunge la situaţii în care aceeaşi tabelă poată să conţină date sursă şi date replicate (prin partiţionare).

O problemă ceva mai delicată apare atunci când replicarea este selectivă, în sensul că "publicaţia" nu este o tabelă întreagă. În acest caz se recurge la un procedeu numit data subsetting. Replicarea selectivă este importantă din cel puţin două motive. În primul rând, astfel se poate minimiza traficul în reţea (să ne imaginăm că fiecare produs din nomenclator este însoţit de scheme, desene sau chiar videoclipuri de prezentare - replicarea acestora poate fi extrem de costisitoare). În al doilea rând, este foarte posibil ca drepturile de acces la anumite date să fie rezervate proprietarului, caz în care replicarea acestora afectează securitatea sau confidenţialitatea informaţiilor.

De regulă, indiferent de modul de specificare a sursei, se ajunge la o instrucţiune SELECT prin care se defineşte subsetul dorit. Restricţiile impuse asupra acestei selecţii depind de posibilităţile fiecărui produs în parte, dar câteva sunt în general valabile. Cel mai important aspect este că fiecare linie selectată trebuie să corespundă unei linii din tabela sursă. Aceasta înseamnă, la modul minimal, că subsetul de coloane selectat trebuie să cuprindă toate coloanele care formează cheia primară a tabelei sursă. În majoritatea produselor de replicare, în selecţia datelor pentru replicare nu se pot folosi funcţii de agregare sau distinct, nu se admit clauze GROUP BY sau CONNECT BY, asocieri (join), operaţii cu seturi (de pildă UNION) şi anumite forme de interogări înglobate.

Pentru exemplificare, mă voi referi la Oracle8 Replication, unde definirea "publicaţiei" se face printr-o instrucţiune de tipul:

      CREATE SNAPSHOT nume 
      AS selecţie. 

Oracle se referă la două niveluri de complexitate pentru replicare: nivelul de bază (lucrează doar unidirecţional) şi nivelul avansat (poate lucra şi bidirecţional). Replicile pot fi la rândul lor simple sau complexe. Ambele categorii pot fi folosite în replicarea de bază, însă numai replicile simple pot fi actualizabile.

O replică simplă se bazează pe o singură tabelă sursă iar selecţia pe care se bazează se supune tuturor restricţiilor amintite mai sus. Dacă vom considera că în scenariul (a) avem în baza de date centrală o tabelă Produse având coloanele cod, den, preţ, comandat şi disponibil, atunci o replică se poate crea astfel:

      CREATE SNAPSHOT produse AS 
      SELECT cod, den, pret FROM produse@centru.ro 

În acest caz, replica preia doar o submulţime a coloanelor tabelei sursă. Actualizările care afectează în sursă doar coloanele excluse din replică nu vor fi propagate. Prin folosirea unei clauze WHERE se pot selecta pentru replicare doar anumite linii:

      CREATE SNAPSHOT produse AS 
      SELECT * FROM produse@centru.ro p 
      WHERE p.disponibil > 100 

O caracteristică interesantă în tehnologia de replicare de la Oracle este că selecţia liniilor se poate baza şi pe parcurgerea referinţelor many-to-one în tabele asociate. În exemplul care urmează, locaţia centrală va prelua de la locaţia locală Mureş doar comenzile care corespund unor clienţi din judeţul Mureş:

      CREATE SNAPSHOT comenzi AS 
        SELECT * FROM comenzi@mures.ro a 
         WHERE EXISTS 
               (SELECT cod 
                FROM clienti@mures.ro b 
                WHERE a.cod_client = b.cod 
                AND b.jud = 'MS'); 

Replicile complexe nu au limitările replicilor simple, în schimb nu pot fi actualizabile iar sincronizarea nu se poate face incremental.

Este utilă o analogie între definirea unei replici şi definirea unui view. Restricţiile privind actualizarea unui view sunt de obicei aceleaşi în cazul unei replici. Este interesant de remarcat că instrumentele de replicare de la IBM permit (e drept, printr-o configurare specială) replicarea unui view. O altă remarcă este că Oracle face o importantă distincţie între replicile actualizabile (updatable snapshots) şi replicarea simetrică între servere (multimaster replication). În acest ultim caz nu se admite replicarea selectivă.

În fine, trebuie spus că la configurarea replicării, sistemele generează diverse obiecte specifice în bazele de date, obiecte care vor fi apoi utilizate pentru replicare. De exemplu, Oracle generează automat pentru fiecare snapshot un index bazat pe cheia primară şi un view. Pentru fiecare sursă de replicare, Oracle construieşte un jurnal (snapshot log), materializat într-o tabelă care cuprinde, pentru fiecare linie actualizată cheia primară, marca de timp şi, eventual identificatorul unic de linie (ROWID). Informix foloseşte un catalog global al replicării (cu meta-date despre fiecare surse, participanţi, definiţii, opţiuni etc.), care este la rândul său replicat în toate serverele implicate în replicare. Alte sisteme folosesc tabele adiţionale pentru modificări, tabele "umbră" (shadow), jurnale specializate etc.

Capturarea modificărilor

Odată ce sursele şi ţintele au fost stabilite, următoarea problemă o reprezentă mecanismele prin care programele (sau procesele, în cazul implementărilor în SGBD) de replicare să sesizeze comiterea unei modificări în sursă şi să poată identifica elementele acestei actualizări.

Există două mari categorii de mecanisme de replicare: bazate pe "declanşatoare" (triggers) şi bazate pe jurnal (log).

Capturarea actualizărilor prin "declanşatoare" este cea mai veche. Un trigger este o procedură stocată ceva mai specială, deoarece nu este apelată explicit, ci se execută automat ca răspuns la tentativa de a actualiza (prin INSERT, DELETE sau UPDATE) tabela căreia îi este ataşat trigger-ul. Unele sisteme permit specificarea unor anumite coloane din tabelă a căror modificare declanşează trigger-ul (desigur, pentru UPDATE).

Un trigger se defineşte într-un limbaj procedural, care variază de la server la server (în ultima vreme mai mulţi producători au introdus în produsele lor posibilitatea de a scrie proceduri stocate în Java). Iată un exemplu, în care voi folosi un limbaj procedural fictiv dar sugestiv (inspirat din Oracle şi Postgres):

      CREATE TRIGGER produse_up 
      BEFORE UPDATE ON prod 
      FOR EACH ROW 
      BEGIN 
        IF OLD.cod != NEW.cod THEN 
        DELETE prod 
          WHERE cod = ODL.cod; 
        INSERT INTO prod VALUES 
          (NEW.cod, NEW.den, NEW.pret); 
        END IF; 
      END; 

Exemplul este sugestiv pentru posibilităţile unui trigger. În primul rând, declanşatorul este legat de o tabelă (în cazul nostru prod) şi de o anume actualizare (aici UPDATE, dar se pot menţiona mai multe, conectate prin OR). Se remarcă faptul că acest trigger se declanşează înainte de efectuarea actualizării (opţiunea AFTER ar lansa trigger-ul după ce s-a realizat actualizarea). În fine, constatăm că trigger-ul are acces atât la valorile dinainte de actualizare (OLD) cât şi la cele de după actualizare (NEW). Codul în sine nu este grozav: în cazul în care se încearcă modificarea cheii primare, comanda UPDATE este înlocuită de o ştergere urmată de o înserare. Este posibil ca inserarea să fie şi ea "păzită" de un trigger, ilustrând astfel declanşarea în cascadă. Nu voi intra în detalii legate de valoarea de retur, de parametrizare etc.

Acest mecanism a fost introdus de producătorii de baze de date mai ales pentru a permite controlul procedural al integrităţii datelor, dar a fost utilizat şi pentru a implementa o primă generaţie de aplicaţii de replicare. Este uşor de imaginat un set de trigger-e care adaugă într-o coadă mesaje privind actualizările efectuate într-o tabelă.

Dezavantajul major al acestei abordări este că trigger-ele se execută în baza de date, fiind procese destul de consumatoare de resurse, fapt care afectează performanţele. Pe de altă parte, trigger-ele utilizator nu au acces la informaţii despre tranzacţia în cadrul cărei se execută, deci nu permit decât replicarea la nivel de linie. În fine, apar şi problemele de interferenţă cu trigger-e destinate unor alte scopuri (de pildă validări complexe de consistenţă).

Capturarea modificărilor din jurnal este o metodă oarecum mai simplă. Orice server de baze de date serios jurnalizează într-un fel sau altul tranzacţiile, în scopul de a oferi o posibilitate de refacere a bazei de date în cazul unui incident. Sistemele de jurnalizare sunt diverse, dar în general se consemnează un identificator al tranzacţiei, utilizatorul care a comis tranzacţia, actualizările realizate, valorile intermediare (dinainte şi de după fiecare actualizare) şi momentul comiterii. Numărul mare de informaţii jurnalizate se explică prin faptul că anularea unei tranzacţii (rollback) se face tot pe baza acestor informaţii.

Mecanismul de capturare poate fi integrat în mecanismul de jurnalizare, caz în care identificarea tranzacţiilor care trebuie replicate se poate face dinamic, sau poate fi exterior, caz în care jurnalul este examinat periodic, într-o ordine strictă. Deoarece unele sisteme scriu în jurnal într-o manieră circulară (cele mai noi intrări le suprascriu pe cele mai vechi intrări), procesele de jurnalizare şi capturare trebuie corelate astfel încât capturarea să nu rămână în urmă şi să rateze anumite tranzacţii (în astfel de situaţii, toate tranzacţiile în curs de desfăşurare sunt blocate până când programul de capturare avansează suficient - situaţia poate apare la o defectuoasă configurare a spaţiului rezervat fişierelor log). Deşi unele sisteme marchează chiar în jurnal tranzacţiile care trebuie replicate, majoritatea recurg la structuri de stocare specializate (de genul unui "jurnal consolidat" sau a unor tabele care consemnează schimbările).

Avantajul capturării bazate pe jurnal constă în faptul că se pot identifica tranzacţiile şi că nu se produc interferenţe cu procesările curente (exceptând situaţii de genul celei descrise mai sus). Pe de altă parte, programul sau procesul care realizează capturarea implică procesări destul de complexe şi, mai ales, multe operaţii de transfer de date, ceea ce conduce la o încărcare mai mare a serverului şi/sau a reţelei.

Ambele tehnici sunt folosite şi fiecare producător insistă pe avantajele soluţiei pe care a ales-o. Oracle merge pe soluţia trigger, dar într-o variantă optimizată. Trigger-ele pentru replicare sunt standardizate şi, mai ales, internalizate. Aceasta înseamnă că sunt compilate şi incluse chiar în motorul bazei de date, ceea ce conduce la performanţe foarte bune, plus posibilitatea de a avea acces la toate informaţii relative la tranzacţiile utilizatorilor care realizează actualizări. Sybase se bazează pe un software specializat (Replication Server) care se ocupă numai de replicare. O componentă specializată a acestuia (Log Transfer Manager) rulează alături de fiecare server sursă şi alimentează serverul de replicare cu actualizările capturate din jurnal. Informix şi IBM folosesc de asemenea mecanisme bazate pe jurnal.

Evaluarea datelor pentru replicare

Toate sistemele de replicare încearcă să minimizeze volumul informaţiilor care trebuie propagate. În acest sens, se recurge adesea la o evaluare a informaţiilor capturate despre modificări. Această etapă se desfăşoară de regulă la momentul compunerii mesajelor care vor fi depuse în coadă. În cazul sistemelor bazate pe trigger-e, este posibil ca o parte din aceste procesări să fie realizate chiar de trigger-ul de capturare. Pe de altă parte, este posibil ca trigger-ele să nu scrie direct în coada de mesaje ci într-un jurnal propriu, de unde informaţiile pot fi evaluate în vederea replicării (aşa procesează Oracle cu replicile actualizabile).

Pentru exemplificare, mă voi referi la Informix Enterprise Replication. O primă evaluare se referă la momentul comiterii tranzacţiei şi este necesară pentru a plasa în coadă doar tranzacţii deja comise. Importantă este însă evaluarea imaginii liniilor care trebuie replicate. Această evaluare are ca scop determinarea efectului net al unui serii de actualizări care se realizează asupra unei linii în cadrul unei singure tranzacţii. În felul acesta se evită propagarea fiecărei operaţii şi se trimite doar o singură actualizare (sau poate nici atât), ceea ce minimizează atât traficul cât şi volumul actualizărilor la nodul ţintă.

Deoarece în cadrul unei tranzacţii operaţiile sunt strict secvenţiale, este suficient să analizăm modul cum se evaluează grupuri de două actualizări (actualizarea rezultantă este apoi evaluată împreună cu următoarea şi aşa mai departe).

Cazul cel mai simplu este cel în care inserarea unei linii este urmată de ştergerea liniei, caz în care efectul este nul. Dacă însă inserarea este urmată de o modificare (UPDATE) atunci se verifică dacă imaginea finală a liniei corespunde criteriului de selecţie în vederea replicării. Dacă nu, efectul este iarăşi nul. Dacă da, atunci efectul net este inserarea unei linii cu imaginea finală rezultată.

Dacă o modificare a unei linii este urmată de ştergerea linei, atunci trebuie aplicat criteriul de selecţie asupra rezultatului modificării. Dacă linia rezultată nu trebuie replicată atunci rezultatul net este nul. Dacă însă linia modificată corespunde criteriului, atunci rezultatul este ştergerea imaginii iniţiale a liniei.

De exemplu, dacă replicarea se referă doar la clienţii din judeţul Mureş, atunci tranzacţia următoare:

      BEGIN WORK; 
      INSERT INTO pers 
        VALUES (1325, 'Ion', 'MS'); 
      UPDATE pers SET nume = 'Vasile' 
        WHERE marca = 1325; 
      COMMIT WORK; 

va avea ca efect net trimiterea către ţintă a operaţiei:

      INSERT INTO pers 
        VALUES (1325, 'Vasile', 'MS'); 

Dacă mai apoi se execută următoarea tranzacţie:

      BEGIN WORK; 
      UPDATE pers SET jud = 'CJ' 
        WHERE marca = 1325; 
      DELETE FROM pers 
        WHERE nume = 'Vasile'; 
      COMMIT WORK; 

efectul net va fi replicarea operaţiei:

      DELETE FROM pers 
        WHERE marca = 1325 
          AND nume = 'Vasile' 
          AND jud = 'MS'; 

Observaţie: Desigur, ar fi suficient să se specifice cheia primară pentru ştergere. Condiţiile suplimentare sunt însă utile pentru detectarea unor eventuale conflicte.

Dacă o linie care nu corespundea criteriului va fi actualizată astfel încât să corespundă criteriului, rezultatul net este inserarea în replică a unei linii cu imaginea finală. O situaţie inversă conduce la o ştergere. Este, cred suficient de clar, modul în care se tratează alte combinaţii.

Un caz particular îl reprezintă grupurile de modificări care schimbă cheia primară a unei linii. În acest caz, dacă atât imaginea iniţială cât şi imaginea finală corespund criteriului de selecţie pentru replicare, se procedează la ştergerea liniei iniţiale şi inserarea liniei finale.

O precizare: câmpurile de tip blob sau text sunt tratate oarecum diferit.

Mecanisme de propagare

Propagarea modificărilor se realizează printr-un mecanism bazat pe cozi de mesaje (message queuing). Această tehnologie este suficient de complexă pentru a merita o tratare separată dar, în acest context, este suficient dacă menţionez că aceste mecanisme (fie integrate în baza de date, fie implementate ca middleware) garantează stocarea sigură a mesajelor atât la sursă cât şi la destinaţie, garantează livrarea fiecărui mesaj şi permit urmărirea fiecărui mesaj în parte. Deocamdată, o analogie cu sistemele de poştă electronică este suficientă (unele sisteme folosesc chiar MAPI), deşi produsele de tip MOM (Message Oriented Middleware) sunt adesea mult mai complexe.

În principiu, actualizările capturate se consemnează în nişte mesaje stocate într-o "coadă stabilă" (care nu poate fi afectată de incidente soft sau hard) pe sistemul sursă. Mesajele sunt preluate de mecanismul de transport asincron şi sunt expediate către ţinte, unde sunt stocate tot în cozi stabile. Prin acelaşi mecanism de mesagerie, ţintele răspund cu mesaje de confirmare pentru fiecare mesaj primit. Odată de recepţia a fost confirmată de toţi destinatarii, mesajul poate fi şters din coada de plecare. Protocolul poate fi destul de complex, implicând retransmiterea mesajelor, mecanisme de verificare etc. Este important ca mesajele să poată fi serializate înainte de aplicare, să nu se piardă şi să nu fie dublate.

În general, replicarea se face incremental: fie se trimit operaţiile care au fost efectuate asupra sursei de la ultima sincronizare (eventual evaluate ca efect net al tranzacţiilor care le-au realizat), fie se trimit aşa-numite "fişiere delta", care cuprind diferenţele intervenite în imaginile liniilor între două sincronizări succesive (în cazul replicării non-tranzacţionale). Există însă situaţii în care se recurge la replicarea totală a unei surse. Un caz tipic este cel în care un server ţintă este indisponibil o perioadă mai lungă de timp. În această situaţie, mesajele din coadă care-i sunt adresate nu vor putea fi şterse iar spaţiul ocupat va deveni foarte mare. Soluţia este îndepărtarea ţintei respective din configuraţia de replicare. De îndată ce serverul este din nou disponibil, este reintrodus în configuraţie, ceea ce va conduce la o replicare totală a sursei (echivalentă cu ştergerea tuturor liniilor şi inserarea tuturor liniilor din sursă).

Aplicarea actualizărilor

După ce mesajele cuprinzând actualizările sosesc la serverul ţintă, ele trebuie aplicate (de preferinţă cât mai repede) asupra bazei de date ţintă. Procesul în sine nu este interesant. Eventual se poate menţiona faptul că există posibilitatea ca regulile de integritate din baza de date ţintă să fie diferite de cele din sursă şi că ele vor superviza actualizările. Aceasta înseamnă că deşi la sursă actualizările au fost acceptate, ele pot fi respinse la baza de date ţintă. Situaţiile de acest gen nu sunt tipice, ci fiind mai degrabă rezultatul unor erori de proiectare sau configurare.

Ca regulă generală, în cadrul configuraţiilor de tip master/slave regulile de integritate din baza de date slave sunt mai relaxate decât cele din master, iar în cadrul configuraţiilor peer-to-peer regulile de integritate sunt echivalente în toate locaţiile. În aceste condiţii se poate presupune că nu vor fi replicate decât actualizări realizate de tranzacţii valide la sursă.

Chiar şi în aceste condiţii, caracterul asincron al replicării poate conduce la situaţii de conflict. Ajungem astfel la subiectul cel mai interesant din cadrul acestei secţiuni: detectarea şi rezolvarea conflictelor.

În primul rând, nu orice anomalie este un conflict. Considerând scenariul (a), o actualizare a preţului unui produs P la sediul central efectuată la momentul T1 va fi replicată la filiala Mureş la momentul T2. Între momentele T1 şi T2, filiala Mureş va vinde produsul P cu un preţ incorect. E o anomalie, dar nu e un conflict. O astfel de anomalie nu poate fi detectată de mecanismul de replicare, deci rămâne pe seama aplicaţiei. O posibilă variantă ar fi ca (în locaţia replicată) un trigger de actualizare să caute în tabela de vânzări toate înregistrările introduse după momentul actualizării linei respective în locaţia primară şi să le listeze (urmând ca rezolvarea situaţiei să fie făcută manual).

Conflictele sunt caracteristice configuraţiilor cu replici actualizabile şi apar la momentul în care aplicarea actualizărilor în baza de date replicată nu este posibilă datorită unor actualizării contradictorii realizate de diverse locaţii. Cele mai comune conflicte (numite uneori coliziuni) sunt cele datorate unor erori în ordinea replicării. Iată câteva exemple:

  • Replicarea unei operaţii DELETE nu găseşte linia care trebuie ştearsă (conflict de ştergere). Explicaţia poate fi faptul că în timpul propagării, o altă operaţie a actualizat linia.

  • O operaţie UPDATE replicată nu găseşte linia care trebuie actualizată (conflict de modificare). Explicaţia este aceeaşi ca în cazul ştergerii.

  • O operaţie INSERT replicată găseşte deja în replică o linie având aceeaşi cheie primară (conflict de unicitate). Explicaţia poate fi faptul că în timpul propagării, a intervenit o operaţie de inserare sau o operaţie de modificare asupra cheii primare a unei linii existente.

Multe sisteme permit aplicarea unor reguli prestabilite pentru rezolvarea automată a unor astfel de conflicte. Cele mai comune sunt regulile bazate pe mărci de timp (timestamps). Există şi metode de rezolvare a conflictelor bazate pe un sistem de priorităţi acordate locaţiilor şi/sau utilizatorilor. O altă variantă de rezolvare o constituie construirea unor proceduri stocate care să fie executate atunci când este detectat un conflict. Acestea au acces la toate informaţiile referitoare la actualizările conflictuale, prin urmare pot combina metode bazate pe mărci de timp, priorităţi sau alte reguli specifice aplicaţiei.

Informix, de exemplu, permite rezolvarea automată a conflictelor prin mărci de timp (ultima actualizare învinge), prin proceduri stocate (logica acesteia determină actualizarea care învinge) sau prin... ignorarea conflictului. Dacă pentru o anumită replică se alege regula mărcilor de timp, se poate adăuga ca regulă secundară lansarea unei proceduri stocate (pentru cazul în care actualizările în conflict au aceeaşi marcă de timp). Un detaliu: pentru ca regulile bazate pe mărci de timp să poată funcţiona corect, sistemele participante trebuie să aibă ceasurile sincronizate şi să ţină seama de eventualele diferenţe de fus orar.

Să considerăm un exemplu, în versiune Informix. Fie trei locaţii (A, B şi C) în configuraţie peer-to-peer şi o tabelă Pers în care a fost inserată o linie prin instrucţiunea:

      INSERT INTO pers 
        VALUES (1325, 'Vasile', 'MS'); 

Regula de rezolvare a conflictelor este cea bazată pe mărci de timp. Considerăm ca la momentul T0 (să zicem, ora 12:00), linia există în toate replicile.

La momentul T1 (12:05), la locaţia B se execută instrucţiunea:

      UPDATE pers SET jud = 'CJ' 
       WHERE marca = 1325; 

La momentul T2 (12:10), la locaţia C se execută instrucţiunea:

      DELETE FROM pers 
      WHERE marca = 1325 
      AND nume = 'Vasile' 
      AND jud = 'MS' 

La momentul T3 (13:00), actualizările de la locaţia C sosesc la locaţia A şi sunt aplicate în tabela Pers. Linia cu cheia primară 1325 este ştearsă din tabelă Pers. Sistemul păstrează însă linia într-o "umbră" (shadow) a tabelei.

La momentul T4 (13:15), sosesc la locaţia A actualizările de la B. Se încearcă aplicarea modificării, care însă nu găseşte linia. Sistemul detectează astfel apariţia unui conflict.

Deoarece linia cu cheia primară căutată lipseşte din tabela Pers, se scanează "umbra". Este găsită linia în cauză şi se compară marca de timp aplicată de ultima actualizare (în cazul nostru T2, adică 12:10) cu marca de timp a noii actualizări (T1, adică 12:05). Este considerată învingătoare actualizarea cu marca de timp mai mare (în cazul nostru, ştergerea), deci modificarea nu se mai aplică şi linia rămâne ştearsă.

Faptul că actualizarea de la C au sosit mai repede decât cele de la B este întâmplător. Ce s-ar fi întâmplat dacă ordinea ar fi fost inversată? S-ar fi aplicat întâi modificarea (jud ar fi devenit CJ) după care ştergerea n-ar fi găsit linia (observaţi importanţa condiţiilor suplimentare pentru ştergere). S-ar fi căutat linia cu cheia primară 1325 şi s-ar fi comparat mărcile de timp. Ştergerea ar fi fost declarată învingătoare, deci s-ar fi anulat modificarea şi s-ar fi şters linia.

Este de remarcat faptul că în ambele cazuri s-ar fi ajuns la acelaşi rezultat, care ar fi fost apoi replicat către celelalte locaţii. Să observăm că dacă instrucţiunea UPDATE ar fi modificat cheia primară, putem presupune că ştergerea n-ar mai fi putut găsi linia dinainte de modificare. Nu este aşa, deoarece la evaluarea lui UPDATE, aceasta ar fi fost înlocuită cu o ştergere urmată de o înserare, deci linia ar fi fost găsită în tabela "umbră".

S-ar putea însă ca această rezolvare să nu fie ceea ce ne-am dorit. Poate am fi dorit ca prima actualizare să învingă. Unele sisteme permit stabilirea unei astfel de reguli, dar în Informix va trebui să scriem o procedură stocată în acest scop. (Dacă am fi ales varianta "ignore" am fi obţinut rezultate diferite în funcţie de momentul replicării.)

O ultimă precizare: Informix permite şi stabilirea aplicabilităţii regulii de rezolvare la nivel de linie sau la nivel de tranzacţie. În primul caz, este posibil ca dintre actualizările realizate în cadrul unei tranzacţii unele să fie aplicate şi altele nu. Dacă rezolvarea se face la nivel de tranzacţie, atunci fie întreaga tranzacţie este aplicată, fie este anulată (rollback). Această ultimă variantă este menită să păstreze integritatea referenţială.

Prevenirea conflictelor

Conflictele reprezintă partea cea mai sensibilă a sistemelor distribuite pe bază de replicare. O regulă de bun simţ spune că este preferabil ca acestea să fie prevenite decât să se mizeze pe rezolvarea lor automată. În scopul prevenirii conflictelor, varianta cea mai bună este stabilirea strictă a apartenenţei datelor şi evitarea pe cât posibil a configuraţiilor peer-to-peer. În exemplul precedent, datele despre personalul unei companii se pretează cel mai bine la partajare, astfel încât drepturile de actualizare să fie deţinute de fiecare filială doar pentru angajaţii proprii. În aceste condiţii, transferul unui angajat de la filiala Mureş la filiala Cluj (operaţie care a fost simulată în exemplu) s-ar petrece puţin mai complicat (persoana ar ştearsă din baza de date la Mureş şi ar fi inserată din nou la Cluj) dar de bună seamă mai sigur.

Tot o metodă de prevenire a conflictelor poate fi considerată tehnologia promovată de IBM, care propune înlocuirea configuraţiilor peer-to-peer cu configuraţii ierarhice cu un nod de referinţă. În această configuraţie, toate actualizările sunt replicate întâi spre nodul de referinţă (rădăcina ierarhiei), de unde sunt apoi replicate către celelalte noduri. Eventualele conflicte sunt rezolvate la nodul de referinţă, printr-o metodă bazată pe aşa-numitele "tranzacţii de compensare". Atunci când două tranzacţii încearcă să realizeze actualizări contradictorii, una dintre ele este aleasă ca "victimă". Aceasta va fi anulată la nivelul nodului de referinţă, după care sistemul va genera o "tranzacţie de compensare", care va fi propagată în scopul de a anula efectele tranzacţiei "victimă" la nivele inferioare ale ierarhiei. Este interesant de notat că această metodă funcţionează şi în cazul conflictelor mai complexe, implicând date din mai multe tabele.

Să considerăm, ca exemplu, o configuraţie peer-to-peer cu trei noduri (A şi B), în care avem definite tabelele F (furnizori) şi P (produse), asociate printr-o cheie străină (f_id) în tabela P (orice produs trebuie să aparţină unui furnizor). Regula pentru ştergere în restricţia referenţială pentru cheia străină este ON DELETE RESTRICT (un furnizor nu poate fi şters decât dacă nu are nici un produs care să-i fie asociat).

Să considerăm că la locaţia A s-a făcut inserarea:

      INSERT INTO F VALUES (12, ...); 

Să mai presupunem că nu există nici un produs de la furnizorul 12 şi că această inserare a fost replicată cu succes, astfel încât la momentul T0 (ora 12:00) starea este consistentă.

La momentul T1 (12:05), la locaţia A se execută instrucţiunea:

      DELETE FROM F WHERE id = 12; 

La momentul T2 (12:07), la locaţia B se execută instrucţiunea:

      INSERT INTO P 
      VALUES (1023, 12, 'Ciocan'); 

La momentul T3 (12:10), la locaţia A soseşte mesajul cu actualizarea produsă la momentul T2 la B. Inserarea nu se poate face deoarece nu există furnizorul referit (id = 12).

La momentul T4 (12:15), la locaţia B este replicată ştergerea efectuată la momentul T1 la locaţia A. Furnizorul cu codul 12 nu poate fi şters deoarece există un produs care-l referă.

Observăm că s-a ajuns la o stare inconsistentă: la nodul A nu mai există nici furnizorul 12 nici produsul 1023, în timp ce la nodul B ambele există.

În varianta propusă de IBM, vom considera că un nod (C) ca fiind nod de referinţă. Considerând momentele aceleaşi operaţii în nodurile A şi B, la aceleaşi momente (T1 şi T2), scenariul poate fi continuat astfel:

La momentul T3 (12:10), inserarea de la momentul T2 din nodul B este replicată în nodul C. Este aplicată cu succes, după care este trimisă spre replicare nodului A.

La momentul T4 (12:15), ştergerea de la momentul T1 din nodul A este replicată în nodul C. Deoarece furnizorul 12 are un produs care-l referă, nu poate fi şters. Se aplică o metodă de rezolvare a conflictului, de pildă last timpstamp wins. Conform acesteia, tranzacţia care a comis inserarea învinge, iar cea care a comis ştergerea este considerată "victimă". Prin urmare, ştergerea nu este replicată către B şi se generează o tranzacţie de compensare care este trimisă către A. Aceasta ar putea fi de forma:

      BEGIN WORK; 
      DELETE FROM P 
        WHERE cod = 1023 
        AND f_id = 12 
        AND den = 'Ciocan'; 
      INSERT INTO F 
        VALUES (12, ...); 
      INSERT INTO P 
        VALUES (1023, 12, 'Ciocan'); 
      COMMIT WORK; 

Chiar dacă nu s-au evitat astfel conflictele, prin centralizare numărul lor s-a redus iar posibilităţile de rezolvare au sporit. Cu toate că nodurile sunt consistente, problema nu poate fi considerată complet rezolvată. Considerând că tranzacţia de compensare a fost comisă în nodul A la momentul T5 (să zicem 12:17), orice tranzacţie care a citit tabela F de la nodul A, a avut ocazia să ia o decizie greşită bazată pe faptul că furnizorul cu codul 12 a lipsit din tabelă.

Concluzia finală este că nici una dintre tehnicile de rezolvare a conflictelor nu este infailibilă şi că vor exista mereu situaţii în care se impun intervenţii manuale. Este un preţ care trebuie plătit în schimbul avantajelor pe care replicarea asincronă le oferă. Corolarul acestei concluzii este că pentru anumite probleme replicarea asincronă este aplicabilă şi pentru altele nu.

Ce-a mai rămas?

Nu am detaliat în această prezentare subiecte extrem de interesante cum ar replicarea actualizărilor în schema bazelor de date sau replicarea prin apeluri asincrone de proceduri la distanţă (asynchronous RPC). Nu am vorbit despre mecanismele de replicare la nivel de câmp şi de rezolvările conflictelor prin versiuni multiple din Lotus Notes.

Nu am vorbit despre replicarea procedurală practicată de Oracle şi nici despre modelele de propagare pull şi push utilizate de IBM şi de alţi producători. Am amintit doar de problemele speciale pe care le ridică replicarea pentru lucrătorii mobili.

O problematică largă este replicarea în medii eterogene, cuprinzând baze de date de la producători diferiţi. În fine, nu am amintit despre produsele independente de la terţi producători, cum ar fi PeerDirect sau ThinkNet. Şi ar mai fi multe altele.

Speranţa mea este că noţiunile pe care am încercat să le prezint în aceste (poate prea multe) pagini vă vor fi de folos atunci când vă veţi confrunta direct cu problematica replicării.

Sau măcar că v-am stârnit curiozitatea.


 

(Publicat în PC Report 87 - decembrie 1999)

 

Copyright © 1999 Agora Media

Creative Commons License
This work is licensed under a Creative Commons License.