Tema 2 - Distributed Database

Responsabili:

  • Data publicării: 08.04.2024
  • Deadline HARD: 07.05.2024 23:59:59

Actualizări

  • 10 aprilie - Am actualizat deadline-ul hard - 7 mai, ora 23:59
  • 11 aprilie - Am adăugat un exemplu de test, cu ref-ul corespunzător
  • 17 aprilie - Am actualizat scheletul - am reglat erorile de coding style din schelet - vezi utils.h - și am modificat valoarea constantei MAX_LOG_LENGTH
  • 17 aprilie - Am adăugat checker-ul și testele
  • 25 aprilie - Am actualizat ref-urile testelor 26-30.

Obiective

În urma realizării acestei teme veţi:

  • Învăţa aplicaţii utile şi populare ale hash-urilor existente în literatură.
  • Exersa implementarea unui sistem mai complex urmând o descriere detaliată a fiecărei componente.
  • Căpăta intuiţie despre limitările unui volum mare de date şi despre sistemele distribuite.
  • Înțelege utilitatea cache-ului în sistemele reale.

Introducere

După ce a învățat să implementeze propriul alocator de memorie, Marcel, student în anul I, își dorește să iasă temporar din lumea sistemelor de operare și să aibă o privire de ansamblu asupra modului în care companiile mari oferă servicii de stocare în cloud. Om simplu, el a decis să rivalizeze cu AWS și să își creeze propriul sistem distribuit de stocare, pe care să îl poată folosi împreună cu colegii de facultate.

Având un volum foarte mare de date, Marcel a intuit că trebuie să folosească mai multe servere, scopul fiecăruia fiind să stocheze un subset din datele pe care vrea să le păstreze. Lovindu-se pentru prima dată de un sistem format din mai multe entități în afara de propriul laptop, Marcel a început să citească despre concepte de system design folosite frecvent în proiectarea eficientă a sistemelor distribuite: caching, load balancing, consistent hashing, task queues. Rolul vostru este să îl ajutați pe Marcel să pună în practică tot ceea ce a aflat în incursiunea lui prin lumea sistemelor distribuite.

Cerință

Scopul proiectului este de a dezvolta o bază de date distribuită în care se păstrează documente. Pentru a optimiza accesul la documentele utilizate frecvent, se dorește implementarea unui sistem de caching bazat pe algoritmul LRU (Least Recently Used). Acesta este mecanism de evacuare a intrărilor din cache, conform căruia, în momentul în care nu mai există spațiu suficient pentru a insera noi date, sunt eliminate cele mai vechi valori existente.

În plus, pentru a concepe un sistem cât mai eficient, ne dorim ca baza noastra de date sa foloseasca multiple servere, între care sa se distribuie uniform documentele stocate. Pentru aceasta, vom folosi conceptul de Load Balancing, folosind Consistent Hashing. Acesta este un mecanism frecvent utilizat în cadrul sistemelor distribuite și are avantajul de a îndeplini minimal disruption constraint, adică minimizarea numărului de transferuri necesare atunci când un server este oprit sau pornit. Mai exact, când un server este oprit, doar obiectele aflate pe acesta trebuie redistribuite către servere apropiate. Analog, când un nou server este adăugat, va prelua obiecte doar de la un număr limitat de servere, cele vecine.

Cache-ul reprezintă un layer de stocare utilizat pentru memorarea temporară a datelor care au o probabilitate mare de a fi refolosite, astfel încât accesările ulterioare la aceste date să necesite un timp mult mai scurt față de obținerea lor din locația originală de memorie.

Load Balancer este componenta care are rolul de a dirija uniform traficul către un set de servere cu o putere limitata de procesare. Acesta are misiunea de a asigura că toate serverele stochezează și procesezează un volum similar date pentru a maximiza eficienta întregului sistem. În cazul nostru, obiecte din sistem vor fi perechi <doc_name, doc_content> unde cheia va fi numele unui document, iar valoarea va salva conţinutul acestuia. Load balancer-ul e responsabil de a decide pe ce server va fi salvat un obiect în funcție de cheia acestuia. Mecanismul eficient prin care acesta mapează un obiect <doc_name, doc_content> unui server_id poartă numele de Consistent Hashing şi este descris în detaliu într-o secțiune următoare.

