12 maj 2025·6 min czytania

Idempotentne endpointy w Go: klucze, tabele deduplikacji, ponawianie żądań

Projektuj idempotentne endpointy w Go przy użyciu kluczy idempotencji, tabel deduplikacji i obsługiwaczy odpornych na ponowienia dla płatności, importów i webhooków.

Idempotentne endpointy w Go: klucze, tabele deduplikacji, ponawianie żądań

Dlaczego ponowienia tworzą duplikaty (i dlaczego idempotencja ma znaczenie)

Ponowienia zdarzają się nawet wtedy, gdy nic „nie jest zepsute”. Klient ma timeout, podczas gdy serwer nadal przetwarza żądanie. Połączenie mobilne spada i aplikacja próbuje ponownie. Runner zadań dostaje 502 i automatycznie wysyła to samo żądanie ponownie. Przy co najmniej jednokrotnym dostarczeniu (powszechne przy kolejkach i webhookach) duplikaty są normalne.

Dlatego idempotencja ma znaczenie: powtarzające się żądania powinny dawać ten sam końcowy rezultat, co pojedyncze żądanie.

Kilka terminów łatwo pomylić:

  • Bezpieczne (safe): wywołanie nie zmienia stanu (jak odczyt).
  • Idempotentne: wywołanie wielokrotnie ma ten sam efekt co wywołanie raz.
  • At-least-once: nadawca będzie ponawiał aż „przyklejenie się” nastąpi, więc odbiorca musi obsługiwać duplikaty.

Bez idempotencji ponowienia mogą wyrządzić realne szkody. Endpoint płatności może obciążyć kartę dwa razy, jeśli pierwsze obciążenie się powiodło, ale odpowiedź nie dotarła do klienta. Endpoint importu może stworzyć duplikaty wierszy, gdy worker ponawia po timeoutie. Handler webhooków może przetworzyć to samo zdarzenie dwukrotnie i wysłać dwa e-maile.

Kluczowy punkt: idempotencja to kontrakt API, a nie prywatny szczegół implementacyjny. Klienci muszą wiedzieć, co mogą ponawiać, jaki klucz wysłać i jaką odpowiedź dostaną, gdy wykryjesz duplikat. Jeśli zmieniasz zachowanie po cichu, psujesz logikę ponawiania i tworzysz nowe tryby awarii.

Idempotencja nie zastępuje też monitoringu i rekonsyliacji. Śledź wskaźniki duplikatów, loguj decyzje o „replay” i okresowo porównuj systemy zewnętrzne (np. dostawcę płatności) z bazą danych.

Wybierz zakres idempotencji i zasady dla każdego endpointu

Zanim dodasz tabele lub middleware, zdecyduj, co oznacza „to samo żądanie” i co serwer obiecuje zrobić, gdy klient ponowi.

Większość problemów pojawia się przy POST, bo często tworzy coś lub wyzwala efekt uboczny (obciążyć kartę, wysłać wiadomość, uruchomić import). PATCH też może wymagać idempotencji, jeśli wyzwala efekty uboczne, a nie tylko prostą aktualizację pola. GET nie powinien zmieniać stanu.

Zdefiniuj zakres: gdzie klucz jest unikalny

Wybierz zakres zgodny z regułami biznesowymi. Zbyt szeroki blokuje prawidłową pracę. Zbyt wąski dopuszcza duplikaty.

Typowe zakresy:

  • Per endpoint + klient (customer)
  • Per endpoint + obiekt zewnętrzny (np. invoice_id lub order_id)
  • Per endpoint + tenant (dla systemów multi-tenant)
  • Per endpoint + metoda płatności + kwota (tylko jeśli reguły produktu na to pozwalają)

Przykład: dla endpointu „Create payment” ustaw klucz unikalny per klient. Dla „Ingest webhook event” zakresuj go do ID zdarzenia dostawcy płatności (unikalność globalna od dostawcy).

Zdecyduj, co odtwarzać przy duplikatach

