Razvoj

System Architecture: Planning for Failure, Not Just Success

Arhitektura sustava: planiranje za neuspjeh, a ne samo za uspjeh

Većina arhitektonskih dijagrama priča utješnu priču. Zahtjev stiže, aplikacija ga obrađuje, baza podataka odgovara, a korisniku se vraća koristan rezultat. Taj sretan tijek je važan, ali rijetko je mjesto na kojem sustavi stječu ugled.

Produkcijski sustavi žive u manje elegantnim trenucima: pružatelj usluge plaćanja odgovara presporo, spremnik veza baze podataka je iscrpljen, Docker spremnik se ponovno pokrene usred zadatka ili klijent ponovi zahtjev nakon što ga je poslužitelj već dovršio. Dobra arhitektura ne pretpostavlja da su ti događaji iznimni. Čini ih razumljivima, ograničenima i oporavljivima.

Počnite od načina na koje zahtjev može neuspješno završiti

Za svaku važnu krajnju točku postavite namjerno pesimistično pitanje: što se događa ako ovaj korak ne uspije nakon što je prethodni korak uspio?

Razmotrite API koji stvara narudžbu. Može provjeriti valjanost unosa, rezervirati zalihe, zapisati redak narudžbe, naplatiti putem pružatelja usluge plaćanja i objaviti događaj za e-poštu ili isporuku. Tretiranje toga kao jedne neprekinute operacije poziva nedosljedno stanje. Transakcija baze podataka može zaštititi rad u bazi podataka, ali ne može sigurno poništiti vanjski poziv za plaćanje ni već poslanu e-poruku.

Praktičan odgovor jest učiniti stanje eksplicitnim. Pohranite narudžbu s jasnim statusom kao što su pending, confirmed ili payment_failed. Zabilježite dovoljno informacija za nastavak ili usklađivanje tijeka rada. Zatim svaki prijelaz učinite namjernim umjesto da se nadate kako će dug zahtjev biti savršeno dovršen.

Ponovni pokušaji trebaju granice, a ne optimizam

Ponovni pokušaji korisni su kada je kvar privremen: mrežno prekoračenje vremena, kratko preopterećenje ili ponovno pokretanje usluge. Štetni su kada jednu sporu ovisnost pretvore u poplavu dupliciranog rada.

Pravila ponovnih pokušaja trebala bi odgovoriti na četiri pitanja:

  • Koji su kvarovi vjerojatno prolazni?
  • Koliko je pokušaja prihvatljivo prije nego što se rad izloži na pregled?
  • Koliko bi svaki pokušaj trebao čekati?
  • Može li ponavljanje operacije stvoriti duplicirani učinak?

Koristite ograničene ponovne pokušaje s rastućim odgodama i imajte na umu rok zahtjeva. Ponoviti pokušaj ovisnosti tri puta nije korisno ako svaki pokušaj može potrošiti cijelo vremensko ograničenje web zahtjeva. Pozadinski radnici često su bolje mjesto za spor, ponovno pokušiv rad nego sinkroni HTTP zahtjev.

Idempotentnost je pratitelj ponovnih pokušaja. Ako klijent dvaput pošalje isti zahtjev za narudžbu zato što nije primio prvi odgovor, sustav bi trebao stvoriti jednu narudžbu, a ne dvije. Klijentski generiran ključ idempotentnosti može se pohraniti uz operaciju i zaštititi jedinstvenim ograničenjem baze podataka. Aplikacija tada može vratiti izvorni rezultat kada se isti ključ ponovno pojavi.

$order = DB::transaction(function () use ($request) {
    return Order::firstOrCreate(
        ['idempotency_key' => $request->header('Idempotency-Key')],
        ['status' => 'pending', 'total_cents' => $request->integer('total_cents')]
    );
});

Točan API okvira razlikovat će se, ali arhitektonska poanta ne: ispravnost mora preživjeti ponovljenu isporuku.

Uskladite istinu baze podataka i vanjske učinke

Jedan čest obrazac kvara jest zapisivanje zapisa u bazu podataka, a zatim objavljivanje poruke. Ako se proces sruši između tih koraka, baza podataka kaže da se promjena dogodila, ali nizvodni sustavi nikada ne saznaju za nju.

Obrazac izlaznog spremnika rješava to bez pretvaranja da su distribuirane transakcije jednostavne. U istoj transakciji baze podataka koja mijenja poslovni zapis napišite redak izlaznog spremnika koji opisuje događaj. Zaseban radnik čita neobjavljene retke izlaznog spremnika, šalje ih posredniku ili vanjskoj usluzi i označava ih isporučenima tek nakon uspješne predaje.

Time se problem mijenja iz “mogu li se dva sustava potvrditi u točno istom trenutku?” u “možemo li pouzdano ponovno pokušati s trajnim zapisom?” Drugi je problem i dalje stvaran, ali je rješiv. Potrošači također moraju tolerirati duplicirane događaje jer radnik može uspješno objaviti događaj i srušiti se prije bilježenja tog uspjeha.

Osmislite ponašanje pri preopterećenju prije nego što ga promet nametne