O metoda simpla de a implementa un load balancer se poate realiza prin asignarea unei sarcini către un server responsabil folosind următoarea formula: server_id = hash(data) % NUM_SERVERS

Astfel, load balancer-ul îşi îndeplineşte scopul şi reuşeşte să distribuie uniform datele spre toate cele NUM_SERVERS servere din sistem. În schimb, dezavantajul acestei metode este faptul că într-un sistem real numărul de servere nu este niciodata constant. Dacă unul din servere dispare, din cauza acestei metode simpliste ar trebui să redirijam toate datele din sistem.

Exemplu:

  1. NUM_SERVERS = 10
  2. Vream sa stocam documentul denumit “123” cu conţinutul “iPhone 12” în sistem. hash(“123”) = 0x65b71f5b. Serverul care va stoca această informaţie va fi: server_id = 0x65b71f5b % 10 = 1
  3. Un server dispare. NUM_SERVERS = 9 Vor trebui verificate toate obiectele din sistem pentru că hash(data) % NUM_SERVERS va avea altă valoare față de cea precedentă.
  4. Pentru documentul cu numele “123” adăugat anterior, avem: server_id = hash(“123”) % NUM_SERVERS = 0x65b71f5b % 9 = 4 ⇒ În trecut era salvat pe serverul 1, dar acum va trebui mutat pe serverul cu id-ul 4.
  5. De asemenea, și id-urile serverelor se vor shifta pentru a avea indici consecutivi.

Observăm că această metodă este foarte ineficientă.

Detaliile Sistemului

În cadrul acestei teme, vă propunem organizarea întregului proiect sub forma unei ierarhii cu următoarele componente: un client, un load balancer și mai multe servere.

Load balancer-ul este prima componentă din sistem. Acesta se ocupă de distribuirea request-urilor primite de la clienți. În cazul nostru, există 2 tipuri de request-uri:

  • adresate serverelor- care au ca scop manipularea documentelor stocate pe servere (EDIT_DOCUMENT, GET_DOCUMENT)
  • adresate load balancer-ului - care au ca scop administrarea topologiei de servere (ADD_SERVER, REMOVE_SERVER)

În primul caz, load balancer-ul decide spre ce server trebuie redirecționat request-ul, prelucrarea acestuia fiind făcută la nivelul serverului.

În cel de-al doilea caz, se adaugă sau se șterg servere din sistem și se rebalansează stocarea serverelor astfel încât să nu se piardă documentele, utilizând logica explică la Consistent Hashing.

Server(50p)

Pentru fiecare server se respectă următoarea arhitectură, bazată pe o coadă de task-uri, un cache și o memorie principală (o bază de date):

Interacțiunea cu server-ul se face prin intermediul unui request. Există două tipuri de request-uri. Pentru ca server-ul să facă minimul de operații necesare, vom folosi o coadă, a cărei dimensiune maximă este TASK_QUEUE_SIZE = 1000, în care păstram request-urile care ar trebui să modifice conținutul unui document. Astfel, execuția lor este amânată până în momentul în care un request de tip GET_DOCUMENT este primit, urmând a fi rulate toate request-urile stocate până atunci în coadă, trebuind să se afișeze un răspuns corespunzător pentru fiecare request.

Odată ce un request este executat, server-ul returnează o structura de tip response, cu următoarele câmpuri:

  • server_id - id-ul server-ului care a executat request-ul
  • server_response - un string de lungime maximă MAX_RESPONSE_LENGTH, putând conține fie conținutul documentului cerut, fie un mesaj tip în funcție de flow-ul request-ului în server, fiind definit prin macro-urile: MSG_A, MSG_B, MSG_C
  • server_log - un string de lungime maximă MAX_LOG_LENGTH, care indică informații relevante referitoare la starea curentă a serverului, fiind definit prin macro-urile: LOG_HIT, LOG_MISS, LOG_EVICT, LOG_FAULT, LOG_LAZY_EXEC