Gdy przychodzi duplikat, zwróć ten sam wynik, co pierwsze udane wywołanie. W praktyce oznacza to odtworzenie tego samego kodu statusu HTTP i tego samego ciała odpowiedzi (albo przynajmniej tego samego ID zasobu i stanu).

Klienci polegają na tym. Jeśli pierwsza próba się powiodła, ale sieć upadła, ponowienie nie powinno stworzyć drugiego obciążenia ani drugiego zadania importu.

Wybierz okres przechowywania

Klucze powinny wygasać. Trzymaj je wystarczająco długo, by pokryć realistyczne ponowienia i opóźnione zadania.

  • Płatności: zwykle 24–72 godziny.
  • Importy: tydzień może być rozsądny, jeśli użytkownicy mogą ponawiać później.
  • Webhooki: dopasuj do polityki ponowień dostawcy.

Zdefiniuj „to samo żądanie”: eksplicytny klucz vs hash ciała

Eksplicytny klucz idempotencji (header lub pole) jest zwykle najczystszym rozwiązaniem.

Hash ciała może pomóc jako zabezpieczenie, ale łatwo się łamie przy nieszkodliwych zmianach (kolejność pól, białe znaki, znaczniki czasu). Jeśli używasz hashowania, normalizuj wejście i bądź restrykcyjny co do pól, które uwzględniasz.

Klucze idempotencji: jak działają w praktyce

Klucz idempotencji to prosty kontrakt między klientem a serwerem: „Jeśli zobaczysz ten klucz ponownie, traktuj to jako to samo żądanie.” To jedno z najpraktyczniejszych narzędzi dla API bezpiecznych przy ponowieniach.

Klucz może pochodzić od obu stron, jednak dla większości API powinien być generowany po stronie klienta. Klient wie, kiedy ponawia tę samą akcję, więc może użyć tego samego klucza w kolejnych próbach. Klucze generowane po stronie serwera pomagają, gdy najpierw tworzysz zasób „szkicowy” (np. zadanie importu), a potem pozwalasz klientom ponawiać, odwołując się do ID zadania, ale nie pomagają przy pierwszym żądaniu.

Używaj losowego, nieodgadnionego ciągu. Celuj w co najmniej 128 bitów losowości (np. 32 znaki hex lub UUID). Nie buduj kluczy z timestampów ani ID użytkownika.

Na serwerze zapisz klucz z wystarczającym kontekstem, aby wykryć nadużycia i odtworzyć oryginalny wynik:

  • Kto wykonał wywołanie (account lub user ID)
  • Na który endpoint/operację się odnosi
  • Hash istotnych pól żądania
  • Aktualny status (w toku, zakończone, niepowodzenie)
  • Odpowiedź do odtworzenia (kod statusu i ciało)

Klucz powinien być zakresowany, zwykle per użytkownik (lub token API) plus endpoint. Jeśli ten sam klucz zostanie użyty z innym payloadem, odrzuć z jasnym błędem. To zapobiega przypadkowym kolizjom, gdy błędny klient wysyła nową kwotę płatności używając starego klucza.

Przy odtwarzaniu zwróć ten sam wynik, co przy pierwszym udanym wywołaniu. To oznacza ten sam kod HTTP i to samo ciało odpowiedzi, a nie świeże odczytanie, które mogło się zmienić.

Tabele deduplikacji w PostgreSQL: prosty, niezawodny wzorzec

Dedykowana tabela deduplikacji to jeden z najprostszych sposobów implementacji idempotencji. Pierwsze żądanie tworzy wiersz z kluczem idempotencji. Każde ponowienie odczytuje ten sam wiersz i zwraca zapisany wynik.

Co przechowywać

Utrzymuj tabelę małą i skupioną. Typowa struktura:

  • key: klucz idempotencji (text)
  • owner: do kogo należy klucz (user_id, account_id lub ID klienta API)
  • request_hash: hash istotnych pól żądania
  • response: ostateczny payload odpowiedzi (często JSON) lub wskaźnik do przechowywanego wyniku
  • created_at: kiedy klucz został pierwszy raz zauważony

Unikalny constraint to sedno wzorca. Wymuś unikalność na (owner, key), żeby jeden klient nie mógł stworzyć duplikatu i żeby dwóch różnych klientów nie kolidowało.

