Într-o industrie competitivă, guvernată de schimbare și adaptare, unde inovația este în centrul oricărei idei, atingerea unui nivel de performanță optimă este un obiectiv mai mult decât necesar. Dar ce este performanța în contextul dezvoltării software? Performanța este un pilon fundamental când vine vorba de dezvoltarea software, nu este doar o măsură tehnică, ci influențează atât viteza, cât și fiabilitatea unei aplicații, influențând gradul de satisfacție al utilizatorilor și implicit definește succesul unei aplicații. Designul unei aplicații performante constituie un avantaj în fața competitorilor, iar o abordare orientată către performanță și eficiență poate fi factorul decisiv în business.
Pentru dezvoltarea unor aplicații performante avem la dispoziție o multitudine de servicii care oferă soluții eficiente și scalabile - care pot gestiona eficient datele chiar și în cazul unei creșteri abrupte a volumului de date. În plus, aceste soluții sunt flexibile și ușor de configurat, open-source, orientate către securitate, care deține chiar reprezentări grafice și soluții de alertare și notificare în timp real și care se integrează cu ușurință cu o varietate de surse date.
Prin analizarea unor reprezentări vizuale, se pot detecta anomalii sau fluctuații neașteptate în performanță, iar timpii de execuție sunt ușor de urmărit și problemele de latență devin rapid de identificat. Reprezentările grafice ale datelor joacă un rol important în procesul de analiză și ajută la identificarea problemelor de performanță.
În continuare, vom analiza câteva instrumente care stau la baza obținerii performanței sistemelor dezvoltate.
Grafana oferă un mediu de vizualizare interactiv, oferind posibilitatea de a crea panouri personalizate cu grafice, diagrame și metrici în timp real. Este o platformă pentru vizualizarea și monitorizarea datelor, flexibilă, care consolidează date din surse multiple, într-o interfață customizabilă, transformând-o într-un tool esențial pentru monitorizarea metricilor de performanță în timp real.
În plus, are posibilitatea de a crea panouri interactive și personalizate, prin care utilizatorul poate defini multiple panouri prin folosirea de JSON-uri pentru a le defini sau prin interfața grafică pe care aceasta o pune la dispoziție.
Grafana este un tool flexibil, care se poate integra și poate ingera date din diverse surse - precum Prometheus sau ElasticSearch, inclusiv baze de date, cât și diverse sisteme de monitorizare și servicii cloud.
Pe lângă multiplele sale avantaje, Grafana permite configurarea de alerte personalizabile pentru detecția de anomalii, astfel încât utilizatorii pot seta thresholduri și condiții pentru a primi notificări în timp real atunci când anumite evenimente ies din parametri.
Prometheus este un tool open-source, scalabil și flexibil, care oferă funcții de alertare și monitorizare. Este utilizat frecvent în dezvoltarea software, bazându-se pe colectarea eficientă a datelor și prin oferirea unui model robust pentru identificarea problemelor de performanță, prin colectarea, stocarea și analiza datelor colectate.
Prometheus colectează date în timp real, având capacitatea de a se adapta la medii scalabile, unde numărul de instanțe monitorizate poate crește, fără a afecta sau compromite performanța sau eficiența sistemului.
Graylog este un sistem open-source, flexibil, scalabil pentru centralizarea și managementul logurilor, oferind posibilitatea de a colecta, analiza și vizualiza logurile din întreaga aplicație, indiferent că ne gândim la o arhitectură cu microservicii sau un monolit. Permite colectarea centralizată a logurilor din diverse surse.
O analiză detaliată a logurilor este esențială în identificarea problemelor de performanță.
Graylog agregă logurile provenite din diverse surse într-un dashboard interactiv. La fel ca celelalte tooluri prezentate, Graylog este un tool scalabil și customizabil, astfel încât este potrivit pentru folosirea în cadrul aplicațiilor și al organizațiilor de toate dimensiunile.
Capabilitățile sale de procesare și agregare a logurilor, funcționalitățile de căutare și vizualizare a logurilor fac din Graylog o soluție cuprinzătoare de monitorizare, identificare și remediere a problemelor.
Pe lângă soluțiile de colectare, analiză, căutare și vizualizare a datelor, Graylog are soluții de gestionare a securității într-un mod eficient, prin acordarea posibilității de a defini accesul utilizatorilor, fiind crucial în mediile unde securitatea datelor este impetuoasă.
Suportă funcționalități de alertare în timp real, dispunând de posibilitatea de a se configura notificări în cazul în care anumite evenimente au fost înregistrate.
Realizează o analiză detaliată și are o indexare eficientă, facilitând analiza datelor, chiar și pe volume mari de date, dând răspunsuri rapide și relevante.
Elastic APM (Elastic Performance Monitoring) face parte din Elastic Stack (ecosistemul elastic) care este o colecție de tooluri open-source pentru căutări, analize și vizualizarea datelor în timp real. Elastic APM poate fi utilizat pentru a monitoriza performanța aplicațiilor. Este o alternativă la Graylog, având și opțiunea de a stoca datele în cloud.
Ca parte din ecosistemul Elastic, Elastic APM se poate integra facil cu ElasticSearch, astfel încât poate stoca, căuta și analiza date, iar integrarea cu Kibana dispune de o modalitate rapidă și ușoară de vizualizare a datelor într-un mod interactiv și ușor de personalizat.
Pe lângă toate avantajele prezentate, APM acordă suport pentru o gamă largă de limbaje de programare, precum Java, Ruby, Python sau Node.js. În plus, are soluții de alertare și notificare în timp real, oferind posibilitatea configurării de notificări în cazul unor erori critice sau depășirea unor thresholduri setate în prealabil. Unul din cele mai mari avantaje ar fi definirea unor praguri per ruta monitorizată. De exemplu, pe ruta /api/purchases rata de succes ar trebui să scadă sub 95% pentru a genera o alertă, pe când /api/products ar putea avea un prag definit la 90%.
https://www.elastic.co/observability/application-performance-monitoring
Face parte din Elastic Stack, deținând o soluție completă și cuprinzătoare pentru monitorizarea, identificarea și remedierea problemelor aplicațiilor. Oferă soluții pentru monitorizarea tranzacțiilor.
Conceput pentru a acorda date despre performanța aplicației prin monitorizarea datelor dă informații despre timpul de răspuns al aplicației, distribuția erorilor, cât și traseul parcurs de fiecare request în parte. Urmărește parcursul unui request prin variile componente ale sistemului, astfel încât identificarea și remedierea problemelor în cadrul sistemelor distribuite este mult ușurată.
Zipkin este soluție open-source, dezvoltată inițial de X (Twitter), pentru monitorizarea și diagnosticarea serviciilor distribuite. Urmărește traseul unui API request și colectează date referitoare la timpii de execuție pentru a ajuta în troubleshootingul problemelor de latență în arhitecturi distribuite.
Precum și celelalte servicii prezentate, Zipkin dispune de un UI care oferă vizualizări detaliate ale datelor, dar și o diagramă a dependințelor care arată câte requesturi au trecut prin fiecare aplicație în parte. Ajută la diagnosticarea și detectarea erorilor, cât și la măsurarea performanței prin colectarea și gruparea de date legate de timpii de răspuns ai fiecărui serviciu. Zipkin poate fi integrat cu o varietate de limbaje de programare și frameworkuri (ElasticSearch), ceea ce simplifică integrarea cu o gamă largă de aplicații. Fiind proiectat pentru a se mula pe arhitecturi distribuite și complexe, este scalabil și poate suporta o creștere masivă a ingestiei de date în cazul în care apare o anomalie sau o fluctuație neașteptată a volumului de date.
Dezvoltarea software implică de cele mai multe ori integrări cu diverse sisteme externe (3rd party) care impun limitări pe numărul de requesturi sau pot bloca aceste requesturi.
WireMock este un serviciu open-source, care deține o shiftare în paradigma de testare prin oferirea posibilității de testare a unor sisteme 3rd party și anume prin simularea de requesturi HTTP API. Este un serviciu care permite crearea și gestionarea de mockuri ale API-urilor prin simulări detaliate ale acestor API-uri, inclusiv definirea de rute, parametri, răspunsuri și comportamente specifice (ex. simulare latență).
WireMock suportă o varietate de metode HTTP, având o sintaxă simplă și ușor de înțeles, unde programatorii pot specifica în detaliu requesturile trimise și răspunsurile așteptate, permițând simularea de scenarii complexe de testare.
Apache JMeter este o soluție open-source care oferă capabilități de testare prin scrierea de scripturi. Acordă suport pentru protocoale SOAP și REST, dar și FTP, LDAP, SMTP și altele, ceea ce îl face o soluție atemporală, versatilă și adaptabilă pentru diverse abordări de testare, precum load testing, stress testing, stability testing și analiza performanței. Permite simularea scenariilor din viața reală prin testarea aplicațiilor, a API-urilor și a bazelor de date. Poate fi folosit pentru testarea performanței web prin simularea unui număr mare de utilizatori și solicitări pentru a evalua comportamentul aplicației în condiții de stres. Permite testarea aplicațiilor HTTP/S prin simularea de scenarii, trimiterea de requesturi, gestionare de cooki-uri și manipulare de sesiuni. Poate testa performanța aplicațiilor care rulează pe diverse platforme (Apache, Nginx), deține capabilități de testare ale bazelor de date, prin JDBC. În plus, poate măsura timpul de răspuns al interogărilor SQL.
JMeter generează rapoarte detaliate și grafice care surprind rezultatele testelor, timpii de răspuns și ratele de eroare.
Artillery este o soluție modernă și ușor de folosit specializată în testarea sistemelor pentru a evalua scalabilitatea și performanța în diverse situații de stres. În primul rând, atenția Artillery-ului este centrată pe aplicațiile web, pe testarea performanței acestora folosind HTTP și WebSockets. Oferă capabilități de scriptare simple și flexibile, bazat pe YAML pentru definirea sau modificarea scenariilor de testare.
Aplicația mea este o colecție mică de resurse statice (~10MB) susținute de un API. Un utilizator le încarcă o singură dată, le stochează în cache, apoi la fiecare vizită le refolosește. Sună familiar? Lumea evoluează, iar soluțiile oferite trebuie să țină pasul cu ea. Avem măsurători care dau detalii despre comportamentul utilizatorilor, date care stau la baza deciziilor viitoare. Oricine vrea să fie relevant pe piață trebuie să ia decizii rapide. Toate acestea duc către un proces rapid de livrare. S-au dus zilele în care un nou release se făcea o dată pe trimestru. În prezent, un release în producție se întâmplă la fiecare două săptămâni sau mai des. Chiar și o schimbare de un caracter într-o resursă statică te forțează să îl descarci din nou.
Cum putem scoate traficul din rețea? Apelăm la un CDN (ex. Fastly, Cloudflare, Akamai) pentru tot efortul de a livra resurse statice, lăsând întreaga rețea și resursele la dispoziția API-ului. Viteza de livrare este drastic îmbunătățită, dacă luăm în calcul ușurința cu care ajungi la utilizatori din toate colțurile lumii. CDN-ul se ocupă de replicarea resurselor în mai multe zone ale globului, fără ca infrastructura ta să sufere vreo modificare. Desigur, costul este de luat în calcul și o analiză a distribuției utilizatorilor este obligatorie pentru a alege cea mai bună replicare pentru nevoile tale. Un alt beneficiu deloc de neglijat ar fi protecția împotriva unui atac DDOS.
Un sistem poate avea șansa să fie performant atâta vreme cât e up and running. Am creat un sistem care poate duce 1000 de utilizatori simultan. Sună bine. Dar dacă mai vin 1000? Avem scalare pe orizontală, și toată lumea este mulțumită. VM-urile și podurile sunt auto-scalabile cu ușurință. Nu la fel se poate spune și despre o bază de date relațională. Timpul de start este de luat în calcul, și până ai o nouă instanță la dispoziție tot sistemul poate fi pus pe butuci de o creștere neașteptată a traficului. Dacă banii nu sunt o problemă putem lua cele mai performante VM-uri care stau nefolosite, la potențialul lor, marea majoritate a timpului.
Privind lucrurile dintr-o perspectiva realistă, ar fi bine să îți setezi așteptările pentru un flux de utilizatori pe care vrei să îi deservești, să setezi limite și să permiți accesul doar al unui număr de utilizatori cu care sistemul tău poate opera fără a fi în pericol de out-of-service. Ultimul lucru pe care ți-l dorești ar fi ca toți să vadă o pagină Service Unavailable. Parcă ar fi mai bine ca primii 1k utilizatori să aibă parte de performanță, iar ceea ce depășește această limită, să aștepte preț de câteva minute pentru a-ți accesa aplicația.
Pe lângă limitările pe care fiecare sistem le are, mai adăugăm limitările impuse de un 3rd party.
Virtual waiting rooms sunt concepute exact pentru un asemenea scenariu. Previn un flux mare de utilizatori care ar putea afecta performanța sau duce la dezastru. Îți oferă luxul de a crește sau a reduce controlabil fluxul de utilizatori în timp real, oferind în același timp o experiență plăcută pentru utilizatori. Cât timp stau în așteptare, un waiting room decent poate fi construit cu rich-content, un video de prezentare făcându-le așteptarea mai suportabilă.
Cachingul este componenta fără de care performanța nu poate fi atinsă fără a cheltui bugete semnificative. Efortul de a integra un sistem de caching este cât se poate de mic. Deși este o piesă importantă, ea nu ar trebui să dicteze statusul unui serviciu în clusterul tău. Fără o conexiune la baza de date, un pod nu ar trebui să fie alive. Absența unei conexiuni la soluția de cache ar trebui să lase serviciul în stare de funcționare.
Repetiția este mama învățăturii, însă nu și la runtime. Dacă ceva a fost găsit/calculat, refăcând același proces ajungi la exact același rezultat atunci cachingul este soluția salvatoare.
În situația nefericită în care depinzi de un 3rd party care are timpi mari de răspuns, caching este cu atât mai important. Dependența între "durata de viață" a unei înregistrări ar trebui să fie direct proporțională cu "timpul de răspuns al API-ului partener". Cu cât răspunde mai greu, cu atât este mai bine să stochezi informație pentru mai mult timp. Îți oferă răgazul de a decide, când să faci update înregistrărilor din cache bazându-te pe o logică întreținută de tine și nu de comportamentul utilizatorilor.
Regula de bază - dacă exista o șansă, oricât de scăzută, ca o informație sa fie folosită de doi utilizatori - salveaz-o în cache. Lasă reglajele fine asupra duratei de stocare pe mai târziu.
Ce e preferabil într-un context de microservicii ? Mai puține instanțe cu resurse mari( cpu/memorie), sau mai multe instanțe cu resurse reduse? Aș prefera oricând a doua variantă. Cu cât mai mic serviciul, cu atât mai rapid este la start-up dar și la clean-up. În perioade în care cererea scade, numărul de instanțe poate fi redus la minim (auto-scalare pe orizontală), fără a ține în priză VM-uri mari. Nu aș trata memoria unei instanțe ca o alternativa la caching. Durata de viață a fiecărui obiect de transfer ar trebui să fie cât mai mică. Experimentarea cu auto-scalare pe verticala (VPA în kubernetes) s-ar putea să îmi schimbe părerea. Dar până atunci "less is better".
Prea multe conversii aduc presiune pe memorie și CPU. Orice obiect creat are un impact asupra timpului de alocare dar și la distrugere. Cu cât folosim un număr mai mic de instanțe, cu atât mai fericit va fi garbage collector-ul la dealocare. O consecință a folosirii mapstruct ar fi ușurința cu care facem conversii dintr-un model în altul fără a depune efort. Definim o dată cum facem conversia și o refolosim când ne convine. Problema apare atunci când din comoditate folosim un mapper în mai multe clase. Ideal ar fi ca între un mapper și o clasă serviciu să fie o corespondență 1-1. Orice folosire a unui mapper în mai multe clase aș trata-o ca o posibilă sursă de conversii multiple inutile.
Optional este cool. Hai să returnăm la fiecare metodă care poate returna null un Optional. Ne asigurăm că mesajul ajunge la fiecare programator care folosește metoda noastră și va fi obligat să trateze corespunzător evitarea unui NullPointerException. Dacă milisecunda este unitatea de măsură, atunci folosirea banalului IF este recomandată. În unele situații ar fi mai indicată folosirea unui obiect nullable sau clase NoOp.
Concatenarea Stringurilor o vedem la fiecare pas; dacă e vorba de scrierea unei informații în loguri, sau alcătuirea de informații comasând mai multe surse. Este una din greșelile des întâlnite, mai ales la construirea mesajului unui log.
Multe probleme pot fi depistate cu ajutorul unui static-code analysis tool.
Scopul final în a avea o imagine de ansamblu asupra performanței sistemului este de a dimensiona corespunzător hardware-ul folosit (ca este vorba de cloud sau on-premise). Dacă ar fi să mă rezum la trei puncte importante de urmărit, acelea ar fi:
Totul să fie up and running;
Protecție la fluxuri mari de requesturi;
Load testingul este un tip de testare care simulează diverse % de încărcare ale sistemului. Ajută la identificarea limitelor acestora pentru a verifica dacă sistemul poate gestiona volumul de utilizatori anticipat.
Stress testingul se bazează pe identificarea punctelor de eșec prin plasarea sistemului sub o încărcare excesivă pentru a testa modul în care se comportă sistemul în condiții de stres/extreme.
Stability testingul verifică și asigură stabilitatea sistemului prin explorarea comportamentului sistemului în timp, monitorizând performanța pe termen lung. Poate identifica probleme care apar în timp, cum ar fi scurgeri de memorie sau acumularea de erori.