Ili je svaka nesretna tvrtka s monolitom nesretna na svoj način.
Razvoj Dodo IS sustava započeo je odmah, poput Dodo Pizza biznisa - 2011. godine. Temeljio se na ideji potpune i potpune digitalizacije poslovnih procesa, te sam, koji je i tada 2011. godine izazvao mnoga pitanja i skepticizam. Ali već 9 godina idemo tim putem - vlastitim razvojem koji je započeo s monolitom.
Ovaj članak je “odgovor” na pitanja “Zašto prepravljati arhitekturu i raditi tako velike i dugoročne promjene?” na prethodni članak “Povijest Dodo IS arhitekture: put pozadinskog ureda”. Počet ću s time kako je počeo razvoj Dodo IS-a, kako je izgledala početna arhitektura, kako su se pojavili novi moduli i zbog kojih je problema bilo potrebno napraviti velike promjene.
Serija članaka "Što je Dodo IS?" govori o:
Rani monolit u Dodo IS (2011.-2015.). (Ti si ovdje)
Resursi za razvoj prvog modula za primanje narudžbi bili su ograničeni. Trebalo je napraviti puno, brzo i s malom ekipom. Mali tim se sastoji od 2 programera koji su postavili temelje za cijeli budući sustav.
Njihova prva odluka odredila je buduću sudbinu tehnološkog niza:
Pozadina na ASP.NET MVC, C# jeziku. Programeri su bili dotnetteri; ovaj im je skup bio poznat i ugodan.
Frontend na Bootstrapu i JQueryju: korisnička sučelja temeljena na prilagođenim stilovima i skriptama.
MySQL baza podataka: nema troškova licence, jednostavna za korištenje.
Poslužitelji na Windows Serveru, jer je .NET tada mogao biti samo na Windowsima (nećemo raspravljati o Mono).
Fizički, sve je to bilo izraženo u “domaćinskom stolu”.
Arhitektura aplikacije za prihvaćanje naloga
Tada su već svi pričali o mikroservisima, a SOA se koristila u velikim projektima oko 5 godina, primjerice WCF je izašao 2006. godine. Ali onda su odabrali pouzdano i provjereno rješenje.
Evo ga.
Asp.Net MVC je Razor, koji na zahtjev obrasca ili klijenta proizvodi HTML stranicu s renderiranjem na poslužitelju. Na klijentu CSS i JS skripte već prikazuju informacije i po potrebi izvode AJAX zahtjeve putem JQueryja.
Zahtjevi na poslužitelju spadaju u klase *Controller, gdje metoda obrađuje i generira konačnu HTML stranicu. Kontroleri podnose zahtjeve sloju logike koji se zove *Services. Svaka od usluga odgovarala je nekom aspektu poslovanja:
Na primjer, DepartmentStructureService pružio je informacije o pizzerijama i odjelima. Odjel je skupina pizzerija kojima upravlja jedan primatelj franšize.
ReceivingOrdersService primio je i izračunao sadržaj narudžbe.
I SmsService je poslao SMS pozivom API servisa za slanje SMS-a.
Servisi su obrađivali podatke iz baze i pohranjivali poslovnu logiku. Svaki servis je imao jedno ili više *Repozitorija s odgovarajućim nazivom. Već su sadržavali upite pohranjenim procedurama u bazi podataka i sloj mapera. Repozitoriji su imali poslovnu logiku, osobito mnogi od njih koji su proizvodili podatke za izvješća. ORM se nije koristio, svi su se oslanjali na rukom pisani sql.
Postojao je i sloj modela domene i općih pomoćnih klasa, na primjer klasa Order, koja je pohranjivala red. Tamo, u sloju, nalazio se pomoćnik za pretvorbu prikaza teksta prema odabranoj valuti.
Sve se to može prikazati ovim modelom:
Način narudžbe
Razmotrimo pojednostavljeni početni način stvaranja takvog naloga.
U početku je stranica bila statična. Na njemu su bile cijene, a na vrhu je stajao broj telefona i natpis “Ako želite pizzu, nazovite broj i naručite”. Da bismo naručili, moramo implementirati jednostavan tijek:
Klijent odlazi na statičnu web stranicu s cijenama, odabire proizvode i poziva broj naveden na web stranici.
Kupac imenuje proizvode koje želi dodati narudžbi.
Daje svoju adresu i ime.
Operater prihvaća narudžbu.
Narudžba se prikazuje u sučelju prihvaćenih narudžbi.
Sve počinje prikazom izbornika. Prijavljeni korisnik operater prihvaća samo jednu narudžbu odjednom. Stoga se nacrt košarice može pohraniti u njegovu sesiju (korisnička sesija pohranjuje se u memoriju). Postoji objekt Cart koji sadrži proizvode i informacije o kupcima.
Klijent imenuje proizvod, operater klikne na + pored proizvoda, a zahtjev se šalje poslužitelju. Podaci o proizvodu se izvlače iz baze i podaci o proizvodu se dodaju u košaricu.
Primijetiti. Da, ovdje ne morate izvlačiti proizvod iz baze podataka, već ga prenijeti s prednje strane. Ali radi jasnoće, pokazao sam točno put od baze.
Zatim unesite adresu i ime klijenta.
Kada kliknete na "Izradi narudžbu":
Šaljemo zahtjev na OrderController.SaveOrder().
Dobivamo košaricu sa sesije, ima proizvoda u količini koja nam je potrebna.
Košaricu dopunjujemo podacima o klijentu i prosljeđujemo ih metodi AddOrder klase ReceivingOrderService, gdje se spremaju u bazu podataka.
Baza podataka ima tablice s nalogom, sadržajem naloga, klijentom i sve je povezano.
Sučelje za prikaz naloga ide i izvlači najnovije naloge te ih prikazuje.
Novi moduli
Primanje narudžbe bilo je važno i potrebno. Ne možete voditi posao s pizzom ako nemate narudžbu za prodaju. Stoga je sustav počeo stjecati funkcionalnost - otprilike od 2012. do 2015. godine. Tijekom tog vremena pojavilo se mnogo različitih blokova sustava, koje ću nazvati moduli, za razliku od koncepta usluge ili proizvoda.
Modul je skup funkcija koje su objedinjene nekim zajedničkim poslovnim ciljem. Štoviše, fizički se nalaze u istoj aplikaciji.
Moduli se mogu nazvati sistemskim blokovima. Na primjer, ovo je modul za izvješćivanje, administrativna sučelja, pratilac kuhinjskih proizvoda, ovlaštenje. Sve su to različita korisnička sučelja, neka čak imaju različite vizualne stilove. Štoviše, sve je unutar jedne aplikacije, jednog pokrenutog procesa.
Tehnički, moduli su dizajnirani kao Area (ova ideja je čak ostala u asp.net jezgra). Postojale su zasebne datoteke za sučelje, modele, kao i vlastite klase kontrolera. Kao rezultat toga, sustav je transformiran iz takvog...
... na ovo:
Neki moduli su implementirani na zasebnim stranicama (izvršni projekt), zbog potpuno odvojene funkcionalnosti, a dijelom i zbog donekle odvojenog, usmjerenijeg razvoja. Ovaj:
osoblje - osobni račun zaposlenika. Razvijen je zasebno i ima vlastitu ulaznu točku i poseban dizajn.
fs — projekt za statički hosting. Kasnije smo se udaljili od toga, premjestivši sav statični sadržaj na Akamai CDN.
Preostali blokovi nalazili su se u BackOffice aplikaciji.
Objašnjenje imena:
Blagajna - blagajna restorana.
ShiftManager - sučelja za ulogu “Shift Manager”: operativna statistika prodaje pizzerije, mogućnost stavljanja proizvoda na stop listu, promjena narudžbe.
OfficeManager - sučelja za uloge “Upravitelj pizzerije” i “Primatelj franšize”. Ovdje možete pronaći funkcije za postavljanje pizzerije, njezine bonus promocije, primanje i rad sa zaposlenicima te izvješća.
PublicScreens - sučelja za televizore i tablete koji vise u pizzerijama. Televizori prikazuju jelovnik, reklamne informacije i status narudžbe nakon isporuke.
Koristili su zajednički servisni sloj, zajednički blok domenskih klasa Dodo.Core i zajedničku bazu. Ponekad su ipak mogli voditi kroz prolaze jedan do drugoga. Osim toga, pojedinačne stranice, poput dodopizza.ru ili personal.dodopizza.ru, također su pristupale zajedničkim uslugama.
Kada su se pojavili novi moduli, pokušali smo što je više moguće ponovno koristiti već kreirani kod za usluge, pohranjene procedure i tablice u bazi podataka.
Za bolje razumijevanje razmjera modula izrađenih u sustavu, evo dijagrama iz 2012. godine s planovima razvoja:
Do 2015. sve je bilo na pravom putu, a još više je bilo u proizvodnji.
Prihvat narudžbi prerastao je u zaseban blok Kontakt centra, gdje narudžbe prima operater.
U pizzerijama su se pojavili javni zasloni s jelovnicima i informacijama.
Kuhinja ima modul koji automatski pušta glasovnu poruku “Nova pizza” kada stigne nova narudžba, a ispisuje i račun za kurira. To uvelike pojednostavljuje procese u kuhinji i omogućuje zaposlenicima da ne budu ometani velikim brojem jednostavnih operacija.
Dostavni blok postao je zasebna Dostavna blagajna, gdje se narudžba izdavala kuriru, koji je prethodno preuzeo svoju smjenu. Njegovo radno vrijeme uzeto je u obzir za obračun plaće.
Paralelno, od 2012. do 2015. pojavilo se više od 10 programera, otvoreno 35 pizzerija, sustav je implementiran u Rumunjsku i pripremljen za otvaranje točaka u SAD-u. Programeri više nisu bili uključeni u sve zadatke, već su bili podijeljeni u timove. svaki specijaliziran za svoj dio sustava.
Problemi
Uključujući i zbog arhitekture (ali ne samo).
Kaos u bazi
Jedna baza je prikladna. Moguće je postići konzistentnost i nauštrb alata ugrađenih u relacijske baze podataka. Rad s njim je poznat i zgodan, pogotovo ako ima malo tablica i malo podataka.
Ali tijekom 4 godine razvoja, baza podataka je sadržavala oko 600 tablica, 1500 pohranjenih procedura, od kojih su mnoge imale i logiku. Nažalost, pohranjene procedure ne pružaju mnogo koristi pri radu s MySQL-om. Baza ih ne sprema u predmemoriju, a pohranjivanje logike u njih komplicira razvoj i otklanjanje pogrešaka. Ponovno korištenje koda također je teško.
Mnoge tablice nisu imale odgovarajuće indekse, negdje je, naprotiv, bilo puno indeksa, što je otežavalo umetanje. Oko 20 tablica je moralo biti izmijenjeno—transakcija za stvaranje narudžbe mogla je trajati oko 3-5 sekundi.
Podaci u tablicama nisu uvijek bili u najprikladnijem obliku. Negdje je bilo potrebno napraviti denormalizaciju. Dio redovito zaprimljenih podataka bio je u stupcu u obliku XML strukture, što je produžilo vrijeme izvršenja, produžilo upite i kompliciralo razvoj.
Iste tablice bile su predmet vrlo heterogenih zahtjeva. Popularne tablice, poput gore spomenute, bile su posebno pogođene narudžbe ili tablice picerija. Korišteni su za prikaz operativnih sučelja u kuhinji i analitici. Stranica ih je također kontaktirala (dodopizza.ru), gdje bi puno zahtjeva moglo iznenada stići u bilo kojem trenutku.
Podaci nisu agregirani a mnogi su se izračuni odvijali u hodu pomoću baze. To je stvorilo nepotrebne izračune i dodatno opterećenje.
Često je kod otišao u bazu podataka kada to nije mogao učiniti. Negdje je nedostajalo skupnih operacija, negdje bi bilo potrebno jedan zahtjev razdvojiti na nekoliko kroz kod kako bi se ubrzalo i povećala pouzdanost.
Kohezija i konfuzija u kodu
Moduli koji su trebali biti odgovorni za svoj dio posla nisu to radili pošteno. Neki od njih imali su dupliciranje funkcija za uloge. Na primjer, lokalni trgovac koji je odgovoran za marketinšku aktivnost mreže u svom gradu morao je koristiti i sučelje "Administrator" (za postavljanje promocija) i sučelje "Upravitelj ureda" (za pregled utjecaja promocija na poslovanje). Naravno, unutar oba modula koristila se ista usluga, koja je radila s bonus promocijama.
Servisi (klase unutar jednog monolitnog velikog projekta) mogu se međusobno pozivati kako bi obogatili svoje podatke.
Sa samim klasama modela koje pohranjuju podatke, rad u kodu odvijao se drugačije. Negdje su postojali konstruktori preko kojih ste mogli specificirati potrebna polja. Negdje se to radilo putem javnih posjeda. Naravno, dobivanje i transformiranje podataka iz baze bilo je raznoliko.
Logika je bila ili u kontrolerima ili servisnim klasama.
Čini se da su to manji problemi, ali su uvelike usporili razvoj i smanjili kvalitetu, što je dovelo do nestabilnosti i grešaka.
Složenost velikog razvoja
Poteškoće su se javile u samom razvoju. Bilo je potrebno kreirati različite blokove sustava, i to paralelno. Postalo je sve teže uklopiti potrebe svake komponente u jedan kod. Nije bilo lako složiti se i zadovoljiti sve komponente u isto vrijeme. Tome su pridodana ograničenja u tehnologiji, posebice u pogledu baze i prednjeg kraja. Bilo je potrebno napustiti JQuery u korist okvira visoke razine, posebno u pogledu usluga klijentima (web stranica).
Neki dijelovi sustava mogli bi koristiti baze podataka koje su za to prikladnije. Na primjer, kasnije smo imali presedan za prebacivanje s Redisa na CosmosDB za pohranu košarice za narudžbu.
Timovi i programeri koji rade u svom području jasno su željeli više neovisnosti za svoje usluge, kako u smislu razvoja tako i u smislu uvođenja. Sukobi tijekom spajanja, problemi tijekom izdanja. Ako je za 5 programera ovaj problem beznačajan, onda bi s 10, a još više s planiranim rastom, sve postalo ozbiljnije. A tek je uslijedio razvoj mobilne aplikacije (počeo je 2017., a 2018. veliki pad).
Različiti dijelovi sustava zahtijevali su različite pokazatelje stabilnosti, no zbog jake povezanosti sustava to nismo mogli pružiti. Pogreška prilikom razvijanja nove funkcije u administratorskoj ploči mogla je rezultirati prihvaćanjem narudžbe na stranici, jer je kod uobičajen i može se ponovno koristiti, baza podataka i podaci također su isti.
Vjerojatno bi bilo moguće izbjeći te greške i probleme u okviru takve monolitno-modularne arhitekture: stvoriti razdvajanje odgovornosti, refaktorirati i kod i bazu podataka, jasno odvojiti slojeve jedne od drugih i pratiti kvalitetu svaki dan. Ali odabrana arhitektonska rješenja i fokus na brzo proširenje funkcionalnosti sustava doveli su do problema u pogledu stabilnosti.
Kako je blog Power of Mind stavio blagajne u restorane
Kad bi se rast mreže (i opterećenja) pizzeria nastavio istim tempom, onda bi nakon nekog vremena padovi bili takvi da se sustav ne bi oporavio. Sljedeća priča dobro ilustrira probleme s kojima smo se počeli suočavati 2015. godine.
U blogu "Snaga uma"postojao je widget koji je prikazivao podatke o prihodu za godinu za cijelu mrežu. Widget je pristupio javnom Dodo API-ju, koji pruža ove podatke. Ove su statistike sada dostupne na http://dodopizzastory.com/. Widget se prikazivao na svakoj stranici i slao je zahtjeve na mjeraču vremena svakih 20 sekundi. Zahtjev je otišao na api.dodopizza.ru i pitao:
broj pizzerija u mreži;
ukupni mrežni prihod od početka godine;
današnji prihod.
Zahtjev za statistiku prihoda otišao je ravno u bazu podataka i počeo tražiti podatke o narudžbama, agregirati podatke izravno u hodu i izdavati iznos.
Blagajne u restoranima išle su na istu tablicu narudžbi, učitavale listu prihvaćenih narudžbi za danas te su joj dodavale nove narudžbe. Blagajne su svoje zahtjeve postavljale svakih 5 sekundi ili kad je stranica osvježena.
Dijagram je izgledao ovako:
Jednog dana u jesen, Fjodor Ovčinnikov je napisao dug i popularan članak na svom blogu. Puno je ljudi došlo na blog i počelo sve pažljivo čitati. Dok je svatko od ljudi koji su došli čitao članak, widget prihoda radio je ispravno i zahtijevao API svakih 20 sekundi.
API je nazvao pohranjenu proceduru za izračun iznosa svih narudžbi od početka godine za sve pizzerije u mreži. Agregacija se temeljila na tablici narudžbi, koja je vrlo popularna. Na njega idu sve blagajne svih u to vrijeme otvorenih restorana. Blagajne su prestale reagirati i narudžbe se nisu primale. Također nisu prihvaćeni sa stranice, nisu se pojavili na trackeru, a voditelj smjene ih nije mogao vidjeti u svom sučelju.
Ovo nije jedina priča. Do jeseni 2015., svakog petka opterećenje sustava bilo je kritično. Nekoliko puta smo isključili javni API, a jednom smo čak morali isključiti stranicu jer ništa nije pomoglo. Postojao je čak i popis usluga s redoslijedom gašenja pod velikim opterećenjem.
Od ovog trenutka počinje naša borba s opterećenjem i stabilizacijom sustava (od jeseni 2015. do jeseni 2018.). Tada se to dogodilo"Veliki pad" Nadalje, ponekad je bilo i kvarova, od kojih su neki bili vrlo osjetljivi, no opće razdoblje nestabilnosti sada se može smatrati završenim.
Brz poslovni rast
Zašto se nije moglo "odmah dobro napraviti"? Pogledajte samo sljedeće grafikone.
Također 2014.-2015. bilo je otvorenje u Rumunjskoj, au pripremi je bilo otvaranje u SAD-u.
Lanac je vrlo brzo rastao, otvarale su se nove zemlje, pojavili su se novi formati pizzerija, na primjer, pizzerija je otvorena u food courtu. Sve je to zahtijevalo značajnu pozornost upravo na proširenju funkcija Dodo IS-a. Bez svih ovih funkcija, bez praćenja u kuhinji, obračunavanja proizvoda i gubitaka u sustavu, prikaza dostave narudžbi u dvorani s hranom, malo je vjerojatno da bismo sada govorili o „ispravnoj“ arhitekturi i „ispravnoj“ ” pristup razvoju.
Još jedna prepreka pravodobnoj reviziji arhitekture i općenitom obraćanju pažnje na tehničke probleme bila je kriza iz 2014. godine. Takve stvari štete mogućnostima rasta timova, posebno za mladu tvrtku kao što je Dodo Pizza.
Brza rješenja koja su pomogla
Problemi su zahtijevali rješenja. Konvencionalno, rješenja se mogu podijeliti u 2 skupine:
One brze koje gase vatru i daju nam malu sigurnost i kupuju nam vrijeme da se promijenimo.
Sustavno i, stoga, dugo. Reinženjering niza modula, dijeljenje monolitne arhitekture na zasebne servise (većina njih nisu mikro, već makro servisi, a o tome više izvješće Andreja Morevskog).
Suha lista brzih promjena je:
Povećaj osnovni glavni
Naravno, prva stvar koja se radi u borbi protiv opterećenja je povećanje snage poslužitelja. To je učinjeno za glavnu bazu podataka i web poslužitelje. Nažalost, to je moguće samo do određene granice; preko toga postaje preskupo.
ReadReplicaza zahtjeve imenika. Koristi se za čitanje imenika, kao što su grad, ulica, pizzerija, proizvodi (polako promijenjena domena), te u onim sučeljima gdje je prihvatljivo malo kašnjenje. Bilo je 2 ove replike, osigurali smo njihovu dostupnost na isti način kao i majstor.
ReadReplica za zahtjeve izvješća. Ova baza podataka bila je manje dostupna, ali su sva izvješća išla u nju. Oni mogu imati teške zahtjeve za ogromnim ponovnim izračunima podataka, ali ne utječu na glavnu bazu podataka i operativna sučelja.
Predmemorije u kodu
Nigdje u kodu (uopće) nije bilo predmemorije. To je dovelo do dodatnih, ne uvijek potrebnih zahtjeva za učitanu bazu podataka. U početku su predmemorije bile i u memoriji i na vanjskoj usluzi predmemorije, bio je to Redis. Sve je vremenski nevažeće, postavke su navedene u kodu.
Više poslužitelja za backend
Pozadina aplikacije također je morala biti skalirana kako bi izdržala povećana opterećenja. Bilo je potrebno napraviti klaster od jednog IIS servera. Preselili smo se prijavna sesija iz memorije na RedisCacheu, što je omogućilo stvaranje nekoliko poslužitelja iza jednostavnog balansera opterećenja s kružnim radom. Isprva je korišten isti Redis kao i za spremišta, a zatim su podijeljeni u nekoliko.
Kao rezultat toga, arhitektura je postala složenija...
...ali dio napetosti je popustio.
A onda je bilo potrebno preraditi učitane komponente, što smo mi preuzeli. O tome ćemo govoriti u sljedećem dijelu.