Przechowuj też request_hash, by wykrywać nadużycia klucza. Jeśli przychodzi ponowienie z tym samym kluczem, ale innym hashem, zwróć błąd zamiast mieszać dwie różne operacje.

Retencja i indeksowanie

Wiersze deduplikacji nie powinny żyć wiecznie. Trzymaj je tak długo, by pokryć realne okna ponowień, potem sprzątaj.

Dla szybkości pod obciążeniem:

  • Unikalny indeks na (owner, key) dla szybkiego insertu/lookup
  • Opcjonalny indeks na created_at dla taniego sprzątania

Jeśli odpowiedź jest duża, przechowuj wskaźnik (np. result ID) i trzymaj pełny payload gdzie indziej. To zmniejsza rozmiar tabeli przy zachowaniu spójnego zachowania przy ponowieniach.

Krok po kroku: przepływ handlera odpornego na ponawianie w Go

Uczyń importy przyjaznymi dla ponowień
Twórz restartowalne zadania importu, które zwracają to samo ID zadania przy ponowieniach.
Buduj teraz

Handler odporny na ponawianie potrzebuje dwóch rzeczy: stabilnego sposobu identyfikacji „tego samego żądania” oraz trwałego miejsca do przechowania pierwszego wyniku, by móc go odtworzyć.

Praktyczny przepływ dla płatności, importów i przyjmowania webhooków:

  1. Zweryfikuj żądanie, potem wyprowadź trzy wartości: klucz idempotencji (z nagłówka lub pola klienta), właściciela (tenant lub user ID) i hash żądania (hash istotnych pól).

  2. Rozpocznij transakcję bazy danych i spróbuj utworzyć rekord deduplikacji. Uczyń go unikalnym na (owner, key). Zapisz request_hash, status (started, completed) i miejsca na odpowiedź.

  3. Jeśli insert konfliktuje, załaduj istniejący wiersz. Jeśli jest zakończony, zwróć zapisany rezultat. Jeśli jest w toku, albo chwilę poczekaj (proste polling) albo zwróć 409/202, żeby klient spróbował później.

  4. Tylko gdy uda Ci się „zająć” wiersz deduplikacji, wykonaj logikę biznesową raz. Zapisuj efekty uboczne w tej samej transakcji, jeśli to możliwe. Utrwal wynik biznesowy oraz odpowiedź HTTP (kod i ciało).

  5. Commituj i loguj z kluczem idempotencji oraz właścicielem, by wsparcie mogło śledzić duplikaty.

Minimalny wzorzec tabeli:

create table idempotency_keys (
  owner_id text not null,
  idem_key text not null,
  request_hash text not null,
  status text not null,
  response_code int,
  response_body jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  primary key (owner_id, idem_key)
);

Przykład: endpoint „Create payout” timeoutuje po pobraniu opłaty. Klient ponawia z tym samym kluczem. Twój handler trafia na konflikt, widzi zakończony rekord i zwraca oryginalne ID payoutu bez ponownego obciążenia.

Płatności: obciąż dokładnie raz, nawet przy timeoutach

Płatności to obszar, gdzie idempotencja przestaje być opcjonalna. Sieci zawodzą, aplikacje mobilne ponawiają, a bramki czasami timeoutują po tym, jak już utworzyły charge.

Praktyczna zasada: klucz idempotencji chroni tworzenie obciążenia, a ID dostawcy płatności (provider ID) staje się źródłem prawdy po tym kroku. Gdy zapiszesz provider ID, nie twórz nowego obciążenia dla tego samego żądania.

Wzorzec radzący sobie z ponowieniami i niepewnością bramki:

  • Odczytaj i zwaliduj klucz idempotencji.
  • W transakcji bazy utwórz lub pobierz wiersz płatności kluczowany przez (merchant_id, idempotency_key). Jeśli ma już provider_id, zwróć zapisany rezultat.
  • Jeśli nie ma provider_id, wywołaj bramkę, by stworzyć PaymentIntent/Charge.
  • Jeśli bramka zwróci sukces, zapisz provider_id i oznacz płatność jako „succeeded” (lub „requires_action”).
  • Jeśli bramka timeoutuje lub zwraca nieznany rezultat, zapisz stan „pending” i zwróć konsekwentną odpowiedź mówiącą klientowi, że bezpiecznie może ponowić.

