Dane i API

Idempotencja w integracjach: jak nie tworzyć duplikatów

Idempotencja oznacza, że wielokrotne wykonanie tej samej operacji daje ten sam zamierzony skutek co jedno wykonanie. W integracji osiąga się ją przez stabilny klucz operacji, zapis wyniku pierwszego przetworzenia, kontrolę unikalności i zwracanie poprzedniej odpowiedzi dla duplikatu. Dzięki temu timeout lub ponowiony webhook nie tworzy drugiego zamówienia.

Dwie identyczne operacje przechodzą przez kontrolę i tworzą jeden zaakceptowany rekord
Powtórzona wiadomość jest normalnym zdarzeniem; drugi skutek biznesowy nie powinien nim być.Obraz wygenerowany przez OpenAI dla KARQIS

W integracji brak odpowiedzi nie oznacza braku wykonania operacji. System A może wysłać zamówienie do systemu B, system B poprawnie je zapisać, a połączenie zerwać się przed zwróceniem odpowiedzi. Nadawca widzi timeout i próbuje ponownie. Jeżeli odbiorca nie rozpoznaje powtórzenia, w ERP pojawiają się dwa zamówienia, dwie rezerwacje albo dwa dokumenty.

Idempotencja sprawia, że ponowienie jest bezpieczne. Nie polega na blokowaniu wszystkich duplikatów „na oko”, lecz na jednoznacznym rozpoznaniu tej samej intencji biznesowej i zwróceniu tego samego skutku. To podstawowa cecha niezawodnego API, odbiornika webhooków i konsumenta kolejki.

Co dokładnie oznacza idempotencja

RFC 9110 definiuje metodę idempotentną jako taką, dla której zamierzony skutek wielu identycznych żądań jest taki sam jak skutek jednego. GET, PUT i DELETE mają taką semantykę z definicji protokołu, choć aplikacja nadal może zapisywać osobny log każdej próby. POST nie jest automatycznie idempotentny, ale można zaprojektować konkretną operację tak, aby była bezpieczna przy powtórzeniu.

Ważne jest słowo „zamierzony”. Dwa wywołania mogą otrzymać inną odpowiedź czasową albo utworzyć dwa wpisy diagnostyczne, lecz nie powinny stworzyć dwóch skutków biznesowych. Przykładowo ponowne żądanie utworzenia zamówienia może zwrócić istniejący numer ERP wraz z informacją, że operacja była już przetworzona.

Idempotencja nie jest tym samym co deduplikacja po wszystkich polach. Dwa identyczne koszyki złożone przez tego samego klienta mogą być dwiema prawidłowymi transakcjami. Potrzebny jest identyfikator intencji, nie podobieństwo treści.

Jak wybrać klucz idempotencji

Najlepszy klucz powstaje w systemie inicjującym i pozostaje niezmienny dla wszystkich prób tej samej operacji. Może to być UUID zamówienia, identyfikator płatności, numer dokumentu w połączeniu z identyfikatorem spółki albo specjalny nagłówek Idempotency-Key.

Klucz powinien:

  • być unikalny w ustalonym zakresie,
  • identyfikować operację biznesową, nie połączenie HTTP,
  • pozostawać ten sam podczas retry,
  • mieć znane źródło i okres retencji,
  • być powiązany ze skrótem lub istotnymi parametrami żądania.

Jeżeli klient wyśle ten sam klucz z inną treścią, serwer powinien odrzucić żądanie jako konflikt. W przeciwnym razie użytkownik nie wie, czy system zastosował pierwszą, czy drugą wersję danych. Stripe opisuje ten model przez zapis wyniku pierwszego żądania dla danego klucza i porównanie parametrów kolejnych prób.

Atomowy zapis klucza i wyniku

Prosta sekwencja „sprawdź, czy klucz istnieje, a potem utwórz zamówienie” ma lukę. Dwa równoległe żądania mogą sprawdzić klucz w tej samej chwili, oba zobaczyć brak wpisu i oba wykonać operację. Kontrola musi być atomowa.

Najczęściej realizuje się ją przez unikalny indeks w bazie oraz transakcję. Rekord idempotencji może zawierać:

Pole Cel
klucz rozpoznanie operacji
system źródłowy oddzielenie przestrzeni identyfikatorów
skrót żądania wykrycie zmienionej treści
status processing, completed, failed
identyfikator wyniku numer zamówienia, płatności lub dokumentu
odpowiedź dane zwracane przy powtórzeniu
czas i retencja audyt oraz usuwanie wpisów technicznych

Pierwsze żądanie rezerwuje klucz. Kolejne widzi stan processing i może poczekać albo zwrócić odpowiedź „operacja w toku”. Po zakończeniu odbiorca zapisuje wynik. Jeśli proces upadnie pomiędzy zapisem skutku biznesowego a aktualizacją rekordu idempotencji, obie czynności powinny znaleźć się w jednej transakcji lokalnej albo wymagać procesu naprawczego.

