Bolje performanse i skalabilnost uz primenu CQRS-a

Share

Arhitektura nekog softverskog rešenja često predviđa korišćenje iste relacione baze podataka za upis, odnosno čuvanje podataka kao i za čitanje, odnosno prikaz tih podataka. Korisiti se isti model za upis i pregled podataka iako je taj model obično direktno proizašao iz potrebe održavanja konzistentnosti prilikom upisa podataka, upotrebom tehnika normalizacije kako ne bi bilo redudantnih podataka ili da se ta redudantnost svede na minimum. Međutim, pored svih benefita normalizovane strukture pri upisu podataka, neretko se dobija veoma kompleksan i zahtevan model za čitanje tih podataka gde se moraju spajati podaci iz više tabela (npr. SQL join). Kompleksnost upita raste uvođenjem novih funkcionalnosti u model. Nažalost,ovo nije jedina manjkavost ovakvog dizajna.

Vremenom količina podataka raste, a vreme izvršavanja upita se, usled njihove kompleksnosti, obično povećava po nekoj zakonitosti zavisnoj od količine podataka. Takođe, usled korišćenja zajedničkog modela za upis i čitanje a samim tim i zajedničke baze koja je usko grlo, sistem obično može samo da skalira vertikalno (npr. korišćenjem više memorije ili povećanjem procesorske moći), što znači da je ograničeno i skupo a vrlo brzo dolazi do trenutka kad više nije moguće proširenje.

Mnoge aplikacije koriste model koji je reprezentacija nekog poslovnog procesa koji aplikacija treba da podrži. Model vrlo često preslikava struktura baze podataka kako bi se što lakše oni sačuvali bez mnogo transformacija. Usled uske povezanosti između slojeva same aplikacije kao i jednog modela koji je najviše prilagođen upisu i čuvanju podataka, implementacija i održavanje aplikacije se komplikuje dodavanjem novih funkcionalnosti koji treba podržati. Promene u modelu koje su potrebne samo prilikom čitanja, utiču i na upis.

Način na koji se prikazuju podaci, njihov kvantitet, struktura i sl., se razlikuju od načina na koji se ti podaci menjaju.

Veoma često su performanse čitanja i prikaza podataka u različitim kontekstima sistema mnogo bitnije za sveukupno funkcionisanje aplikacije nego performanse samog upisa. Pored toga, upis podataka se odvija ređe od samog korišćenja i čitanja tih podataka. Primera radi, podaci se jednom upišu, promene nad njima su retke, ali se često koriste u različitim kontekstima u aplikaciji. Ukoliko je aplikacija suočena sa ovakvim problemima, rešenje za bolje performanse i skalabilnost može da bude primena CQRS-a u određenim delovima aplikacije.

Šta je CQRS?

CQRS (Command Query Responsibility Segregation) je patern čijom se primenom razdvajaju operacije koje menjaju podatke od operacija koje služe za čitanje podataka. Operacije koje menjaju podatke se opisuju komandama (commands) dok se čitanje tih podataka opisuje upitima (queries).

Komande se iniciraju sa korisničkog interfejsa ili od nekog drugog sistema koji šalje zahteve ka aplikaciji kako bi se desila određena operacija. Upiti se takođe mogu inicirati sa korisničkog interfejsa ili pak sa neke druge klijentske aplikacije. U tipičnom CRUD pristupu, komande su kreiraj, promeni i izbriši neki resurs (npr. CreatePostCommand, UpdatePostCommand, i sl. za resurs Post), dok bi upiti bili tipični za prikaz resursa ili liste resursa (npr. GetAllPosts). Naravno, benefiti CQRS-a u slučaju CRUD-a su pod znakom pitanja, jer će se i povećati kompleksnost same aplikacije zbog uvođenja komandi, različitih komponenti za usmeravanje i prosleđivanje tih komandi na obradu i sl. Zato treba biti vrlo oprezan sa primenom CQRS-a u slučajevima gde to nije prirodno.