Kluczowe jest traktowanie timeoutów: nie zakładaj porażki. Oznacz płatność jako pending, potem potwierdź zapytując bramkę później (albo przez webhook) używając provider ID, gdy go masz.

Odpowiedzi z błędami powinny być przewidywalne. Klienci budują logikę ponawiania wokół tego, co zwracasz, więc trzymaj kształt kodów statusu i błędów stabilny.

Importy i endpointy wsadowe: deduplikacja bez utraty postępu

Zachowaj kontrolę dzięki kodowi źródłowemu
Zyskaj rzeczywiste źródło kodu, gdy potrzebujesz przeglądu lub samodzielnego hostowania usług.
Eksportuj kod

Importy to miejsce, gdzie duplikaty najbardziej bolą. Użytkownik wgrywa CSV, Twój serwer timeoutuje przy 95% i użytkownik ponawia. Bez planu albo stworzysz duplikaty, albo zmusisz użytkownika do zaczynania od początku.

Dla pracy wsadowej myśl o dwóch warstwach: zadanie importu i elementy w jego obrębie. Idempotencja na poziomie zadania zatrzymuje tworzenie wielokrotnych zadań. Idempotencja na poziomie elementu zatrzymuje ponowne zastosowanie tego samego wiersza.

Wzorzec na poziomie zadania: wymagaj klucza idempotencji dla żądania importu (lub wyprowadź go z stabilnego hasha żądania + user ID). Zapisz go z rekordem import_job i zwracaj to samo ID zadania przy ponowieniach. Handler powinien umieć powiedzieć „Widziałem to zadanie, oto jego aktualny stan” zamiast „zacznij od nowa”.

Dla deduplikacji na poziomie wiersza polegaj na naturalnym kluczu, który już istnieje w danych. Na przykład każdy wiersz może zawierać external_id z systemu źródłowego, lub stabilną kombinację jak (account_id, email). Wymuś to unikalnym ograniczeniem w PostgreSQL i używaj upsert, żeby ponowienia nie tworzyły duplikatów.

Zanim wdrożysz, zdecyduj, co robi replay, gdy wiersz już istnieje. Bądź explicyty: pomiń, zaktualizuj konkretne pola lub zakończ błędem. Unikaj „merge” chyba, że masz bardzo jasne reguły.

Częściowy sukces jest normalny. Zamiast zwracać jedno wielkie „ok” albo „failed”, zapisuj wyniki per wiersz powiązane z zadaniem: numer wiersza, naturalny klucz, status (created, updated, skipped, error) i komunikat błędu. Przy ponowieniu możesz bezpiecznie uruchomić ponownie, zachowując te same wyniki dla wierszy, które już się zakończyły.

Aby uczynić importy restartowalnymi, dodaj checkpointy. Przetwarzaj w stronach (np. 500 wierszy naraz), zapisuj ostatni przetworzony kursor (index wiersza lub cursor źródła) i aktualizuj go po każdym commicie strony. Jeśli proces się wywali, następna próba wznowi od ostatniego checkpointu.

Przyjmowanie webhooków: deduplikuj, waliduj, a potem przetwarzaj bezpiecznie

Spraw, by płatności były jednokrotne
Zbuduj przepływ płatności, który unika podwójnych obciążeń dzięki kluczom idempotencji i zapisanym ID dostawcy.
Skonfiguruj płatności

Nadawcy webhooków ponawiają. Wysyłają też zdarzenia poza kolejnością. Jeśli Twój handler zmienia stan przy każdej dostawie, w końcu zduplikujesz rekordy, wyślesz dwukrotnie e-maile albo podwójnie obciążysz.

Zacznij od wyboru najlepszego klucza deduplikacji. Jeśli dostawca daje unikalne ID zdarzenia, użyj go. Tylko gdy go nie ma, użyj hasha payloadu.

