Registrul distribuit pentru seturi de roți: o experiență cu Hyperledger Fabric

Bună ziua, Lucrez în echipa proiectului DRD KP (registru de date distribuite pentru monitorizarea ciclului de viață al seturilor de roți). Aici vreau să împărtășesc experiența echipei noastre în dezvoltarea unui blockchain enterprise pentru acest proiect sub constrângerile tehnologiei. Voi vorbi mai ales despre Hyperledger Fabric, dar abordarea descrisă aici poate fi extrapolată la orice blockchain autorizat. Scopul final al cercetării noastre este de a pregăti soluții de tip blockchain pentru întreprinderi, astfel încât produsul final să fie plăcut de utilizat și să nu fie prea dificil de întreținut.

Nu vor exista descoperiri, soluții neașteptate și nicio dezvoltare unică nu va fi evidențiată aici (pentru că nu am). Vreau doar să împărtășesc experiența mea modestă, să arăt că „a fost posibil” și, poate, să citesc despre experiențele altora de a lua decizii bune și nu atât de bune în comentarii.

Problemă: Blockchain-urile nu se extind încă

Astăzi, eforturile multor dezvoltatori vizează să facă din blockchain o tehnologie cu adevărat convenabilă, și nu o bombă cu ceas într-un înveliș frumos. Canalele de stat, acumularea optimistă, plasmă și fragmentarea vor deveni probabil obișnuite. Într-o zi. Sau poate TON va amâna din nou lansarea cu șase luni, iar următorul Grup Plasma va înceta să mai existe. Putem crede în următoarea foaie de parcurs și putem citi documente albe strălucitoare noaptea, dar aici și acum trebuie să facem ceva cu ceea ce avem. gata.

Sarcina stabilită echipei noastre în proiectul actual arată în general astfel: sunt mulți subiecți, ajungând la câteva mii, care nu doresc să construiască relații pe încredere; Este necesar să se construiască o soluție pe DLT care să funcționeze pe computere obișnuite fără cerințe speciale de performanță și să ofere o experiență de utilizator nu mai rea decât orice sistem de contabilitate centralizat. Tehnologia din spatele soluției trebuie să minimizeze posibilitatea manipulării rău intenționate a datelor - de aceea blockchain este aici.

Sloganurile din cărțile albe și mass-media ne promit că următoarea dezvoltare ne va permite să facem milioane de tranzacții pe secundă. Ce este de fapt?

Mainnet Ethereum rulează în prezent la ~30 tps. Numai din acest motiv, este dificil să îl percepem ca blockchain în vreun fel potrivit pentru nevoile corporative. Printre soluțiile permise există benchmark-uri care arată 2000 tps (Cvorum) sau 3000 tps (Hyperledger Fabric, există puțin mai puțin în publicație, dar trebuie să țineți cont de faptul că benchmark-ul a fost efectuat pe vechiul motor de consens). A fost o încercare de prelucrare radicală a țesăturilor, care nu a dat cele mai proaste rezultate, 20000 tps, dar până acum aceasta este doar cercetare academică, care așteaptă implementarea ei stabilă. Este puțin probabil ca o corporație care își poate permite să mențină un departament de dezvoltatori blockchain să suporte astfel de indicatori. Dar problema nu este doar debitul, există și latența.

Latență

Întârzierea de la momentul inițierii unei tranzacții până la aprobarea finală a acesteia de către sistem depinde nu numai de viteza cu care mesajul trece prin toate etapele de validare și comandă, ci și de parametrii de formare a blocurilor. Chiar dacă blockchain-ul nostru ne permite să ne angajăm la o viteză de 1000000 de tps, dar necesită 10 minute pentru a genera un bloc de 488 MB, ne va deveni mai ușor?

Să aruncăm o privire mai atentă asupra ciclului de viață al tranzacției în Hyperledger Fabric pentru a înțelege unde este petrecut timpul și cum se leagă acesta cu parametrii de generare a blocurilor.

Registrul distribuit pentru seturi de roți: o experiență cu Hyperledger Fabric
luat de aici: hyperledger-fabric.readthedocs.io/en/release-1.4/arch-deep-dive.html#swimlane

(1) Clientul creează o tranzacție, o trimite către colegii de aprobare, aceștia din urmă simulează tranzacția (aplică modificările făcute prin codul de lanț la starea curentă, dar nu se angajează în registru) și primesc RWSet - nume chei, versiuni și valori ​​preluate din colecția din CouchDB, (2) susținătorii trimit înapoi clientului un RWSet semnat, (3) clientul fie verifică prezența semnăturilor tuturor colegilor necesari (gizatorii), apoi trimite tranzacția către serviciul de comandă. , sau îl trimite fără verificare (verificarea va avea loc mai târziu), serviciul de comandă formează un bloc și ( 4) trimite înapoi tuturor colegilor, nu doar avizelor; colegii verifică dacă versiunile cheii din setul de citire se potrivesc cu versiunile din baza de date, că toți susținătorii au semnături și, în final, commit blocarea.

