
Se spune că în viață trebuie să încerci totul măcar o dată. Și dacă ești obișnuit să lucrezi cu SGBD-uri relaționale, merită să te familiarizezi practic cu NoSQL, măcar pentru dezvoltarea generală. Acum, datorită evoluției rapide a acestei tehnologii, există multe opinii contradictorii și dispute aprinse pe această temă, ceea ce stârnește și mai mult interesul.
Dacă te aprofundezi în esența tuturor acestor dispute, poți observa că ele apar din cauza unei abordări greșite. Cei care folosesc baze de date NoSQL acolo unde sunt necesare sunt mulțumiți și obțin toate avantajele acestei soluții. Iar experimentatorii, care se bazează pe această tehnologie ca pe o panacee acolo unde nu este aplicabilă deloc, suferă dezamăgiri, pierzând punctele forte ale bazelor de date relaționale fără a câștiga beneficii semnificative.
Voi povesti despre experiența noastră de implementare a unei soluții bazate pe SGBD Cassandra: cu ce am fost nevoiți să ne confruntăm, cum am reușit să depășim situații dificile, dacă am reușit să obținem un avantaj din utilizarea NoSQL și unde a fost nevoie să investim eforturi/surse suplimentare.
Sarcina inițială a fost construirea unui sistem care înregistrează apelurile într-un anumit depozit.
Principiul de funcționare al sistemului este următorul. La intrare, vin fișiere cu o structură specifică, care descrie structura apelului. Apoi, aplicația asigură salvarea acestei structuri în coloanele corespunzătoare. Ulterior, apelurile salvate sunt utilizate pentru a afișa informații despre consumul de trafic pentru abonați (facturări, apeluri, istoricul soldului).