Retry, timeout i błędy trwałe

Nie każdą operację należy ponawiać. Błąd walidacji, brak produktu czy zamknięty okres księgowy nie znikną po pięciu sekundach. Timeout, chwilowa niedostępność i ograniczenie ruchu mogą być przejściowe. Wytyczne Microsoft dotyczące błędów przejściowych zalecają ograniczoną liczbę prób, rosnące odstępy z losowym odchyleniem oraz respektowanie sygnałów takich jak Retry-After.

Idempotencja i retry muszą być projektowane razem. Bez idempotencji ponowienie ryzykuje duplikat. Bez retry chwilowy błąd staje się ręcznym incydentem. Bez limitu retry przeciążony system otrzymuje lawinę dodatkowych żądań.

Praktyczna polityka może wyglądać następująco:

  1. krótki timeout dla pojedynczej próby,
  2. jedna natychmiastowa próba tylko dla rzadkiego błędu sieciowego,
  3. kolejne próby z exponential backoff i jitter,
  4. maksymalna liczba prób lub budżet czasu,
  5. przeniesienie do kolejki wyjątków,
  6. alert i możliwość kontrolowanego wznowienia tym samym kluczem.

Webhooki i kolejki dostarczają co najmniej raz

Nadawca webhooka zwykle ponawia zdarzenie, gdy nie otrzyma oczekiwanej odpowiedzi. Kolejka może ponownie udostępnić wiadomość, gdy konsument wykonał pracę, ale nie zdążył jej potwierdzić. Dlatego odbiorca powinien zakładać dostarczenie „co najmniej raz”, nawet jeśli w typowym przebiegu widzi każde zdarzenie tylko raz.

Dla webhooka naturalnym kluczem jest identyfikator zdarzenia nadany przez dostawcę. Warto zapisać go przed uruchomieniem kosztownej logiki i zweryfikować podpis wiadomości. Dla kolejki można użyć identyfikatora komunikatu, ale tylko wtedy, gdy ponowne opublikowanie tej samej operacji zachowuje identyfikator. Często bezpieczniejszy jest biznesowy operationId umieszczony w payloadzie.

Idempotentny powinien być każdy etap, nie tylko wejście. Jeżeli konsument zapisze zamówienie raz, ale dwukrotnie wyśle e-mail albo zlecenie kurierskie, proces nadal ma dwa skutki. W wieloetapowym przepływie każdy efekt uboczny potrzebuje własnej kontroli.

Typowe błędy implementacyjne

Najczęstsze pułapki to generowanie nowego klucza przy każdym retry, przechowywanie kluczy krócej niż trwa okno ponowień, brak unikalnego indeksu, uznawanie wszystkich podobnych danych za duplikat oraz zwracanie sukcesu przed trwałym zapisem wyniku.

Problemem jest też „idempotencja” tylko w pamięci procesu. Po restarcie aplikacja zapomina przetworzone operacje. W środowisku z kilkoma instancjami każda ma inną pamięć. Rejestr musi być współdzielony i trwały przynajmniej przez wymagane okno.

Testy powinny objąć dwa identyczne żądania sekwencyjne, dwa równoległe, timeout po zapisie, restart konsumenta, ten sam klucz z innym payloadem i ponowienie po dłuższej przerwie. Wynikiem ma być dokładnie jeden skutek biznesowy.

Wniosek

Powtórzenia są normalną częścią integracji, nie wyjątkowym przypadkiem. Idempotencja pozwala traktować je bezpiecznie, pod warunkiem że klucz opisuje intencję, kontrola jest atomowa, a wynik pierwszego przetworzenia zostaje zachowany. Dzięki temu można wdrożyć retry bez obawy, że chwilowy problem sieciowy zmieni się w podwójne zamówienie, płatność albo dokument.

Pytania i odpowiedzi

Czy metoda POST może być idempotentna?

Tak, mimo że HTTP nie definiuje POST jako metody z natury idempotentnej. Aplikacja może nadać operacji semantykę idempotentną przez klucz żądania, kontrolę unikalności i zwracanie zapisanego wyniku dla kolejnych identycznych prób.

Jak długo przechowywać klucze idempotencji?

Okres powinien być dłuższy niż maksymalne okno ponowień i możliwego opóźnienia wiadomości. Dla płatności lub dokumentów biznesowych często warto zachować identyfikator trwale, natomiast dla krótkich operacji technicznych można ustalić kontrolowaną retencję.

Czy unikalny numer zamówienia wystarczy?

Często tak, jeżeli jest stabilny i jednoznaczny w obrębie źródła. W integracji wielu kanałów lepiej użyć pary system źródłowy plus numer zewnętrzny albo osobnego identyfikatora operacji, aby uniknąć kolizji.

Źródła

  1. RFC 9110: HTTP Semantics Internet Engineering Task Force, dostęp:
  2. Transient fault handling Microsoft, dostęp:
  3. Idempotent requests Stripe, dostęp:

Zaprojektuj bezpieczne ponowienia