Dar asta nu este tot. Cuvintele „comandantul formează un bloc” ascund nu numai ordonarea tranzacțiilor, ci și 3 solicitări succesive de rețea de la lider la adepți și înapoi: liderul adaugă un mesaj în jurnal, îl trimite adepților, acesta din urmă îl adaugă la jurnalul lor, trimite confirmarea replicării reușite liderului, liderul comite mesajul, trimite confirmare de comitere adepților, adepții commit. Cu cât dimensiunea și timpul formării blocului sunt mai mici, cu atât serviciul de comandă va trebui să stabilească un consens mai des. Hyperledger Fabric are doi parametri pentru formarea blocurilor: BatchTimeout - timpul de formare a blocului și BatchSize - dimensiunea blocului (numărul de tranzacții și dimensiunea blocului în sine în octeți). Imediat ce unul dintre parametri atinge limita, un nou bloc este eliberat. Cu cât sunt mai multe noduri de ordine, cu atât va dura mai mult. Prin urmare, trebuie să creșteți BatchTimeout și BatchSize. Dar, deoarece RWSets sunt versiuni, cu cât blocul pe care îl facem este mai mare, cu atât este mai mare probabilitatea de conflicte MVCC. În plus, pe măsură ce BatchTimeout crește, UX-ul se degradează catastrofal. Următoarea schemă pentru rezolvarea acestor probleme mi se pare rezonabilă și evidentă.

Cum să evitați așteptarea finalizării blocului și să nu pierdeți capacitatea de a urmări starea tranzacției

Cu cât timpul de formare și dimensiunea blocului sunt mai lungi, cu atât debitul blockchain-ului este mai mare. Unul nu decurge direct de celălalt, dar trebuie amintit că stabilirea consensului în RAFT necesită trei solicitări de rețea de la lider la adepți și înapoi. Cu cât sunt mai multe noduri de ordine, cu atât va dura mai mult. Cu cât dimensiunea și timpul formării blocului sunt mai mici, cu atât există mai multe astfel de interacțiuni. Cum să măresc timpul de generare și dimensiunea blocului fără a crește timpul de răspuns al sistemului pentru utilizatorul final?

În primul rând, trebuie să rezolvăm cumva conflictele MVCC cauzate de o dimensiune mare a blocului, care poate include diferite RWS-uri cu aceeași versiune. Evident, din partea clientului (în legătură cu rețeaua blockchain, acesta ar putea fi backend-ul și vreau să spun serios) aveți nevoie Gestionarea conflictelor MVCC, care poate fi fie un serviciu separat, fie un decorator obișnuit deasupra apelului care inițiază tranzacția cu logica de reîncercare.

Reîncercarea poate fi implementată cu o strategie exponențială, dar apoi latența se va degrada la fel de exponențial. Deci, ar trebui să utilizați fie o reîncercare aleatorie în anumite limite mici, fie una constantă. Cu ochiul pe posibile coliziuni în prima opțiune.

Următorul pas este să facem asincronă interacțiunea clientului cu sistemul, astfel încât să nu aștepte 15, 30 sau 10000000 de secunde, pe care le vom seta ca BatchTimeout. Dar, în același timp, este necesar să se mențină capacitatea de a verifica dacă modificările inițiate prin tranzacție sunt/nu sunt înregistrate în blockchain.
O bază de date poate fi utilizată pentru a stoca starea tranzacției. Cea mai simplă opțiune este CouchDB datorită ușurinței sale de utilizare: baza de date are o interfață de utilizare din cutie, un API REST și puteți configura cu ușurință replicarea și fragmentarea pentru aceasta. Puteți crea pur și simplu o colecție separată în aceeași instanță CouchDB care utilizează Fabric pentru a-și stoca starea mondială. Trebuie să stocăm aceste tipuri de documente.

{
 Status string // Статус транзакции: "pending", "done", "failed"
 TxID: string // ID транзакции
 Error: string // optional, сообщение об ошибке
}

Acest document este scris în baza de date înainte ca tranzacția să fie trimisă către colegi, ID-ul entității este returnat utilizatorului (același ID este folosit ca cheie) dacă aceasta este o operațiune de creare, iar apoi câmpurile Status, TxID și Error sunt actualizate pe măsură ce se primesc informații relevante de la colegi.

Registrul distribuit pentru seturi de roți: o experiență cu Hyperledger Fabric

În această schemă, utilizatorul nu așteaptă ca blocul să se formeze în sfârșit, urmărind roata care se învârte pe ecran timp de 10 secunde, el primește un răspuns instantaneu de la sistem și continuă să lucreze.