Bezpieczeństwo jest pierwsze: zweryfikuj sygnaturę przed zaakceptowaniem czegokolwiek. Jeśli sygnatura nie przejdzie, odrzuć żądanie i nie zapisuj rekordu deduplikacji. W przeciwnym razie atakujący mógłby „zarezerwować” ID zdarzenia i blokować prawdziwe zdarzenia później.

Bezpieczny przepływ przy ponowieniach:

  • Zweryfikuj sygnaturę i podstawowy kształt (wymagane nagłówki, event ID).
  • Wstaw event ID do tabeli deduplikacji z unikalnym ograniczeniem.
  • Jeśli insert zawiedzie z powodu duplikatu, zwróć 200 natychmiast.
  • Zapisz surowy payload (i nagłówki) gdy przydaje się do audytu i debugowania.
  • Kolejkuj przetwarzanie i zwróć 200 szybko.

Szybkie potwierdzanie ma znaczenie, bo wielu dostawców ma krótkie timouty. Wykonaj w żądaniu najważniejszą pracę: weryfikację, deduplikację, persystencję. Potem przetwarzaj asynchronicznie (worker, kolejka, job w tle). Jeśli nie możesz robić asynchronicznie, utrzymuj przetwarzanie idempotentne poprzez kluczowanie efektów ubocznych na tym samym event ID.

Dostawa poza kolejnością jest normalna. Nie zakładaj, że „created” przyjdzie przed „updated”. Preferuj upserty po zewnętrznym ID obiektu i śledź ostatni przetworzony timestamp lub wersję.

Przechowywanie surowych payloadów pomaga, gdy klient mówi „nie dostaliśmy update’u”. Możesz ponownie uruchomić przetwarzanie z zapisanego ciała po naprawieniu błędu, bez proszenia dostawcy o ponowne wysłanie.

Współbieżność: zachowaj poprawność przy równoległych żądaniach

Ponowienia robią się trudne, gdy dwa żądania z tym samym kluczem przychodzą jednocześnie. Jeśli oba handlery wykonają krok „zrób pracę” zanim któreś zapisze wynik, wciąż możesz podwójnie obciążyć, podwójnie zaimportować lub podwoić enqueue.

Najprostszy punkt koordynacji to transakcja bazy danych. Zrób pierwszy krok „zarezerwuj klucz” i niech baza zdecyduje, kto wygrywa. Typowe opcje:

  • Unikalny insert do tabeli deduplikacji (baza wymusza jednego zwycięzcę)
  • SELECT ... FOR UPDATE po utworzeniu (lub znalezieniu) wiersza deduplikacji
  • Bloki doradcze na poziomie transakcji (advisory locks) haszowane po kluczu idempotencji
  • Unikalne ograniczenia na rekordzie biznesowym jako ostateczne zabezpieczenie

Dla długotrwałej pracy unikaj trzymania locka wiersza podczas wywoływania systemów zewnętrznych lub uruchamiania importów trwających minuty. Zamiast tego przechowuj mały state machine w wierszu deduplikacji, aby inne żądania mogły szybko odpuścić.

Praktyczny zestaw stanów:

  • in_progress z started_at
  • completed z cachowaną odpowiedzią
  • failed z kodem błędu (opcjonalnie, zależnie od polityki ponowień)
  • expires_at (dla sprzątania)

Przykład: dwa instancy aplikacji dostają to samo żądanie płatności. Instancja A wstawia klucz i oznacza in_progress, potem wywołuje dostawcę. Instancja B trafia na ścieżkę konfliktu, czyta wiersz deduplikacji, widzi in_progress i zwraca szybką odpowiedź „w toku” (albo chwilę czeka i ponownie sprawdza). Gdy A skończy, aktualizuje wiersz na completed i zapisuje ciało odpowiedzi, żeby późniejsze ponowienia dostały dokładnie ten sam output.

Częste błędy, które psują idempotencję

Wdróż kompletne rozwiązanie
Wypuść backend, aplikację webową i natywne aplikacje mobilne do chmury, którą wybierzesz.
Wdróż aplikację