De ce am ales Cassandra este destul de clar — scrie ca un mitralieră, este ușor scalabilă, rezistentă la erori.
Deci, iată ce ne-a adus experiența
Da, o nodă căzută nu este o tragedie. Asta este esența rezistenței Cassandra. Dar o nodă poate fi activă și totuși să înceapă să coboare în performanță. Așa cum s-a dovedit, asta se reflectă imediat asupra performanței întregului cluster.
Cassandra nu va proteja acolo unde Oracle a salvat cu constrângerile sale. Și dacă autorul aplicației nu a înțeles acest lucru din timp, atunci o dublură ajunsă în Cassandra nu este cu nimic mai prejos decât originalul. Daca a venit, atunci să o inserăm.
Cassandra gratuită „din cutie” nu a plăcut deloc securității informației: nu există logare a acțiunilor utilizatorilor, nici o delimitare a drepturilor. Informațiile despre apeluri se referă la date personale, ceea ce înseamnă că toate încercările de a le solicita/schimba în orice mod trebuie să fie jurnalizate cu posibilitatea de audit ulterior. De asemenea, trebuie să conștientizăm necesitatea de a separa drepturile pe diferite niveluri pentru diferiți utilizatori. Un inginer de operare obișnuit și un super-admin care poate șterge liber întregul keyspace sunt roluri diferite, cu responsabilități și competențe diferite. Fără o astfel de delimitare a drepturilor de acces, valoarea și integritatea datelor vor fi imediat puse sub semnul întrebării mai repede decât la un nivel de consistență ANY.
Nu am luat în considerare că pentru apeluri este nevoie atât de analize serioase, cât și de extrageri periodice în funcție de cele mai variate condiții. Deoarece înregistrările selectate urmează să fie șterse și rescrise (în cadrul sarcinii trebuie să menținem procesul de actualizare a datelor în cazul în care datele inițial primite încontur sunt eronate), Cassandra nu ne este de ajutor aici. Cassandra, ca o pușculiță – este convenabil să pui în ea, dar nu poți să faci calcule.
Ne-am confruntat cu problema transferului de date în zonele de testare (5 noduri în test față de 20 în producție). În acest caz, nu se poate folosi un dump.
Problema actualizărilor schemei de date pentru aplicația care scrie în Cassandra. Rollback-ul va genera o mare cantitate de tombstones, ceea ce poate afecta în mod imprevizibil performanța. Cassandra este optimizată pentru scriere și, înainte de a scrie, nu se gândește mult. Orice operație cu date existente în ea este, de asemenea, o scriere. Adică, ștergând datele inutile, vom crea doar mai multe înregistrări, iar doar o parte dintre acestea vor fi marcate ca tombstones.
Timeout-uri la inserare. Cassandra este excelentă la scriere, dar uneori fluxul de intrare o poate deruta considerabil. Acest lucru se întâmplă atunci când aplicația începe să rotească mai multe înregistrări care nu pot fi inserate dintr-un anumit motiv. Și vom avea nevoie de un DBA adevărat, care să monitorizeze gc.log, logurile system și debug pentru interogări lente, și metricele pentru compaction pending.
Mai multe centre de date în cluster. De unde să citim și unde să scriem?
Este posibil să împărțim între citire și scriere? Și dacă da, ar trebui să fie un D.C. pentru scriere sau pentru citire, mai aproape de aplicație? Nu ne-ar provoca oare o adevărată divizare a creierului dacă alegem greșit nivelul de coerență? Avem foarte multe întrebări, multe setări neexplorate, oportunități pe care ne-ar plăcea să le ajustăm.
Cum am rezolvat problemele
Pentru a evita căderea nodului, am dezactivat SWAP.. Și acum, în caz de memorie insuficientă, nodul ar trebui să se oprească, nu să creeze pauze lungi de gc.
Așadar, nu mai avem încredere în logica din baza de date. Dezvoltatorii aplicației se reeducă și începe să implementeze măsuri de precauție activ în codul lor. O separare clară și perfectă între stocarea și procesarea datelor.
Am achiziționat suport de la DataStax. Cassandra în versiune cutie a fost deja oprită din dezvoltare (ultimul commit a fost în februarie 2018). Între timp, DataStax oferă un serviciu excelent și o mulțime de soluții adaptate și îmbunătățite pentru sistemele existente.
De asemenea, aș dori să subliniez că Cassandra nu este foarte convenabilă pentru interogările de selecție. Desigur, CQL reprezintă un mare progres pentru utilizatori (în comparație cu Thrift). Dar dacă aveți întregi departamente obișnuite cu astfel de join-uri convenabile, filtrarea liberă după orice câmp și capacitățile de optimizare a interogărilor, iar aceste departamente lucrează pentru a rezolva revendicările și incidentele, atunci o soluție bazată pe Cassandra li se pare o alegere neprietenos și stupidă. Și am început să căutăm modalități prin care colegii noștri să facă selecții.
Am examinat două opțiuni. În prima opțiune, scriem apelurile nu doar în C*, ci și în baza de date arhivă Oracle. Spre deosebire de C*, în această bază de date sunt stocate apelurile doar pentru luna curentă (adâncimea de stocare a apelurilor este suficientă pentru cazurile de rețarifizare). Aici se contura imediat următoarea problemă: dacă scriem sincron, pierdem toate avantajele C*, legate de inserarea rapidă; dacă scriem asincron – nu există nicio garanție că toate apelurile necesare ajung în Oracle. Un avantaj, dar mare: pentru exploatare, avem la dispoziție același familiar PL/SQL Developer, adică practic implementăm modelul „Fasada”. Opțiunea alternativă. Implementăm un mecanism care extrage apelurile din C*, trage unele date pentru îmbogățire din tabelele corespunzătoare din Oracle, face joiunți ale selecțiilor obținute și ne oferă rezultatul obținut, pe care ulterior îl folosim cumva (îl anularăm, îl repetăm, îl analizăm, ne bucurăm de el). Dezavantaje: procesul devine destul de complex, și în plus, lipsește o interfață pentru angajații de exploatare.
În cele din urmă, ne-am oprit totuși asupra celei de-a doua opțiuni. Pentru selecții din diferite baze de date, am folosit Apache Spark. Esenta mecanismului a fost reducerea la un cod Java care, pe baza cheilor specificate (abonat, timp de apel – cheile secțiunii), extrage date din C*, precum și datele necesare pentru îmbogățire din orice altă bază de date. Apoi, le combină în memorie și afișează rezultatul în tabela de rezultate. Peste Spark, am desenat o interfață web și a ieșit destul de adecvat pentru exploatare.

