net·devs
← Wszystkie artykuły
File insight
2 min czytaniaAutor: Kyrylo Osadchukfintechengineering

Idempotentność to jedyna rzecz stojąca między Tobą a zduplikowanym zwrotem

Systemy płatności są ponawiane. Sieci zawodzą w trakcie wywołania. Telefony tracą zasięg. Różnica między czystą księgą a koszmarem obsługi klienta leży w tym, czy Twoje endpointy są naprawdę idempotentne.

Pierwszy raz zobaczyliśmy zduplikowany zwrot w produkcji niemal dziesięć lat temu. Klient mobilny wysłał żądanie zwrotu. Serwer je przetworzył. Odpowiedź 200 nigdy nie dotarła do telefonu — użytkownik wszedł do tunelu.

Telefon ponowił żądanie, jak telefony to robią. Serwer przetworzył je ponownie. Klient dostał dwa zwroty. Sprzedawca zauważył to trzy miesiące później. Rozmowa była nieprzyjemna.

Naprawa zajęła dziesięć minut. Zapobieganie wymaga namysłu za każdym razem, gdy projektowany jest endpoint zapisu. Większość zespołów buduje prawdziwą warstwę idempotentności po pierwszym oparzeniu. Niektóre opalają się dwukrotnie.

Co idempotentny naprawdę oznacza

Endpoint jest idempotentny, gdy wywołanie go dwa razy z tym samym wejściem daje taki sam efekt jak wywołanie go raz. Nie tę samą odpowiedź — ten sam efekt na świat. To rozróżnienie ma znaczenie.

Endpoint GET /balance jest idempotentny przez przypadek, bo odczyt niczego nie zmienia. Endpoint POST /refund jest idempotentny tylko wtedy, gdy tak go zaprojektujesz. Nie ma funkcji językowej, która robi to za Ciebie.

Standardowym wzorcem jest klucz idempotentności: klient generuje unikalny identyfikator na operację logiczną i wysyła go jako nagłówek. Serwer rejestruje klucz za pierwszym razem, gdy go widzi, przechowuje wynik i przy każdym ponowieniu z tym samym kluczem zwraca przechowany wynik zamiast ponownie wykonywać operację.

Gdzie implementacja staje się subtelna

Wzorzec brzmi prosto. Trzy miejsca, gdzie zazwyczaj idzie nie tak.

Przechowywanie klucza po pracy, nie przed. Naiwna implementacja zapisuje operację, a następnie przechowuje klucz idempotentności. Jeśli serwer crasha między tymi dwoma zapisami, kolejne ponowienie wykona operację ponownie. Przechowuj klucz najpierw, w tej samej transakcji co operacja, albo użyj bazy danych gwarantującej atomowość obu zapisów.

Przechowywanie odpowiedzi bez stanu operacji. Ponowienie musi zwrócić tę samą odpowiedź, ale musi też wiedzieć, czy oryginalna operacja faktycznie się zakończyła, czy nadal jest w toku. Przechowywanie tylko odpowiedzi traci stan in-flight, a wolny oryginał plus szybkie ponowienie mogą wyścigować. Rekord idempotentności potrzebuje trzech stanów: in-progress, complete-with-response, failed.

Brak ograniczenia czasu życia klucza. Klucze idempotentności żyją wiecznie w naiwnym projekcie, a ta tabela rośnie bez ograniczeń. Ogranicz je — okno 24 godzin jest hojne dla większości przypadków płatności — i udokumentuj ograniczenie, żeby klienci wiedzieli, że muszą używać świeżych kluczy dla ponowień przekraczających to okno.

Za co odpowiadają klienci

Serwer może zrobić wszystko dobrze i nadal otrzymywać zduplikowane zapisy, jeśli klient powtarza z nowym kluczem przy każdej próbie. Kontrakt musi sięgać po stronie klienta.

Klient generuje UUID na operację logiczną, nie na próbę HTTP. Ten sam logiczny zwrot = ten sam klucz, niezależnie od liczby ponowień sieciowych potrzebnych do dostarczenia. Przechowuj ten klucz lokalnie do czasu, gdy operacja się zakończy lub przekroczy ograniczenie.

To jest część, która zaskakująco często jest pomijana w mobilnych SDK-ach, bo warstwa sieciowa jest traktowana jako coś do przezroczystego ponawiania. Klucze idempotentności wychodzą z warstwy sieciowej do warstwy aplikacji. Nie są zagadnieniem transportu — są zagadnieniem domeny.

Kiedy zacząć

Teraz. Przed pierwszym zduplikowanym zwrotem. Widzieliśmy zespoły mówiące „dodamy to przed startem" i odsuwające deadline z powodów startowych; praca nad idempotentością się ześlizguje, pierwszy incydent uczy ich, dlaczego była ważna, a sprzątanie jest nieporządne, bo w produkcji są teraz rekordy stworzone bez kluczy.

To jeden z tańszych nawyków do rozpoczęcia od razu i jeden z droższych do dołożenia później.

Chcemy poznać Twoje myśli.

nasz CTO Kyrylo Osadchuk odpowie w ciągu 24 godzin. Bez lejka sprzedażowego.