Svaki projekat ima dve strane – benefite koje je doneo, i nepredviđene izazove koji se pojave u toku izrade samog projekta. Danas ću u ovom blogu govoriti upravo o toj manje lepoj strani medalje, koja unosi nervozu u tim i čini donosi neočekivane izazove.
Kao i većina softver developera u Devoteam-u, moj glavni zadatak na projektima bio je da pišem dobar kod koji ispunjava zahteve korisnika, istovremeno poštujući inženjerske principe i zatim da to isporučim u testno okruženje korisnika. Ipak, kada se radi o smeštanju projekta u produkciono okruženje za rad sa klijentima i korisnicima, sve može izgledati dosta drugačije.
Konkretno ovaj projekat pokrenut je sa idejom o transformaciji postojeće monolitne aplikacije u aplikaciju sa mikroservisnom arhitekturom, sastavljene od više manjih servisa koji nezavisno rade na Kubernetes klasteru.
Arhitekte sa iskustvom, biznis analitičari i tim lidovi preuzeli su sve neophodne korake kako bi obezbedili uspešnu tranziciju na produkciju. Imali smo dobro smišljen plan i odličnu realizaciju. Projekat je isporučen uspešno. Ipak, tokom same isporuke projekta pojavilo se nekoliko nepredviđenih izazova. Dve glavne “lekcije” koje mi je ovo iskustvo donelo su:
- Važnost stress testiranja
- Migracije podataka i neslaganja u polju datuma
Važnost stress testiranja
Jedan od najvećih problema u okruženju produkcije bio je značajan pad ukupnih performansi u pogledu brzine. Na ovaj pad uticalo je nekoliko faktora:
- Prekomerni pozivi backend API-ja i nepotrebni importi na frontend-u,
- “Uska grla” i neoptimalna upotreba framework-a u backend-u,
- Sporo dobavljanje informacija kao rezultat značajnog povećanja skupa podataka.
U nastavku ću analizirati svaki od ovih izazova i predstaviću naša rešenja za iste.
Optimizacija Frontend-a
Frontend je najmanje uzrokovao pad performansi. Jedan mali problem koji se pojavio su neiskorišćeni uvozi, i to je rešeno prilično brzo jednostavnim brisanjem pomenutih uvoza.
Dalje, postojali su neki servisi u aplikaciji koji su bili “inRoot”. Kao rezultat toga, aplikacija je zahtevala od svih servisa da budu instancirani pre pokretanja aplikacije. Ovaj izazov prevaziđen je brisanjem “inRoot” konfirguracije tamo gde smo smatrali da je nepotrebna, i pravilnim učitavanjem servisa u module koji su omogućavali bolji “lazy loading”.
I konačno, u našoj aplikaciji korisnik je imao mogućnost da pogleda ili da koriguje dokument. Ipak, nisu sve funkcionalnosti bile omogućene u “view” modu, i tako su neki API pozivi backend-u bili redundantni. Jednostavnom promenom koji je mod aktiviran u instanci komponente, mi smo uspeli da smanjimo API pozive ka backend-u, što je rezultiralo većom brzinom aplikacije.
Optimizacija Backend-a
Optimizacija backend servisa zahtevala je više truda od frontend-a iz prostog razloga što kompletan backend kod obuhvata više mikroservisa. Zato smo proverili svaki servis za koji smo sumnjali da je “usko grlo”, i svaki od servisa predstavljao je jedinstveni problem. U nastavku ću govoriti o nekima od njih.
Jedna od stvari koja nam je prijavljena vrlo brzo nakom što je projekat isporučen na produkciju, je da aplikacija u nekim delovima dana radi bolje dok je u drugima sporija. Proširili smo loging informacije kako bismo imali uvid u uzrok koji usporava performanse aplikacije, i to nam je zapravo otkrilo neke vrlo zanimljive činjenice.
Mi koristimo Keycloak, open-source menadžment alat kako bismo organizovali korisnike, uloge i ovlašećnja u aplikaciji. Unutar same strukture nalazi se MySQL baza podataka koja čuva ove osetljive informacije. Ono što smo našli je da naš kod ispituje bazu podataka kako bi došao do podataka koji se ne menjaju kada se sve podesi. Implementacijom jednostavnog cache rešenja u servis sloju, kao što je hash mapa u kojoj su sačuvane uloge, omogućili smo da se upit zaobiđe ukoliko je tražena uloga na mapi. Ukoliko je urađen update uloge u bazi, isto ćemo uraditi i u cache-u.
Još jedna interesantna stvar je da mi nismo dovoljno iskoristili mogućnosti MongoDB i njene agregacije. Ovo je postalo evidentno kada je završena migracija podataka i kada se veličina podataka povećala sa nekoliko hiljada dokumenata na nekoliko desetina hiljada. MongoDB bio je naš izbor za čuvanje skoro svih podataka koje je generisala aplikacija. Transformisanjem koda u repository sloju servisa od onog baziranog na typescript logici u onaj koji koristi MongoDB aggregation pipeline, smanjili smo opterećenost pri izvršavanju servisa u bazi. Kao rezultat ovoga, celokupne performanse su bile bolje.
Ovo je sve bilo dobro za operacije koje se tiču update-ovanja podataka, ali smo mi takođe trebali da ubrzamo i prikupljanje informacija iz baze, što nikako nismo mogli uraditi poboljšanjem koda. Ipak, postojala je jedna stvar u samoj bazi koju smo mogli, a to je dodavanje indeksa u bazu. Ovo omogućava MongoDB da ograniči broj dokumenata koje treba da pregleda, i tako izvršava upite brže. Mi smo analizirali svaku kolekciju svake baze kako bi identifikovali najoptimalnije indekse za svaku od njih. Kada smo to implementirali, aplikacija je ponovo radila brzo.
Migracije podataka i neslaganja u polju datuma
Jedan od glavnih zahteva je bio upotrebljivost podataka iz prethodne verzije. Ovo je omogućeno zahvaljujući našoj analizi podataka i migracionom timu, koji je mapirao postojeća polja u novi set polja i izvršio migraciju podataka u serijama, sa povremenim testiranjem, kako bi se uverio da će aplikacija neometano raditi.
Sve ovo prošlo bi savršeno, da nismo prevideli jednu činjenicu koja je proistekla iz migracije i skladištenja polja datuma.
Većina dokumenata sa datumima sadrži različita polja: početni i krajnji datum nekog perioda, datume rođenja i klijenata i zaposlenih, datume određenih aktivnosti…
Datumi sami po sebi bili su laki za migraciju, datum iz jedne javascript aplikacije se na isti način interpretira u drugoj. Problem je nastao sa datumima unutar novo kreiranih dokumenata na novoj verziji aplikacije, gde se vreme datuma skladišti kao ponoć. I ne samo to, pošto je naša baza locirana u vremenskoj zoni CET, datum se čuvao kao pretodni dan sa vremenom podešenim na 23:00.
Na primer, ukoliko bismo kreirali novog klijenta sa datumom rođenja 01.03.2007, to bi bilo sačuvano kao “28-02-2007T23:00:00Z”.
U nekim drugim slučajevima, aplikacija je čuvala datum-vreme objekte kao datum i vreme zakazanih sastanaka, datum kreiranja i modifikovanja dokumenata… Ovog puta sačuvani podaci uključivali su i sate i minute, ali opet je sve čuvano u Centralno evropskoj vremenskoj zoni, što znači da će se datum-vreme objekat umanjiti za jedan sat (npr. Sastanak zakazan za 20.01.2022 u 15:30 bio bi sačuvan kao „20-01-2022T14:30:00Z”).
Neusaglašenost između formata datuma stare i nove verzije aplikacije uzrokovalo je da određene funkcionalnosti ne rade. Ovo je bio vrlo značajan problem i ni jedno rešenje ne bi obuhvatilo svaki njegov aspekt. Oni delovi koji su se bavili određenim update-om putem Kubernetes klastera zahtevali su striktan datum i vreme interpretiran u CEST vremenskoj zoni; dok su drugi delovi aplikacije bili agnostični prema vremenskoj zoni, i transformisali su datum prema vremenskoj zoni uređaja korisnika.
Jedno moguće rešenje bilo je da se modifikuje format datuma i da se skladišti u formatu UTC, tako da je vremenska zona nepoznanica. To bi podrazumevalo refaktorisanje delova koda u backend-u gde je potrebna vremenska zona, ali ovo se može postići korišćenjem framework-a kao što je Moment za transformaciju datuma u ispravan format pre nego što se nastavi sa izvršavanjem koda.
Zaključak
Na kraju, želeo bih da dodam još jednu stvar za koju smatram da je ključan faktor za uspeh projekta, a to je saradnja unutar tima, gde svi članovi tima imaju raznovrsne veštine, jer za takav tim svaki izazov koji nastane tokom projekta je moguće prevazići. Tokom ovog perioda javile su neke nove ideje, probali smo nove stvari, i na kraju smo uspeli.
Ovo iskustvo pokazalo je koliko je planiranje dragoceno ne samo prilikom isporuke u produkciju, nego i tokom celog projekta generalno. I ovo je takođe nešto što volim kod svog posla, uvek ima šta novo da se nauči.