Međutim, ukoliko korisnički interfejs (ili neka druga klijentska aplikacija) nije zasnovan na CRUD-u i na razmeni samih podataka, tipično razmene DTO objekata, već registruje nameru korisnika da se neka određena operacija ili zadatak izvrši i prosleđuje dalje “komande” koje su proistekle iz same namere, onda CQRS može da se razmotri kao rešenje. Kao primer, takav interfejs može da nudi approvePost, publishPost itd. umesto direktnog menjanja polja status na resursu tipa Post CRUD operacijama. Tako osmišljen korisnički interfejs je task based UI, a benefit je jasan – aplikacija je svesna namere korisnika, jer nije u pitanju sušta razmena podataka gde aplikacija samo prosledi te podatke dalje ka bazi. Korisnik, s druge strane, takođe, dobija jasniju sliku o samom procesu koji aplikacija obrađuje. Operacije koje je moguće uraditi u datom trenutku su jasno naglašene nasuprot CRUD interfejsu gde korisnik mora da je upoznat sa svim poljima nekog DTO-a i načinima na koje ih je moguće menjati.

Primena CQRS-a

Na slici ispod opisan je primer tipične aplikacije koja korisiti zajednički model za čitanje i upis.

Dok god ovakav model zadovoljava sve zahteve funkcionalnosti i performansi, a pri tom nije kompleksan za održavanje, uvođenje CQRS-a će samo uneti dodatnu kompleksnost.

U zavisnosti od potreba, CQRS se može koristiti na različite načine.

Razdvajanje modela i uvođenje komandi i upita

Komande (najčešće metode) se grupišu u zasebne servise koje dalje pokreću odgovarajuće operacije nad zajedničkim modelom. Model se dalje prenosi u bazu, a ta ista baza se korisiti za prikaz podataka preko zejedničkog modela i servisa za upite (queries).

Pošto su definisani razdvojeni interfejsi ka korisničkom interfejsu (ili nekom drugom sistemu) za upis i čitanje, moguće je koristiti i različite modele. Iako se ista baza podataka koristi i u jednom i u drugom modelu, postoji mogućnost da se koriste posebni prikazi koje nudi sama baza podataka (npr. materialized views) ili dodatne tabele samo za čitanje podataka tamo gde je to potrebno koje već sadrže pripremljene podatke za prikaz, tako da nije potrebno dodatno izračunavanje i obrada u trenutku prikaza. Negde je vrlo korisno i napraviti tabele koje oslikavaju podatke 1:1, koje se prikazuju na korisničkom interfejsu (ili su izložene preko nekog API-ija).

Sama propagacija promena u podacima ka grani za čitanje obavlja se automatski uz pomoć ugrađenih funkcija baza ili odvojenim upisom tih podataka u dodatne tabele najčešće sinhrono. Naravno, promene je moguće propagirati i asinhrono različitim tehnikama (Messaging/Queues, AMQP, Event bus, itd.) ukoliko ta priprema podataka za model za čitanje utiče na performanse samog upisa. Onda najčešće govorimo o eventual consistency modelu za garanciju konzistentnosti.

Zasebne baze podataka

Kompletno odvojena grana za čitanje od grane za upis podataka. Pored zasebnih softverskih komponenti, vrlo često se ove dve grane i pripremaju za odvojenu instalaciju kako bi mogle i odvojeno da skaliraju. Najčešće je potreba za čitanjem podataka dosta veća, a ta asimetričnost može da se oslikava i u skaliranju komponenti sistema.

CommandHandler prihvata komandu i primenjuje je nad modelom za upis podataka. Model za upis na osnovu komande i trenutnog stanja resursa, može da napravi obaveštenja (events) i objavi ih sinhrono ili asinhrono koristeći razne principe i tehnike, a koje će da se upotrebe za sinhronizaciju podataka između modela za upravljanje komandama i baza koje su podrška modelu za čitanje i prikaz podataka.

Najveća prednost ovakvog pristupa jeste mogućnost korišćenja baza podataka koja najviše odgovara modelu koji je koristi. Za model za čitanje podataka mogu da se razmotre različite tehnike za smeštanje podataka uključujći upotrebu relacionih, NoSQL, Full Text Search, Graph, Distributed Cache i drugih baza i alata. Takođe, sam razvoj aplikacije bez zajedničkog modela može da obavlja više timova.