La nivelul server-ului sunt definite următoarele operații:

  • editează un document (“EDIT <document_name> <conținut_document>“)
  • obține conținutul unui document (“GET <document_name>“)

Când se primește orice request:

  • executat lazy - se returnează “Request- <request_type> <document_name> - has been added to queue”
  • executat direct - se returnează conținutul efectiv

În continuare, explicăm flow-ul execuției fiecărui tip de request în momentul în care este rulat.

  • EDIT
    1. Se adaugă request-ul în coada de task-uri si se intoarce un struct response, in care:
      • server_response = MSG_A: “Request- EDIT <doc_name> - has been added to queue”
      • server_log = LOG_LAZY_EXEC: “Task queue size is <size_of_task_queue>“
    2. În momentul în care trebuie executat, se scoate task-ul din front-ul cozii și se stochează conținutul documentului în cache.
    3. Dacă nu există, se dă un CACHE MISS, este adus din memoria principală în cache conținutul asociat documentului existent;
    4. Se scrie in cache si in baza de date continutul asociat cu numele documentului
    5. Membrul server_response al structurii response returnate trebuie asociat cu unul dintre urmatoarele mesaje:
      • MSG_B: “Document <doc_name> has been overridden”
      • MSG_C: “Document <doc_name> has been created”
    6. Câmpul server_log din response trebuie asociat, in functie de caz, cu unul dintre mesajele:
      • LOG_HIT: “Cache HIT for <doc_name>”
      • LOG_MISS: “Cache MISS for <doc_name>”
      • LOG_EVICT: “Cache MISS for <doc_name> - cache entry for <evicted_doc_name> has been evicted”
  • GET
    1. Se execută fiecare operație stocată în coada de task-uri (respectând ordinea impusă de coadă) şi se afişează response-ul returnat de fiecare operaţie, folosind macro-ul PRINT_RESPONSE.
    2. După ce se execută toate task-urile din coadă, server-ul verifică dacă există o intrare în cache:
      1. Dacă există, se returnează.
      2. Altfel, server-ul se uită în memoria principală:
        1. dacă există, îl aduce în cache și îl returnează;
        2. dacă nu există, se returnează un mesaj de eroare.
    3. Câmpul server_response al structurii response returnate trebuie sa fie un pointer către o clonă a conținutului documentului, în cazul în care acesta există pe server. Altfel, trebuie să fie NULL
    4. server_log contine unul din mesajele prezentate la GET sau:
      1. LOG_FAULT: “Document <doc_name> doesn't exist”, in cazul in care documentul cautat nu exista

LRU Cache

În cazul sistemului nostru, va trebui să implementăm un cache software, care să se bazeze pe un hashtable, în care numele documentelor reprezintă cheile, iar conținutul - valoarea. Fiecare server din topologie va avea o anumită dimensiune a cache-ului, exprimată ca număr de intrări, și pasată ca argument la crearea unui nou server (vezi operația de ADD_SERVER).

La nivel conceptual, acest cache reduce timpul de acces al fiecărui server la documentele păstrate în baza de date locală. Practic, în cazul nostru, atât baza de date, cât și cache-ul fiecărui server vor fi stocate în RAM, pentru ușurința implementării, așa că avantajele utilizării unui cache software nu vor fi vizibile dacă urmărim timpii de acces la documente.

Având o dimensiune limitată, la un moment dat vom ajunge în situația în care dorim să adăugăm o nouă intrare în cache, însă acesta este deja plin. Pentru a trata acest scenariu, va trebui să decidem ce intrare eliminăm din cache pentru a face spațiu pentru noile date.

