Otklanjajte pogreške u svojoj arhitekturi, ne samo u kodu
Tvrdoglav produkcijski bug može biti dar: možda vam govori da kod radi točno ono što je arhitektura učinila vjerojatnim.
Iskusni inženjeri uče razlikovati grešku u funkciji od greške u obliku sustava. Prva pita: „Koji je redak pogrešan?” Druga pita: „Zašto ovaj zahtjev može dosegnuti tri servisa, zapisati u dvije baze podataka, a i dalje nema jasnog vlasnika ishoda?” Obje su važne, ali samo jedna sprječava da se ista kategorija incidenta vrati pod drugim tiketom.
Otklanjanje grešaka u arhitekturi znači pratiti ponašanje preko granica: HTTP zahtjeve, redove, sheme, kontejnere, predmemorije, konfiguraciju implementacije i operativne pretpostavke. Manje je uredno od popravljanja uvjeta, ali tu se pouzdanost i održivost obično dobivaju ili gube.
Simptomi često upućuju dalje od stvarnog kvara
Razmotrite PHP krajnju točku koja povremeno prekoračuje vrijeme čekanja pri stvaranju narudžbe. Profiliranje može otkriti spor upit prema bazi podataka, pa je neposredan odgovor dodati indeks. To može biti ispravno. No širi trag može pokazati da krajnja točka također poziva pružatelja plaćanja, rezervira zalihe, šalje e-poruku i sinkrono ažurira analitiku prije vraćanja odgovora.
Spori upit tada je čimbenik, a ne nužno arhitektonski problem. Krajnja točka postala je orkestracijski sloj za rad s vrlo različitim karakteristikama latencije, neuspjeha i ponovnih pokušaja.
Korisno prvo pitanje glasi: što mora uspjeti prije nego što korisnik može dobiti koristan odgovor? Autorizacija plaćanja može biti nužna. Dostava e-poruke vjerojatno nije. Analitika gotovo sigurno nije. Razdvajanje tih odgovornosti smanjuje vrijeme odgovora i olakšava objašnjavanje neuspjeha.
Pratite cijeli zahtjev, a ne samo trag stoga
Trag stoga govori vam gdje se iznimka pojavila. Arhitektonski trag govori vam što se dogodilo prije i poslije nje. Za API zahtjev koji ne uspijeva, mapirajte put od ulaza do trajnog stanja i vanjskih nuspojava.
- Koji servis prihvaća zahtjev i provjerava ga?
- Koji su zapisi u bazu podataka mjerodavni?
- Koji se vanjski pozivi odvijaju sinkrono?
- Što se automatski ponavlja i tko to radi?
- Što se događa ako proces prestane raditi nakon što jedna nuspojava uspije?
- Koja komponenta može sigurno ponoviti operaciju?
To posljednje pitanje otkriva čestu slabost. Istek vremena čekanja na strani klijenta ne znači da poslužitelj nije ništa učinio. Ako klijent ponovno pokuša POST /orders nakon mrežnog kvara, prvi je zahtjev možda stvorio narudžbu, ali nije uspio prije slanja odgovora. Bez strategije idempotentnosti, ponovni pokušaj može stvoriti drugu narudžbu.
Idempotentnost nije samo detalj API-ja. To je ugovor između klijenta, aplikacije, baze podataka i nizvodnih servisa. Praktičan dizajn pohranjuje ključ idempotentnosti koji pruža klijent uz rezultirajući resurs ili odgovor. Ponovljeni zahtjevi s istim ključem vraćaju prethodni rezultat umjesto da ponavljaju poslovnu operaciju.
Učinite prijelaze stanja eksplicitnima
Mnogi neuspjesi pozadinskog sustava postaju jasniji kada su predstavljeni kao prijelazi, a ne kao logičke zastavice. Narudžba nije jednostavno „plaćena” ili „neplaćena”. Može biti na čekanju, autorizirana, potvrđena, otkazana ili refundirana. Svaki prijelaz treba imati vlasnika, dopuštena prethodna stanja i jasan način postupanja s ponovljenim porukama.
Za to nije potreban složen okvir. Potrebno je oduprijeti se iskušenju da nepovezani kontroleri, poslovi i administratorske skripte ažuriraju ista polja prema neznatno različitim pravilima.
Transakcije štite manje nego što ljudi pretpostavljaju
Transakcije baze podataka nužne su, ali pokrivaju samo resurse koji sudjeluju u toj transakciji. Ne poništavaju e-poruku, ne vraćaju HTTP poziv niti poništavaju poruku koja je već objavljena posredniku.
Krhki obrazac izgleda ovako:
$order = $orders->create($payload);
$paymentGateway->charge($order);
$eventBus->publish(new OrderCreated($order));
Ako naplata uspije, a objavljivanje ne uspije, sustav ima plaćenu narudžbu bez događaja. Ponovni pokušaj cijelog zahtjeva može ponovno naplatiti kupca. Omatanje zapisa u bazu podataka transakcijom ne rješava vanjske nuspojave.
Otporniji pristup jest zajedno potvrditi poslovno stanje i zapis odlaznog događaja. Radnik kasnije može objaviti taj zapis, ponovno pokušati nakon neuspjeha i označiti ga kao isporučenog. Taj se obrazac često naziva outbox. Njegova vrijednost nije u nazivu; njegova je vrijednost u tome što se trajno stanje i namjera obavještavanja drugih sustava bilježe atomski.
Potrošači i dalje moraju biti spremni na dvostruku isporuku. U distribuiranim sustavima, „točno jednom” obično je skupa tvrdnja koja skriva pretpostavke. Dizajniranje rukovatelja da budu idempotentni općenito je iskrenije i robusnije.
Granice baze podataka arhitektonske su granice
Baza podataka može biti brza, a ipak pogrešna točka povezanosti. Kada više servisa izravno ažurira iste tablice, njihove implementacije postaju isprepletene. Promjena sheme više nije lokalna migracija; ona je koordinirano izdanje među nepoznatim pozivateljima.
Počnite utvrđivanjem koji servis posjeduje svaki poslovni koncept. Drugi servisi mogu primati podatke putem API-ja, događaja ili namjenski izrađenog modela za čitanje. To se može činiti sporijim od dopuštanja izravnog SQL pristupa, ali čini odgovornost vidljivom i smanjuje slučajne ovisnosti.
Unutar PHP aplikacije vrijedi ista ideja. Izbjegavajte dopuštati da kontroleri, rukovatelji naredbi, radnici reda i predlošci svaki kodiraju vlastitu logiku upita i poslovna pravila. Granice transakcija i odluke domene smjestite u servise čiji nazivi opisuju operaciju koja se izvodi, kao što su ConfirmOrder ili ReserveInventory.
Docker treba otkrivati vrijeme izvođenja, a ne ga prikrivati
Kontejnerizacija čini implementacije ponovljivima samo kada je konfiguracija eksplicitna. Kontejner koji radi lokalno jer se prešutno oslanja na montirani izvorni kod, proširenje samo za razvoj ili već postojeću bazu podataka nije prenosiv; samo je praktičan.
Konfiguraciju vremena izvođenja, gdje je prikladno, držite izvan slike, provjerite potrebne postavke pri pokretanju i neka provjere stanja odražavaju spremnost, a ne samo postojanje procesa. PHP-FPM proces može raditi dok su vjerodajnice baze podataka neispravne ili dok potrebna migracija nije primijenjena.
Također odvojite brige izgradnje od briga vremena izvođenja. Alati za razvoj, ovisnosti za testiranje i prevoditelji ne pripadaju nužno produkcijskoj slici. Manje slike mogu poboljšati pokretanje i smanjiti broj pokretnih dijelova, ali primarni je cilj jasnoća: implementirani kontejner treba sadržavati točno ono što aplikacija treba za rad.
Rad na performansama treba proračun sustava
Podešavanje performansi često je pogrešno usmjereno jer se jedna metrika tretira kao cijeli sustav. Brži upit ne pomaže mnogo ako su skupovi veza iscrpljeni, poništavanje predmemorije stvara nalete promašaja ili red radnika raste brže nego što ga potrošači mogu obraditi.
Definirajte proračune za važan put: latenciju odgovora, vrijeme baze podataka, vrijeme vanjskih poziva, upotrebu memorije i kašnjenje reda. Zatim mjerite na granici na kojoj korisnici osjećaju rezultat. To pretvara neodređene izjave poput „aplikacija je spora” u provjerljiva pitanja.
Na primjer, predmemoriranje zapisa o proizvodu može biti razumno. Predmemoriranje odluka o autorizaciji bez jasnog modela poništavanja i isteka može stvoriti sigurnosni problem. Svaka predmemorija treba odgovor na tri pitanja: što je čini valjanom, kada istječe i što se događa kada nije dostupna?
Dizajnirajte za sljedeći neuspjeh
Najbolje arhitektonsko otklanjanje grešaka ostavlja više od zakrpanog incidenta. Poboljšava opažljivost, sužava vlasništvo i čini sljedeći neuspjeh manje dvosmislenim. Dodajte identifikatore korelacije u zapisnike zahtjeva i poslove. Bilježite značajne promjene stanja. Izložite dubinu reda, stope pogrešaka i latenciju ovisnosti. Napišite operativne priručnike za neuspjehe koji su predvidljivi, ali rijetki.
Najvažnije je da ponavljajuće bugove tretirate kao povratnu informaciju o granicama. Ako ista pogreška zahtijeva popravke u nekoliko servisa, apstrakcija je možda na pogrešnom mjestu. Ako svaka nova značajka treba posebnu iznimku, tijek rada možda nije dovoljno specificiran. Ako implementacija zahtijeva koordinirane ručne korake, sustav možda nosi skrivenu povezanost.
Kod je mjesto gdje softver izražava odluke. Arhitektura je mjesto gdje se te odluke susreću s vremenom, neuspjehom, opsegom i promjenom. Otklanjanje grešaka u oboma način je na koji pozadinski sustav postaje ne samo funkcionalan nego i razumljiv pod pritiskom.