Prestanite nagađati o performansama API-ja: oblikujte za predvidljivost
Većina problema s performansama API-ja počinje pričom koja zvuči razumno: krajnja točka je vjerojatno spora jer je baza podataka zauzeta, spremniku treba više memorije ili je promet sigurno naglo porastao. Ponekad je ta priča istinita. Češće je to pretpostavka donesena prije nego što je itko definirao što znači „brzo” ili gdje se vrijeme zapravo troši.
Predvidljive performanse bolji su inženjerski cilj od povremene brzine. Odgovor koji se dosljedno dovršava unutar razumljivog proračuna lakše je održavati, skalirati, testirati i objasniti nego odgovor koji je obično brz, ali se povremeno uruši pod uobičajenim opterećenjem. Rad počinje u arhitekturi, mnogo prije nego što nadzorna ploča pocrveni.
Pretvorite „brzo” u proračun vremena odgovora
API zahtjev lanac je rada: usmjeravanje, autentikacija, validacija, logika aplikacije, pristup bazi podataka, serijalizacija i mrežna isporuka. Ako krajnja točka ima ciljanu latenciju, svaka značajna faza treba dobiti dio tog cilja.
Na primjer, krajnja točka s ciljem usluge od 300 ms može rezervirati vrijeme za autentikaciju i validaciju, dopustiti ograničenu količinu vremena za rad baze podataka te ostaviti rezervu za serijalizaciju i uobičajene varijacije infrastrukture. Točne vrijednosti ovise o sustavu, ali samo određivanje proračuna mijenja razgovore o dizajnu. „Možemo li dodati još jedan upit?” postaje „Stane li ovo unutar dijela proračuna zahtjeva predviđenog za bazu podataka?”
Pri procjeni rezultata koristite percentile umjesto prosjeka. Prosjek može izgledati zdravo dok je manjina zahtjeva bolno spora. Najsporiji praktični dio uobičajenih zahtjeva obično je mjesto na kojem postaju vidljivi sukobljavanje zaključavanja, promašaji predmemorije, iscrpljeni skupovi veza i preveliki tereti.
Jasno definirajte skupe putanje
Performanse postaju nepredvidljive kada je trošak skriven iza praktičnih apstrakcija. ORM odnos kojem se pristupa unutar petlje, serijalizator koji lijeno dohvaća povezane podatke ili pomoćna funkcija koja poziva vanjsku uslugu mogu jednostavnu krajnju točku pretvoriti u neograničenu količinu rada.
Razmotrite PHP krajnju točku koja vraća narudžbe i imena njihovih kupaca. Naizgled jasna verzija može izdati jedan upit za narudžbe, a zatim jedan upit za kupca po narudžbi:
$orders = $orderRepository->recent();
foreach ($orders as $order) {
$result[] = [
'id' => $order->id(),
'customer' => $order->customer()->name(),
];
}
Ako customer() lijeno učitava podatke, trošak raste s brojem narudžbi. Poboljšanje nije samo „optimizirati upit”. Unaprijed definirajte oblik podataka: dohvatite potrebne narudžbe i polja kupaca ograničenim upitom ili namjernom skupnom obradom, a zatim serijalizirajte već učitane podatke. Krajnja točka trebala bi obaviti približno istu količinu rada za usporedive veličine zahtjeva.
Paginacija zahtijeva istu disciplinu. Ograničenje bez stabilnog poretka nije dizajn performansi. Paginacija temeljena na pomaku može postajati sve skuplja na velikim skupovima podataka koji se često mijenjaju. Za tokove poredane prema jedinstvenoj, indeksiranoj vrijednosti, paginacija pokazivačem često pruža stabilniju putanju jer baza podataka može nastaviti s poznate pozicije umjesto da opetovano preskače ranije retke.
Dizajnirajte upite baze podataka prema obrascima pristupa
Indeks je koristan kada podržava stvarni obrazac filtriranja, spajanja i sortiranja. Reaktivno dodavanje indeksa pojedinačnim stupcima može povećati trošak pisanja bez poboljšanja upita koji je važan. Počnite s oblikom upita krajnje točke, pregledajte njegov plan izvršavanja i potvrdite da odabrani indeks sužava i sortira podatke na koristan način.
Transakcije neka budu kratke. Transakcija koja obavlja udaljene pozive, obradu datoteka ili dugotrajan rad aplikacije dok drži zaključavanja baze podataka čini latenciju ovisnom o nepovezanim sustavima. Najprije validirajte ulaze, obavite najmanje potrebno transakcijsko ažuriranje i premjestite nebitan naknadni rad izvan transakcije.
Odvojite rad zahtjeva od pozadinskog rada
Neki rad pripada zahtjevu jer je pozivatelju potreban njegov rezultat prije nastavka. Drugi rad samo se mora pouzdano dogoditi nakon što se prihvati promjena stanja: slanje e-pošte, generiranje izvoza, osvježavanje podataka pretraživanja ili obavještavanje drugog sustava. Jednako tretiranje obiju kategorija čini API taocem najsporije ovisnosti.
Pouzdan obrazac jest potvrditi primarnu promjenu stanja i zabilježiti događaj ili posao kao dio iste transakcije baze podataka. Radnik tada može obraditi posao uz ponovne pokušaje i mogućnost praćenja. Time se izbjegava i suprotan način neuspjeha: uspješno ažuriranje baze podataka, ali gubitak obavijesti zato što je proces odmah nakon toga otkazao.
Pozadinska obrada ne uklanja složenost; premješta je na mjesto gdje se njome može upravljati. Radnici trebaju idempotentne obrađivače, ograničene politike ponovnih pokušaja i definiran ishod nakon ponovljenog neuspjeha. Ponovni pokušaj bez idempotentnosti može dvaput naplatiti karticu, poslati duplicirane poruke ili opetovano primijeniti istu prilagodbu zaliha.
- Koristite trajni ključ idempotentnosti za operacije koje se mogu ponoviti.
- Pokušajte ponovno prolazne neuspjehe uz ograničenja i sve dulja odgađanja.
- Zabilježite dovoljno konteksta za istraživanje neuspjelih poslova bez njihovog naslijepog ponavljanja.
- Odlučite zahtijeva li trajno neuspjeli posao ručnu provjeru, kompenzaciju ili vidljiv status za korisnike.
Postavite ograničenja na svakoj granici
Predvidljivost proizlazi iz odbijanja neograničenog rada. Postavite maksimalne veličine stranica, ograničenja tijela zahtjeva, vremenska ograničenja upita, ograničenja skupova veza i jasne rokove za odlazne pozive. To nisu proizvoljna ograničenja; ona štite ostatak sustava od jednog skupog zahtjeva.
Odlazni HTTP poziv treba imati izričita vremenska ograničenja za povezivanje i ukupno trajanje. Upit baze podataka ne bi trebao zauvijek čekati zaključavanje ili preopterećen poslužitelj. Potrošač reda treba kontrolirati koliko posla preuzima odjednom. Bez ograničenja pritisak se širi: spore ovisnosti zauzimaju radnike, radnici drže veze, veze stavljaju zahtjeve u red, a lokalni problem postaje prekid rada cijele aplikacije.
Docker olakšava pakiranje API-ja, ali spremnici ne uklanjaju ograničenja resursa. Osigurajte da aplikacija ima promišljen procesni model, dovoljno kapaciteta radnika za očekivanu istodobnost i siguran način da tijekom implementacije prestane prihvaćati novi rad. Spremnik koji se prekine tijekom obrade zahtjeva ili posla trebao bi ili završiti unutar predviđenog razdoblja gašenja ili ostaviti rad u stanju iz kojeg se može oporaviti.
Mjerite putanju koju ste dizajnirali
Metrike su najkorisnije kada odgovaraju na arhitektonska pitanja. Pratite latenciju zahtjeva prema ruti i klasi statusa, stope pogrešaka, trajanje upita baze podataka, zasićenost skupa, dubinu reda, starost posla i latenciju odlaznih ovisnosti. Dodajte strukturirane zapisnike s identifikatorima zahtjeva ili korelacije kako bi se spor zahtjev mogao pratiti preko granica aplikacije i radnika.
Nemojte neselektivno instrumentirati sve. Instrumentirajte granice na kojima rad prelazi iz ruke u ruku: HTTP ulaz, pozive baze podataka, operacije predmemorije, redove i udaljene usluge. Zatim testirajte poznate skupe putanje s realističnim veličinama tereta i istodobnošću. Cilj nije izraditi impresivno izvješće o testiranju opterećenja; cilj je saznati gdje sustav prestaje poštovati svoje proračune.
Predvidljivost je značajka
Brza krajnja točka pruža zadovoljstvo. Predvidljiva krajnja točka ulijeva pouzdanje. Omogućuje proizvodnim timovima da postave iskrena očekivanja, operaterima da brzo prepoznaju neuobičajeno ponašanje i programerima da mijenjaju kôd bez oslanjanja na sreću.
Praktična je navika jednostavna: definirajte proračun, ograničite rad, izdvojite spore zadatke, zaštitite svaku ovisnost ograničenjima i izmjerite rezultat. Kada ti izbori postanu uobičajen dio dizajnerskog rada, performanse prestaju biti kasna igra pogađanja i postaju jedno od najjasnijih obećanja sustava.