În abordarea problemei actualizării datelor, echipa de testare a analizat din nou mai multe metode de soluționare. Atât transferul prin Sstloader, cât și opțiunea de a împărți clusterul în zona de testare în două părți, fiecare dintre care intra alternativ într-un cluster cu cel de producție, alimentându-se astfel de la acesta. La actualizarea testului, se plănuia schimbarea locațiilor: partea care funcționa în test era curățată și introdusă în producție, iar cealaltă începea să lucreze cu datele separat. Totuși, după o reanaliză, am evaluat mai rațional datele care merită transferate și am realizat că apelurile în sine sunt o entitate inconsistentă pentru teste, generată rapid în caz de nevoie, iar setul de date de producție nu are valoare pentru a fi transferat în test. Sunt câteva obiecte de stocare care merită transferate, dar sunt literalmente doar câteva tabele, și nu foarte grele. Așadar, noi am apelat din nou la Spark ca soluție, cu ajutorul căruia am scris și am început să utilizăm activ un script pentru transferul datelor între tabelele de producție și test.
Politica noastră actuală de desfășurare ne permite să lucrăm fără rollback-uri. Înainte de producție, este obligatoriu să se efectueze un test, unde erorile nu costă atât de mult. În cazul unui eșec, este întotdeauna posibil să ștergem case-ul și să reinstalăm schema de la început.
Pentru a asigura disponibilitatea continuă a Cassandra, este nevoie de un DBA și nu doar de el. Toți cei care lucrează cu aplicația trebuie să înțeleagă unde și cum să verifice situația actuală și cum să diagnosticheze problemele la timp. Pentru aceasta, folosim activ DataStax OpsCenter (Administrare și monitorizare a sarcinilor de lucru), metricele sistemului Cassandra Driver (numărul de time-out-uri la scriere în C*, numărul de time-out-uri la citire din C*, latența maximă etc.), monitorizăm funcționarea aplicației în sine care lucrează cu Cassandra.
Când am reflectat asupra întrebării anterioare, ne-am dat seama unde ar putea fi principala noastră riscuri. Acestea sunt formele de prezentare a datelor care extrag informații din mai multe cereri independente către depozit. Astfel, putem obține o informație destul de incoerentă. Dar această problemă ar fi relevantă și dacă am lucra doar cu un singur data center. Așadar, cea mai logică soluție ar fi, desigur, să implementăm o funcție de citire a datelor într-o aplicație externă, care să asigure obținerea datelor într-o perioadă unică de timp. În ceea ce privește separarea citirii și scrierii din perspectiva performanței, aici ne-a oprit riscul că, în cazul unei pierderi temporare de conectivitate între DC-uri, am putea obține două clustere complet incoerente.
Astfel, în prezent ne-am oprit la nivelul de coerență pentru scriere EACH_QUORUM, iar pentru citire – LOCAL_QUORUM
Impresii și concluzii scurte
Pentru a evalua soluția obținută din perspectiva suportului operațional și a posibilităților de dezvoltare ulterioară, am decis să ne gândim unde am putea aplica o astfel de dezvoltare.
Dacă ne gândim rapid, scorarea datelor pentru programe precum „Plătește când îți convine” (încărcăm informații în S*, calcul pe scripturi Spark), gestionarea plângerilor cu agregare pe direcții, păstrarea rolurilor și calcularea pe matricea de roluri a drepturilor de acces ale utilizatorilor.
După cum vedem, repertoriul este larg și divers. Și dacă ar fi să alegem tabăra susținătorilor/propagandiștilor NoSQL, am adera la susținători, deoarece am obținut avantajele dorite, exact acolo unde ne așteptam.
Chiar și varianta Cassandra din cutie permite scalarea orizontală în timp real, rezolvând absolut fără durere problema creșterii datelor în sistem. Am reușit să externalizăm un mecanism cu o foarte mare încărcare pentru calculul agregatelor pe apeluri și, în plus, să separăm schema și logica aplicației, scăpând de practica nocivă a scrierii joburilor personalizate și obiectelor în baza de date în sine. Am obținut posibilitatea de a alege și a configura, pentru accelerare, pe care DC-uri vom efectua calculul și pe care le vom utiliza pentru scrierea datelor, asigurându-ne împotriva prăbușirii atât a nodurilor individuale, cât și a întregului DC.
Aplicând arhitectura noastră la noi proiecte, și având deja o anumită experiență, ne-am dori să luăm imediat în considerare nuanțele menționate mai sus și să evităm anumite erori, să estompăm colțurile ascuțite, care nu au putut fi evitate inițial.
De exemplu, să urmărim în timp util actualizările versiunii Cassandra, deoarece multe dintre problemele pe care le-am avut erau deja cunoscute și au fost remediate.
Să nu instalăm atât baza de date, cât și Spark pe aceleași noduri (sau să le separăm strict în funcție de cantitatea de resurse utilizabile), deoarece Spark poate consuma mai mult RAM decât ar trebui, iar noi vom avea rapid problema numărul 1 din lista noastră.
Să îmbunătățim monitorizarea și competențele de operare încă din etapa de testare a proiectului. Inițial, să luăm în considerare toți consumatorii potențiali ai soluției noastre, deoarece tocmai de acest lucru va depinde, în cele din urmă, structura bazei de date.
Să examinăm schema obținută de câteva ori pentru a căuta eventuale optimizări. Să identificăm ce câmpuri pot fi serializate. Să înțelegem ce tabele suplimentare trebuie să facem pentru a lua în considerare, în mod cât mai corect și optim, și ulterioară livrare a informațiilor solicitate (de exemplu, având în vedere că aceleași date pot fi stocate în tabele diferite, conform diferitelor criterii de fragmentare, putem economisi semnificativ timpul de procesor în timpul cererilor de citire).
Este bine să prevedem imediat atașarea TTL și curățarea datelor depășite.
La exportul de date din Cassandra logica aplicației ar trebui să funcționeze pe principiul FETCH, astfel încât nu toate rândurile să fie încărcate în memorie deodată, ci să fie selectate pe grupe.
Este recomandat să verificăm reziliența sistemului înainte de a muta proiectul pe soluția descrisă, realizând o serie de teste de stres, cum ar fi pierderea datelor într-un centru de date, recuperarea datelor deteriorate pe o anumită perioadă, scăderea rețelei între centrele de date. Aceste teste nu doar vor permite evaluarea avantajelor și dezavantajelor arhitecturii propuse, ci vor oferi și o bună practică pentru inginerii care le efectuează, iar abilitățile obținute nu vor fi în zadar, mai ales dacă defecțiunile sistemului se vor repeta în producție.
Dacă lucrăm cu informații critice (cum ar fi datele pentru facturare, calcularea datoriilor abonatului), atunci ar trebui să acordăm atenție instrumentelor care permit reducerea riscurilor, care apar din cauza specificului SGBD-ului. De exemplu, folosirea utilitarului nodesync (Datastax), elaborând o strategie optimă pentru utilizarea acestuia, pentru a nu forma o încărcătură excesivă asupra Cassandra, și a-l utiliza doar pentru anumite tabele într-o anumită perioadă.
Deci, după șase luni de utilizare a Cassandra? În general, nu există probleme nerezolvate. Nu am avut accidente serioase și pierderi de date. Da, a fost necesar să ne gândim la compensația unor probleme care nu au mai apărut anterior, dar în final acest lucru nu a umbrit prea mult soluția noastră arhitecturală. Dacă doriți și nu vă temeați să încercați ceva nou, și în același timp nu doriți să fiți foarte dezamăgiți, pregătiți-vă pentru faptul că nimic nu este gratuit. Va trebui să învățați, să studiați documentația și să vă strângeți propriile capcane mai mult decât în soluția veche legacy, iar nicio teorie nu vă va spune din timp ce capcane vă așteaptă exact pe dumneavoastră.
Sursa: habr.com