Least Recently Used (LRU) Cache se bazează pe principiul că există o probabilitate foarte mare ca datele accesate cel mai recent să fie accesate din nou în viitorul apropiat. Astfel, în momentul în care cache-ul s-a umplut, politica de evacuare a elementelor din cache specifică faptul că trebuie eliminată intrarea care a fost accesată în urmă cu cel mai mult timp.

Implementarea unui astfel de cache necesită și folosirea unei structuri sau mai multe structuri de date care să rețină ordonate documentele în funcție de momentul accesării

Cea mai uzuală metodă de a implementa acest cache este prin:

  1. o listă dublu înlănțuită, care va reține ordinea în care au fost accesate intrările din cache, la finalul listei fiind poziționate intrările cu care am interacționat cel mai recent, iar la începutul listei fiind cele mai vechi intrări
  2. un hashmap, în care se păstrează cheile (intrărilor din cache) asociate cu pointeri la nodurile corespunzătoare din listă

Aveți libertatea de a implementa în ce mod doriți, cât timp respectați proprietățile unui LRU Cache(adăugarea și ștergea să fie în O(1), iar, de fiecare dată când trebuie eliminată o intrare, să fie eliminată cea mai veche)

În momentul in care o valoare asociata unei chei deja existente in cache trebuie actualizata, vom aplica o politica de scriere write-through, actualizand valoarea atat in cache, cat si in baza de date din memoria locala a serverului, pentru a pastra coerenta dintre cache si memorie.

Funcțiile principale pe care cache-ul vostru trebuie să le implementeze sunt:

  • put(document_name, document_content)
    • verifică dacă există deja în cache un document cu numele primit
    • dacă există, actualizează conținutul
    • dacă nu există:
      • în cazul în care este suficient spațiu liber în cache, introduce o nouă intrare
      • altfel, elimină cea mai veche intrare
  • get(document_name)
    • returnează conținutul asociat cu numele primit ca parametru

Pentru ambele operații trebuie luat în calcul că, după execuția lor, intrarea asociată cu numele documentului primit ca parametru este cea mai recentă din cache.

Load Balancer(30p)

Distribuția serverelor poate fi modificată folosind următoarele comenzi:

  • ADD_SERVER <server_id> <number_of_cached_documents>
  • REMOVE_SERVER <server_id>

Inițial, nu există niciun server în sistem. În momentul în care load balancer-ul primește una dintre comenzi, anumite documente și task-uri sunt redistribuite între servere (vezi logica de la Consistent Hashing).

Astfel, flow-ul inerent fiecărei comenzi referitoare la topologie este:

  1. (Doar pentru ADD_SERVER) Se creează server-ul cu un cache gol.
  2. Se dau pop-uri succesive pe coada de task-uri a serverului sursă, se executa fiecare comandă eliminată din coadă, şi se foloseşte macro-ul PRINT_RESPONSE pe fiecare rezultat returnat
  3. Se parcurg toate documentele stocate în baza de date locală a serverului sursă, iar cele care trebuie mutate sunt eliminate din cache şi mutate

în baza de date a serverului destinaţie (fară a fi adăugate în cache).

Prin server sursă, ne referim la:

  1. serverul vecin de pe care vom prelua documente, pentru ADD_SERVER
  2. serverul care este eliminat din topologie, în cazul operaţiei de REMOVE_SERVER

Prin server destinaţie, ne referim la:

  1. serverul nou adăugat în topologie, pentru ADD_SERVER
  2. serverul vecin care va primi toate documentele din baza de date a serverului şters, în cazul operaţiei de REMOVE_SERVER

Consistent Hashing

Consistent Hashing este o metoda de hashing distribuit prin care atunci când tabelul este redimensionat, se vor remapa în medie doar n / m chei, unde n este numărul curent de chei, iar m este numărul de slot-uri (în cazul nostru servere). În implementare, se va folosi un cerc imaginar numit hash ring pe care sunt mapate atât numele documentelor, cât și id-urile serverelor. Această mapare se realizează cu ajutorul unei funcții de hashing cu valori între MIN_HASH și MAX_HASH, punctele din acest interval fiind distribuite la distante egale în intervalul logic [0°, 360°] de pe cercul imaginar.