Większość błędów idempotencji to nie skomplikowane deadlocki, tylko „prawie poprawne” wybory, które zawodzą przy ponowieniach, timeoutach lub kiedy dwóch użytkowników robi podobne akcje.

Częstą pułapką jest traktowanie klucza idempotencji jako globalnie unikalnego. Jeśli go nie zakresujesz (po użytkowniku, koncie, endpointzie), dwóch różnych klientów może trafić na ten sam klucz i jeden dostanie wynik drugiego.

Innym problemem jest akceptowanie tego samego klucza z innym ciałem żądania. Jeśli pierwsze wywołanie dotyczyło $10, a replay $100, nie powinieneś cicho zwracać pierwszego wyniku. Przechowuj hash żądania (lub kluczowych pól), porównuj przy replay i zwracaj jasny błąd konfliktu.

Klienci też się gubią, gdy replay zwraca inny kształt odpowiedzi lub inny kod statusu. Jeśli pierwsze wywołanie zwróciło 201 z JSONem, replay powinien zwrócić to samo ciało i zgodny kod. Zmienianie zachowania replay zmusza klientów do zgadywania.

Błędy często powodujące duplikaty:

  • Poleganie wyłącznie na mapie w pamięci lub cache, potem tracenie stanu deduplikacji po restarcie.
  • Używanie klucza bez zakresu (kolizje między użytkownikami lub endpointami).
  • Nie walidowanie rozbieżności payloadu dla tego samego klucza.
  • Wykonywanie efektu ubocznego najpierw (charge, insert, publish) i zapisywanie rekordu deduplikacji później.
  • Zwracanie nowego wygenerowanego ID przy każdym ponowieniu zamiast odtwarzania oryginalnego wyniku.

Cache może przyspieszyć odczyty, ale źródłem prawdy powinna być trwała warstwa (zwykle PostgreSQL). W przeciwnym razie ponowienia po deployu mogą stworzyć duplikaty.

Planuj też sprzątanie. Jeśli będziesz trzymać każdy klucz na zawsze, tabele urosną, a indeksy zwolnią. Ustaw okno retencji na podstawie realnego zachowania ponowień, usuwaj stare wiersze i trzymaj indeks unikalny mały.

Szybka lista kontrolna i kolejne kroki

Traktuj idempotencję jako część kontraktu API. Każdy endpoint, który może być ponawiany przez klienta, kolejkę lub bramkę, potrzebuje jasnej reguły, co oznacza „to samo żądanie” i jak wygląda „ten sam rezultat”.

Lista kontrolna przed wdrożeniem:

  • Dla każdego endpointu możliwego do ponowienia, czy zakres idempotencji jest zdefiniowany (per użytkownik, konto, zamówienie, zdarzenie zewnętrzne) i zapisany?
  • Czy deduplikacja jest egzekwowana przez bazę (unikalny constraint na klucz i zakres), a nie tylko „sprawdzana w kodzie”?
  • Przy replayu, czy zwracasz ten sam kod statusu i ciało odpowiedzi (lub udokumentowany stabilny podzbiór), a nie świeży obiekt lub nowy timestamp?
  • Dla płatności, czy bezpiecznie obsługujesz nieznane wyniki (timeout po submit, bramka zwraca „processing”) bez podwójnego obciążenia?
  • Czy logi i metryki pokazują wyraźnie, kiedy żądanie było po raz pierwszy widziane vs. replayowane?

Jeśli którykolwiek punkt to „może”, popraw to teraz. Większość awarii pojawia się pod obciążeniem: równoległe ponowienia, wolne sieci i częściowe outages.

Jeśli budujesz narzędzia wewnętrzne lub aplikacje klienta na AppMaster (appmaster.io), warto zaprojektować klucze idempotencji i tabelę deduplikacji PostgreSQL wcześnie. W ten sposób, nawet jeśli platforma regeneruje backend w Go gdy wymagania się zmieniają, zachowanie przy ponowieniach pozostanie spójne.

FAQ

Dlaczego ponowienia powodują podwójne obciążenia lub podwójne rekordy, nawet gdy moje API jest poprawne?