CQRS se posebno dobro uklapa u dizajn gde se primenjuje DDD (Domain Driven Design) koji uvodi Bounded Context-e gde se svaki taj kontekst može modelovati na različite načine. Osnovu DDD-a čine agregati (aggregates) koji sadrže samo ponašanje. Usled toga, agregati predstavljaju prirodan nastavak task based korisničkog interfejsa i samim tim, dobro mesto za implementaciju CQRS-a. Na taj način sam model za upis može da objavljuje obaveštenja (events) o promenama u samom modelu koja su u stvari ulazne vrednosti za baze za prikaz i čitanje podataka. Pri tome, imamo jako jednostavan model za čitanje.

CQRS i Event Sourcing

Ovakav dizajn, ukoliko je to potrebno, može se proširiti i uvođenjem Event Sourcing-a. Model se time dodatno komplikuje, međutim u nekim slučajevima i procesima koje aplikacija treba da podrži, dobijaju se razne pogodnosti, pre svega:

  • Jednostavniji upis u komandnom modelu. Npr. nema blokiranja upisa i zaključavanja entiteta u bazi kad više korisnika istovremeno menja iste podatke

  • Stanje nekog resursa u bilo kom trenutku u prošlosti

  • Ponovni pregled događaja koji su prethodili nekom trenutku

  • Reprodukcija događaja i izračunavanje stanja do nekog trenutka kako bi se obezbedili podaci za model za prikaz podataka, bilo da je neka nova baza za čitanje ili korekcije na postojećoj, prikupljanje podataka za neke druge alate, npr. za statistiku, predikciju, obradu uz pomoć veštačke inteligencije i sl.

Gde koristiti CQRS

Kao i većina paterna, CQRS je koristan u nekim rešenjima, u nekim ne. U većini aplikacija je CRUD pristup verovatno bolje rešenje. Međutim, CQRS se može razmotriti na pojedinim delovima aplikacija koje treba da podrže dosta korisnika – klijentskih aplikacija, intenzivan upis i visoku pouzdanost i dostupnost u sledećim situacijama:

  • Struktura i kontekst podataka se dosta razlikuje pri upisu od načina kasnije upotrebe (čitanja)

  • Model postaje kompleksan vremenom zato što sadrži sve što je potrebno i za čitanje i za upis a ne preklapaju se toliko da bismo ga zadržali

  • Dolazi do problema sa performansama kod čitanja podataka najčešće zbog kompleksnih upita ka bazi podataka

  • Potreba za skaliranjem, najčešće dela za čitanja podataka

  • Istovremeni pristup i promene na podacima (concurrency)

  • Ako je Eventual consistency prihvatljiv tamo gde se korisiti zarad optimizacije upisa podataka

Treba imati u vidu da CQRS nije tip arhitekture. CQRS može da se primeni na pojedinim delovima, npr. unutar nekih servisa (koji prate npr. višeslojnu arhitekturu), nikako ne treba da se uzima kao univerzalno rešenje na nivou cele aplikacije.

Literatura:

https://martinfowler.com/bliki/CQRS.html

http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/

http://udidahan.com/2009/12/09/clarified-cqrs/

https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing

https://developer.ibm.com/articles/cl-build-app-using-microservices-and-cqrs-trs/

Share

Prijavi se da prvi dobijaš nove blogove i vesti.

Ostavite odgovor

Njegoš Kurkić

Technical Lead @Seavus
mm

Od 2005.-e u IT-ju na razvoju aplikacija i servisa kao podrška za razne segmente poslovanja i industrije uglavnom pisanih u Java programskom jeziku. Učestvuje u analizi i planiranju, izradi arhitekure softverskih rešenja kao i samom razvoju.

Trenutno radi u Seavusu kao Technical Lead za Java tehnologije.

Prijavi se da prvi dobijaš nove blogove i vesti.

Kategorije