Fiecare document trebuie sa aparţină unui singur server. Astfel, serverul responsabil să stocheze un anumit document va fi cel mai apropiat de pe hash ring în direcţia acelor de ceas.

Să presupunem că avem următorul set de servere ce urmează să primească documente şi rezultatul funcției de hashing după ce a fost aplicată pe id-ul acestora:

ID Server Hash
2 2269549488
0 5572014558
1 8077113362

Acum vom adăuga în sistem o mulţime de documente ce urmează a fi distribuite către serverele deja existente. În momentul în care un document este adăugat în sistem, va trebui să căutăm primul server al cărui ID hash-uit este mai “mare” (în sensul acelor de ceas, atenţie la logica circulară) decât hash-ul numelui documentului.

Pentru a produce interogări cât mai eficiente, hash ring-ul va fi reţinut în memorie ca un array ordonat crescător de etichete de servere. Ordonarea se va face în funcţie de hash, iar în cazul în care 2 etichete de servere au aceeaşi valoare hash, se va sorta crescător după ID-ul serverului.

Atentie! În implementare documentele NU vor fi stocate pe hash ring. Ele vor fi păstrate pe servere. Reprezentările grafice vor pune numele documentelor pe hash ring pentru a explica mai uşor conceptul şi a putea vizualiza care este cel mai apropiat server.

Doc Name / ID Server Hash Stored on
Jade 1633428562 2
2 2269549488 -
Pearl 3421657995 0
Sapphire 5000799124 0
0 5572014558 -
Amethyst 7594634739 1
1 8077113362 -
Ruby 9787173343 2

Eliminare

În cazul în care unul din servere este eliminat din sistem, toate replicile sale sunt eliminate de pe hash ring, iar documentele salvate pe acestea sunt remapate la cele mai apropiate servere ramase (în sensul acelor de ceas).

În exemplul nostru vom presupune că serverul cu id 0 va fi eliminat. În acest moment documentele denumite “Pearl” şi “Sapphire” vor fi redistribuite către serverul 1.

Doc Name / ID Server Hash Stored on
Jade 1633428562 2
2 2269549488 -
Pearl 3421657995 1
Sapphire 5000799124 1
Amethyst 7594634739 1
1 8077113362 -
Ruby 9787173343 2

Adăugare

În cazul adăugării unui nou server în sistem, se vor lua toate documentele de pe serverele vecine (succesoare în sensul acelor de ceas) si se va verifica dacă vor fi remapate către serverul nou sau nu. Dacă un document trebuie să fie mapat pe serverul nou, valoarea sa va fi transferată de pe serverul vechi, iar serverul vechi o va şterge.

Să presupunem că se adaugă un server cu id 3. Serverul care va fi vecin celui cu id 3 pe hash ring (serverul 1, în cazul de faţă) va trebui să îşi redistribuie documentele:

Doc Name / ID Server Hash Stored on
Jade 1633428562 2
2 2269549488 -
Pearl 3421657995 3
3 4514215965 -
Sapphire 5000799124 1
Amethyst 7594634739 1
1 8077113362 -
Ruby 9787173343 2

Bonus - Replici (20p)

În practică, pentru a ne asigura că obiectele sunt distribuite cât mai uniform pe servere se foloseşte următorul artificiu: Fiecare server va fi adaugat de mai multe ori pe hash ring (Aceste servere se vor numi “replici” sau “noduri virtuale”). Acest mecanism se va realiza prin asocierea unei etichete artificiale fiecarei replici, plecând de la id-ul server-ului de bază:

eticheta = replica_id * 10^5  + server_id;

De exemplu, replica 2 a serverului cu id-ul 24 ⇒ eticheta = 200024
Vom lucra cu cel mult 99999 servere, deci etichetele vor fi unic determinate.

În implementarea noastră, fiecare server va fi replicat de fix 3 ori (în total va fi reprezentat în 3 puncte).

Fie următorul exemplu:

Doc Name / Tag Server Hash Stored on
100000 1093130520 -
Jade 1633428562 2
000002 2269549488 -
200002 2717580620 -
Pearl 3421657995 1
200001 4633428562 -
Sapphire 5000799124 0
000000 5572014558 -
100002 6252163898 -
Amethyst 7594634739 1
100001 7613173320 -
200000 7893130520 -
000001 8077113362 -
Ruby 9787173343 0

În cazul cazul în care vrem să eliminăm serverul cu id 0, documentele denumite “Sapphire” şi “Ruby” vor fi redistribuite:

Doc Name / Tag Server Hash Stored on
Jade 1633428562 2
000002 2269549488 -
200002 2717580620 -
Pearl 3421657995 1
200001 4633428562 -
Sapphire 5000799124 2
100002 6252163898 -
Amethyst 7594634739 1
100001 7613173320 -
000001 8077113362 -
Ruby 9787173343 2

Să presupunem că se adaugă un server cu id 4. Serverele care vor fi vecine celui cu id 4 (oricare replică a lui) vor trebui să îşi redistribuie documentele:

Doc Name / Tag Server Hash Stored on
Jade 1633428562 2
000002 2269549488 -
200002 2717580620 -
Pearl 3421657995 1
200001 4633428562 -
Sapphire 5000799124 4
000004 5421603348 -
200004 6017580621 -
100002 6252163898 -
Amethyst 7594634739 1
100001 7613173320 -
000001 8077113362 -
Ruby 9787173343 4
100004 9945918562 -

În situaţia de mai sus, serverul 2 a trebuit să distribuie serverului 4 documentele cu numele “Ruby” si “Sapphire”. Este o coincidenţă că ambele documente redistribuite vin de la acelasi server, puteau veni de la replici ale unor servere diferite.

Implementare

În fișierul main.c din schelet este deja implementată partea ce ține de client si modul în care acesta apelează sistemul distribuit, dar și partea de gestiune a sistemului ce va apela funcțiile de pornire si oprire a serverelor atunci când este cazul. Trebuie să implementați doar funcționalitățile din load_balancer.c, server.c şi lru_cache.c, entry point-ul către sistemul distribuit fiind prin Load Balancer, iar toate apelurile către acesta fiind deja implementate în schelet.

În headere, veţi întâlni macro-uri pentru mesajele pe care trebuie sa le printaţi, cât şi alte macro-uri utile:

  • MAX_LOG_LENGTH - dimensiunea maximă pe care o poate avea field-ul server_log din structura response
  • MAX_RESPONSE_LENGTH - dimensiunea maximă pe care o poate avea field-ul server_response din structura response
  • PRINT_RESPONSE - primeşte un pointer la o structură de tipul response, şi printează server_id-ul serverului care a returnat-o, cât şi cele doua field-uri menţionate mai sus (în plus, dezalocă complet memoria structurii trimise ca parametru)
  • TASK_QUEUE_SIZE - dimeansiunea maximă a cozii de task-uri a fiecărui server
  • MAX_SERVERS - numărul maxim de servere din topologie

Load balancer-ul ar trebui sa interactioneze cu serverele folosind functia server_handle_request. Functiile specifice executiei fiecarui tip de request (server_edit_document, server_get_document) nu sunt expuse in server.h, si nu ar trebui sa fie apelate direct de catre o alta entitate, in afara de server.

server_handle_request va returna load balancer-ului o structura response, care va fi returnata la randul ei in main, acolo unde este printata.

Structura response va fi folosita pentru a pastra atat mesajul care trebuie printat de server la finalul executiei unei operatii, cat si alte date auxiliare pe care vi se pare important sa le memorati.

La bonus, pentru mesajele de logging, campul server_id din structura response returnata de server dupa primirea unui request trebuie sa se refere la eticheta pe care o are nodul virtual catre care se adreseaza operatia.

Pentru a face conversia din tipul request-ului(acest tip este stocat într-un enum) în numele lui efectiv, vă puteți folosi de funcția get_request_type_str din utils.h.