Powtarzania są normalne, ponieważ sieci i klienci zawodzą w codzienny sposób. Żądanie może zostać wykonane po stronie serwera, ale odpowiedź nigdy nie dotrze do klienta — klient spróbuje ponownie i bez mechanizmu rozpoznawania oraz odtwarzania oryginalnego wyniku wykonasz tę samą operację dwukrotnie.

Czego powinienem użyć jako klucz idempotencji i kto powinien go generować?

Wysyłaj ten sam klucz przy każdym ponowieniu tej samej akcji. Generuje go klient jako losowy, nieprzewidywalny ciąg (np. UUID) i nie używaj go ponownie do innej operacji.

Jak powinienem zakresować klucze idempotencji, żeby nie kolidowały między użytkownikami lub tenantami?

Zakresuj klucz zgodnie z regułą biznesową, zwykle per endpoint plus tożsamość wykonawcy: użytkownik, konto, tenant lub token API. Zapobiega to kolizjom, gdy dwóch różnych klientów użyje takich samych kluczy.

Co moje API powinno zwrócić, gdy otrzyma duplikat żądania z tym samym kluczem?

Zwróć ten sam wynik, co przy pierwszym udanym wywołaniu. W praktyce odtwórz ten sam kod statusu HTTP i ciało odpowiedzi, lub przynajmniej to samo ID zasobu i jego stan, aby klienci mogli bezpiecznie ponawiać żądania.

Co jeśli klient przez pomyłkę użyje tego samego klucza idempotencji z innym ciałem żądania?

Odrzuć takie żądanie jasnym błędem konfliktu zamiast zgadywać. Przechowuj i porównuj hash istotnych pól żądania; jeśli klucz zgadza się, a payload nie, zakończ z błędem, by nie mieszać dwóch różnych operacji pod jednym kluczem.

Jak długo powinienem przechowywać klucze idempotencji w bazie danych?

Przechowuj klucze wystarczająco długo, by pokryć realistyczne ponowienia, a potem je usuwaj. Typowe wartości: 24–72 godziny dla płatności, tydzień dla importów; dla webhooków dopasuj retencję do polityki ponowień nadawcy.

Jaki jest najprostszy schemat w PostgreSQL dla idempotencji?

Dedykowana tabela deduplikacji działa dobrze, bo baza egzekwuje unikalny constraint i przetrwa restarty. Przechowaj zakres właściciela, klucz, hash żądania, status i odpowiedź do odtworzenia, a (owner, key) ustaw jako unikalne, żeby tylko jedno żądanie „wygrało”.

Jak obsłużyć dwa identyczne żądania przychodzące w tym samym czasie?

Zgarnij klucz w transakcji bazy danych, a potem wykonuj efekt uboczny tylko jeśli udało Ci się go zarezerwować. Jeśli równoległe żądanie trafi na konflikt, powinno zobaczyć in_progress lub completed i odpowiednio poczekać albo zwrócić odpowiedź retry/wait, zamiast powtarzać logikę.

Jak zapobiec podwójnemu obciążeniu, gdy bramka płatności timeoutuje?

Traktuj timeouty jako „nieznane”, a nie jako porażkę. Zapisz stan pending i, jeśli masz ID dostawcy, użyj go jako źródła prawdy, aby ponowienia zwracały ten sam wynik płatności zamiast tworzyć nową transakcję.

Jak sprawić, by importy były odporne na ponawianie bez zmuszania użytkowników do zaczynania od początku?

Deduplikuj na dwóch poziomach: poziom zadania i poziom pozycji. Zwracaj to samo ID zadania przy ponowieniach, a dla wierszy wymuszaj naturalny klucz (np. external_id lub (account_id, email)) z unikalnym ograniczeniem lub upsertem, żeby reprocessing nie tworzył duplikatów.

Łatwy do uruchomienia
Stworzyć coś niesamowitego

Eksperymentuj z AppMaster z darmowym planem.
Kiedy będziesz gotowy, możesz wybrać odpowiednią subskrypcję.

Rozpocznij