Performanse nisu samo pitanje brzog izvršavanja uspješnih zahtjeva. One se odnose i na izbjegavanje kolapsa kada se ovisnost uspori. Preopterećena PHP aplikacija može ostati bez kapaciteta radnika dok čeka nizvodne pozive; tada čak i lagane provjere stanja i radnje oporavka postaju teške.

Namjerno postavite vremenska ograničenja za veze s bazom podataka, HTTP klijente, redove i pozive predmemorije. Nedostajuće vremensko ograničenje često je neograničeno čekanje prikriveno kao zadana vrijednost. Uskladite spremnike veza i broj PHP radnika s kapacitetom baze podataka i ovisnih usluga. Veća konkurentnost aplikacije ne znači automatski veći protok.

Redovi su vrijedni ventili za smanjenje pritiska, ali samo kada imaju ograničenja i operativnu vidljivost. Definirajte što bi se trebalo dogoditi kada red raste: odbiti nebitan rad, odgoditi ga, dati mu prioritet ili upozoriti operatera. Red koji zauvijek prihvaća rad samo premješta prekid rada iz API-ja u nevidljivi zaostatak.

Učinite degradaciju odlukom o proizvodu

Ne zaslužuje svaka ovisnost jednak tretman. Ako preporuke nisu dostupne, stranica proizvoda može i dalje biti korisna bez njih. Ako se podaci za autorizaciju ne mogu provjeriti, nastavak može biti nesiguran. Zapišite te razlike.

Korisni degradirani načini rada su uski i iskreni: prikažite predmemorirane podatke uz odgovarajuća pravila svježine, odgodite izvješće ili prihvatite zahtjev i obradite ga asinkrono. Izbjegavajte tiho vraćanje nepotpunih podataka kao da su potpuni. Korisničko iskustvo trebalo bi odražavati stvarnu razinu pouzdanosti sustava.

Spremnici poboljšavaju pakiranje, ali sami po sebi ne i pouzdanost

Docker čini okruženja za implementaciju dosljednijima, ali ponovno pokretanje spremnika nije strategija oporavka za nedovršen rad. Spremnici se mogu zaustaviti, ponovno rasporediti ili pokrenuti uz privremeno nedostupnu bazu podataka. Stoga bi se aplikacije trebale pokretati predvidljivo, izložiti smisleno krajnju točku za provjeru stanja i obrađivati kvarove ovisnosti bez narušavanja stanja.

U razmišljanju odvojite živost od spremnosti. Proces može biti živ, a ne moći posluživati promet jer migracije nisu dovršene, konfiguracija nije valjana ili potrebna ovisnost nije dostupna. Automatizacija implementacije trebala bi usmjeravati promet samo na instance koje su spremne za svoju namijenjenu ulogu.

Migracije baze podataka zaslužuju isti oprez. Dajte prednost kompatibilnim, postupnim promjenama: dodajte stupac koji dopušta vrijednost null, implementirajte kod koji može obraditi oba oblika, sigurno popunite postojeće podatke, a zatim kasnije nametnite stroža ograničenja. Migracija koja zaključava veliku tablicu ili uklanja stupac koji stariji spremnici još koriste može rutinsko izdanje pretvoriti u prekid rada.

Promatranje je dio arhitekture

Kada dođe do kvara, prvo pitanje rijetko je “je li nešto zakazalo?” Ono glasi “koji su zahtjev, ovisnost, implementacija ili prijelaz stanja to uzrokovali?” Strukturirani zapisi, ID-ovi zahtjeva, izvještavanje o pogreškama i mali skup metrika razine usluge čine to pitanje odgovorivim.

Zapisujte događaje s kontekstom koji pomaže dijagnostici bez izlaganja tajni ili osobnih podataka. Pratite stopu pogrešaka, latenciju, starost reda i kvarove ovisnosti. Upozoravajte na simptome koje korisnici osjećaju, a ne na svaki bučni interni događaj. Cilj nije nadzorna ploča puna grafikona; cilj je sustav koji se može objasniti pod pritiskom.

Gradite za oporavak, a zatim ga uvježbavajte

Arhitektura svjesna kvarova nije pesimizam. Ona je poštovanje prema stvarnosti i prema ljudima koji će upravljati softverom. Učinite važne operacije idempotentnima, ustrajte u prijelazima stanja, ograničite ponovne pokušaje, izolirajte spore ovisnosti, implementirajte kompatibilne promjene i osigurajte da kvarovi ostavljaju koristan trag.

Test koji se pamti jednostavan je: kada se sretan tijek prekine na pola puta, može li se sustav oporaviti bez nagađanja? Ako je odgovor da, arhitektura radi više od same obrade zahtjeva. Ona štiti poslovne odluke skrivene u njima.

Portret autora bloga

Mihajlo

Ja sam Mihajlo — programer vođen znatiželjom, disciplinom i stalnom željom da stvorim nešto smisleno. Dijelim uvide, tutorijale i besplatne usluge kako bih pomogao drugima da pojednostave svoj rad i rastu u svijetu softvera i umjetne inteligencije koji se neprestano razvija.