Aceasta poate fi folosită pentru anumite mesaje de răspuns ce necesită folosirea tipului request-ului.

Puteţi începe implementând logica de tratare a request-urilor în server, şi folosind câmpul temporar test_server din structura de load_balancer.

Odată ce aţi finalizat implementarea serverului, puteţi elimina field-ul test_server, şi să vă implementaţi propria logică de load balancing.

Testele sunt formate dintr-o linie unde se specifică numărul total de request-uri, urmat de ENABLE_VNODES (opţional, doar pentru testele bonus, în care se verifică utilizarea replicilor), respectiv cate o linie pentru fiecare request.

Click pentru input-ul unui test

Click pentru input-ul unui test

6
ADD_SERVER 100 5
EDIT "manager.txt" "Box understand feel."
GET "manager.txt"
EDIT "manager.txt" "Mouth attorney."
GET "manager.txt"
GET "produce.txt"

Click pentru output-ul unui test

Click pentru output-ul unui test

[Server 100]-Response: Request- EDIT manager.txt - has been added to queue
[Server 100]-Log: Task queue size is 1
 
[Server 100]-Response: Document manager.txt has been created
[Server 100]-Log: Cache MISS for manager.txt
 
[Server 100]-Response: Box understand feel.
[Server 100]-Log: Cache HIT for manager.txt
 
[Server 100]-Response: Request- EDIT manager.txt - has been added to queue
[Server 100]-Log: Task queue size is 1
 
[Server 100]-Response: Document manager.txt has been overridden
[Server 100]-Log: Cache HIT for manager.txt
 
[Server 100]-Response: Mouth attorney.
[Server 100]-Log: Cache HIT for manager.txt
 
[Server 100]-Response: (null)
[Server 100]-Log: Document produce.txt doesn't exist

Restricţii

  • Se execută cel puţin o operaţie de ADD_SERVER înainte de orice prelucrare de fişiere.
  • Pentru a stabili poziţia unui server pe hash ring, se va folosi funcţia hash_uint(), aplicată pe ID-ul serverului (în cazul bonusului, se va aplica pe eticheta nodului virtual).
  • Pentru a stabili unde s-ar poziţiona un document pe hash ring, se va folosi funcţia hash_string(), aplicată pe numele documentului.
  • Numărul de request-uri care ar trebui stocate la un moment dat in task_queue nu va depaşi niciodată dimensinea maximă a queue-ului, TASK_QUEUE_SIZE.
  • La bonus, pentru nodurile virtuale nu trebuie alocate un cache şi o bază de date noi

Schelet

Scheletul și checker-ul se regăsesc în acest repository de Github.

Punctaj

  • 80p teste: fiecare test este verificat cu valgrind. Dacă un test are memory leaks, nu va fi punctat.
    • Testele 0-10 testează implementarea serverului
    • Testele 11-20 testează implementarea load balancer-ului
  • 10p coding style
  • 10p README
  • 20p BONUS 😀
    • Testele 21-30 testează implementarea replicilor în load balancer
  • O tema care nu compilează va primi 0 puncte.

Nu copiați! Toate soluțiile vor fi verificate folosind o unealtă (⛏️) de detectare a plagiatului. În cazul detectării unui astfel de caz, atât plagiatorul cât și autorul original (nu contează cine e) vor primi punctaj 0 pe toate temele! De aceea, vă sfătuim să nu vă lăsați rezolvări ale temelor pe calculatoare partajate (la laborator etc), pe mail/liste de discuții/grupuri etc.

FAQ

Q: Putem modifica scheletul / adăuga funcţii?
A: Da.

Q: Ce funcţii de hashing putem folosi pentru server?
A: Puteţi folosi orice funcţie doriţi atât pentru baza de date a serverului, cât şi pentru cache (inclusiv pe cele din laborator/schelet).

sd-ca/teme/tema2-2024.txt · Last modified: 2024/04/25 18:18 by andrei.otetea
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0