Am ales BoltDB pentru a stoca stările tranzacțiilor, deoarece trebuie să economisim memorie și nu vrem să pierdem timpul cu interacțiunea în rețea cu un server de bază de date separat, mai ales când această interacțiune are loc folosind un protocol text simplu. Apropo, indiferent dacă utilizați CouchDB pentru a implementa schema descrisă mai sus sau pur și simplu pentru a stoca starea lumii, în orice caz, are sens să optimizați modul în care datele sunt stocate în CouchDB. În mod implicit, în CouchDB, dimensiunea nodurilor b-tree este de 1279 de octeți, ceea ce este mult mai mic decât dimensiunea sectorului de pe disc, ceea ce înseamnă că atât citirea, cât și reechilibrarea arborelui va necesita mai mult acces fizic la disc. Dimensiunea optimă corespunde standardului Format avansat și are 4 kiloocteți. Pentru optimizare trebuie să setăm parametrul btree_chunk_size egală cu 4096 în fișierul de configurare CouchDB. Pentru BoltDB o astfel de intervenție manuală nu este necesar.

Contrapresiune: strategie tampon

Dar pot exista o mulțime de mesaje. Mai mult decât poate suporta sistemul, partajarea resurselor cu o duzină de alte servicii în afară de cele prezentate în diagramă - și toate acestea ar trebui să funcționeze impecabil chiar și pe mașinile pe care rularea Intellij Idea ar fi extrem de obositoare.

Problema capacității diferite a sistemelor de comunicare, producător și consumator, este rezolvată în moduri diferite. Să vedem ce am putea face.

scăparea: Putem pretinde că suntem capabili să procesăm cel mult X tranzacții în T secunde. Toate cererile care depășesc această limită sunt respinse. Acest lucru este destul de simplu, dar apoi puteți uita de UX.

Controlul: consumatorul trebuie să aibă un fel de interfață prin care, în funcție de sarcină, să poată controla TPS-ul producătorului. Nu e rău, dar impune obligații dezvoltatorilor clientului care creează încărcarea pentru implementarea acestei interfețe. Acest lucru este inacceptabil pentru noi, deoarece blockchain-ul va fi integrat în viitor într-un număr mare de sisteme de lungă durată.

Tamponare: În loc să încercăm să rezistăm fluxului de date de intrare, putem salva acest flux și îl putem procesa la viteza necesară. Evident, aceasta este cea mai bună soluție dacă dorim să oferim o experiență bună pentru utilizator. Am implementat tamponul folosind o coadă în RabbitMQ.

Registrul distribuit pentru seturi de roți: o experiență cu Hyperledger Fabric

Două acțiuni noi au fost adăugate schemei: (1) după ce sosește o solicitare către API, un mesaj cu parametrii necesari pentru a apela o tranzacție este plasat în coadă, iar clientul primește un mesaj că tranzacția a fost acceptată de către sistemul, (2) backend-ul citește datele la viteza specificată în configurația din coadă; inițiază o tranzacție și actualizează datele din magazinul de stare.
Acum puteți crește timpul de formare și puteți bloca capacitatea cât doriți, ascunzând întârzierile de la utilizator.

Alte instrumente

Aici nu s-a spus nimic despre chaincode, pentru că, de regulă, nu există nimic de optimizat în el. Chaincode ar trebui să fie cât mai simplu și sigur posibil - asta este tot ceea ce i se cere. Cadrul ne ajută să scriem coduri în lanț simplu și în siguranță CCKit de la S7 Techlab și analizor static reînvie^CC.

În plus, echipa noastră dezvoltă un set de utilități pentru a face lucrul cu Fabric simplu și plăcut: explorator blockchain, un utilitar pentru modificări automate ale configurației rețelei (adăugarea/eliminarea organizațiilor, noduri RAFT), utilitate pt revocarea certificatelor și înlăturarea identității. Dacă vrei să contribui, ești binevenit.

Concluzie

Această abordare vă permite să înlocuiți cu ușurință Hyperledger Fabric cu Quorum, alte rețele private Ethereum (PoA sau chiar PoW), să reduceți semnificativ debitul real, dar în același timp să mențineți UX normal (atât pentru utilizatorii din browser, cât și pentru sistemele integrate). Când înlocuiți Fabric cu Ethereum în schemă, va trebui doar să schimbați logica serviciului de reîncercare/decorator de la procesarea conflictelor MVCC la creșterea atomică nonce și retrimitere. Buffering-ul și stocarea stării au făcut posibilă decuplarea timpului de răspuns de timpul de formare a blocului. Acum puteți adăuga mii de noduri de comandă și nu vă fie teamă că blocurile se formează prea des și încărcați serviciul de comandă.

Practic, asta este tot ce am vrut să împărtășesc. Mă voi bucura dacă acest lucru ajută pe cineva în munca lui.

Sursa: www.habr.com

Adauga un comentariu