Przejdź do treści
DedicatedPHP Kontakt

Idempotencja w PHP przy timeoutach i ponowieniach

Projektuj powtarzalne operacje PHP odporne na timeouty, współbieżność i utracone odpowiedzi bez dublowania płatności, zamówień ani wywołań zewnętrznych.

Schemat API PHP używającego klucza idempotencji do kontrolowania ponowień, statusów i wywołań zewnętrznych

Timeout nie oznacza, że operacja się nie powiodła: potwierdza jedynie, że klient nie otrzymał odpowiedzi w wyznaczonym czasie. Serwer mógł już utworzyć zamówienie, dostawca płatności mógł zaakceptować obciążenie, a proces asynchroniczny może nadal się wykonywać. Jeśli klient ponowi próbę bez kontroli, ta sama intencja biznesowa może wywołać zduplikowane skutki.

Idempotencja w PHP przekształca techniczne powtórzenie w odczyt lub zwrócenie wcześniej uzyskanego wyniku. Nie polega na ignorowaniu wszystkich duplikatów ani na poleganiu wyłącznie na tym, że użytkownik nie kliknie dwa razy. Jest to jawny kontrakt między klientem, API, warstwą trwałego zapisu oraz — gdy ma to zastosowanie — systemami zewnętrznymi.

Problem: odpowiedź ginie, ale skutek pozostaje

Problem: odpowiedź ginie, ale skutek pozostaje — guía visual de DedicatedPHP

Rozważ endpoint potwierdzający zakup. Aplikacja waliduje żądanie, zapisuje zamówienie, zleca obciążenie i przygotowuje odpowiedź. Połączenie zostaje przerwane tuż przed jej otrzymaniem przez klienta. Przy ponownym wysłaniu tego samego formularza endpoint nie może wywnioskować na podstawie treści, że chodzi o ten sam zakup: dwa zamówienia z tymi samymi produktami mogą być prawidłowymi, odrębnymi intencjami.

Problem występuje również przy rejestracji użytkowników, przydzielaniu środków, wystawianiu dokumentów, synchronizacjach, webhookach i działaniach administracyjnych. Warto rozdzielić trzy elementy:

  • Intencja biznesowa: „chcę potwierdzić ten konkretny zakup”.
  • Żądanie techniczne: wysłanie HTTP z nagłówkami, treścią i kontekstem uwierzytelnienia.
  • Próba wykonania: każde wewnętrzne przetwarzanie, ponowienie z kolejki lub wywołanie dostawcy.

Klucz idempotencji identyfikuje intencję, a nie połączenie HTTP ani każdą próbę po stronie serwera. Dlatego musi przetrwać ponowienia sieciowe, a gdy wymaga tego przepływ, także restarty procesu.

Które operacje wymagają idempotencji, a które nie

Priorytetowo traktuj operacje, które tworzą, potwierdzają, pobierają opłatę, wysyłają, rezerwują, powiadamiają lub modyfikują zasób z istotnymi konsekwencjami. Jasnymi kandydatami są POST /payments, potwierdzenie zamówienia lub odbiór webhooka. Dotyczy to również zadania kolejki, które może zostać dostarczone więcej niż raz.

Czysty odczyt zwykle nie potrzebuje klucza idempotencji. Aktualizacja może mieć inną semantykę: ustawienie pożądanego stanu, takie jak PUT /profiles/42, może być idempotentne z założenia, jeśli ta sama reprezentacja pozostawia zasób bez zmian. Natomiast działanie takie jak „dodaj saldo” nie staje się idempotentne wyłącznie przez użycie określonego czasownika.

Nie należy też używać klucza jako zamiennika innych reguł. Aby uniemożliwić dwie kolidujące rezerwacje w ograniczonym zasobie, potrzebne są niezmienniki domenowe, kontrola współbieżności i polityka rezerwacji. Aby wykonać zadanie tylko raz w środowisku rozproszonym, rzeczywiste dostarczanie zwykle odbywa się co najmniej raz; konsument musi tolerować duplikaty.

Projekt klucza i trwałego rejestru

Klient powinien generować nieprzezroczysty i wystarczająco nieprzewidywalny klucz w chwili powstania intencji biznesowej, przechowywać go tak długo, jak długo może ponawiać próbę, i przesyłać go na przykład w Idempotency-Key. Jeśli serwer generuje go przy każdym odbiorze, nie będzie w stanie powiązać późniejszego powtórzenia. W przepływach wewnętrznych klucz może zostać wyprowadzony ze stabilnego identyfikatora zdarzenia biznesowego.

Jego zakres musi obejmować aktora lub tenant oraz operację. Ten sam ciąg nie powinien kolidować między dwoma kontami ani między „utworzeniem zamówienia” a „wystawieniem zwrotu”. Określ retencję zgodną z rzeczywistym okresem ponowień i ryzykiem domenowym. Zbyt wczesne usunięcie rejestru ponownie otwiera drogę do duplikatu; przechowywanie go bezterminowo zwiększa koszt i wymaga polityki prywatności oraz usuwania danych.

Minimalny model trwałego zapisu obejmuje:

  • zakres bezpieczeństwa lub tenant, nazwę operacji i klucz idempotencji;
  • kryptograficzny skrót znormalizowanego payloadu;
  • status: processing, completed, failed lub pending, gdy potwierdzenie zewnętrzne jest niepewne;
  • kod i treść odpowiedzi, które zostaną zwrócone w sposób powtarzalny;
  • identyfikatory utworzonego zasobu, korelację wewnętrzną i referencję dostawcy zewnętrznego;
  • daty utworzenia, aktualizacji i wygaśnięcia.

Skrót zapobiega istotnemu błędowi: ponownemu użyciu tego samego klucza z innymi danymi. W takiej sytuacji zwróć konflikt i nie przetwarzaj nowego payloadu. Aby porównanie było wiarygodne, normalizuj pola, których kolejność nie ma znaczenia, oraz wyklucz zmienne metadane, które nie są częścią intencji.

Przepływ PHP: zarezerwuj przed wywołaniem skutku

Ochrona musi być wsparta unikalnym ograniczeniem w bazie danych obejmującym zakres, operację i klucz. Najpierw odczytać, a potem wstawić nie wystarczy: dwa jednoczesne żądania mogą zaobserwować brak rekordu i kontynuować równocześnie.

Zalecany przepływ polega na atomowej rezerwacji. Jeśli wstawienie się powiedzie, ten proces jest początkowym właścicielem wykonania. W przypadku konfliktu unikalności odczytywany jest istniejący rekord, weryfikowany jest skrót, a następnie podejmowane działanie zależne od jego statusu. Ukończony wynik zwraca dokładnie zapisaną odpowiedź; operacja w toku może zwrócić status oczekiwania lub czekać tylko przez ograniczony czas przed ponownym odczytaniem rekordu.

begin transaction
insert idempotency_records(scope, operation, key, payload_hash, status)
values (?, 'create_order', ?, ?, 'processing')
-- unikalne ograniczenie wskazuje właściciela
commit

if reservation_was_created:
    result = execute_business_operation()
    persist_completed_response(result)
else:
    record = load_existing_record()
    assert_same_payload_hash(record)
    return replay_or_pending(record)

Nie utrzymuj otwartej transakcji ani blokady wiersza podczas wolnego wywołania dostawcy. Zmniejsza to przepustowość i może generować długotrwałe blokady. Zamiast tego rezerwuj i potwierdzaj stan lokalny w krótkich transakcjach. Jeśli skutek zewnętrzny i rekord lokalny muszą być skoordynowane, zapisz dodatkowo zlecenie wysyłki w tabeli transakcyjnej i przetwarzaj je osobno. Ten wzorzec nie eliminuje ponowień, ale pozwala odzyskać oczekującą pracę bez utraty zarejestrowanej intencji.

Współbieżność, timeouty i niepewne stany

Dwa żądania z tym samym kluczem mogą nadejść w odstępie milisekund. Unikalne ograniczenie ustala, które z nich rezerwuje operację. Drugie nie może uruchomić kolejnego skutku zewnętrznego. Może zwrócić 202, dopóki status to processing lub pending, wraz z identyfikatorem do sprawdzenia wyniku; jeśli kontrakt wymaga odpowiedzi synchronicznej, może zastosować ograniczone oczekiwanie i ponownie odczytać rekord.

Błąd przed rozpoczęciem jakiegokolwiek skutku pozwala oznaczyć status failed z powtarzalnym błędem. Jednak timeout podczas wywołania systemu zewnętrznego tworzy niepewność: nie można automatycznie oznaczyć operacji jako nieudanej ani po prostu ponownie wysłać zlecenia. Zapisz referencję wysłanego żądania, jeśli istnieje, sprawdź u dostawcy wynik za pomocą tej referencji i uzgodnij wynik. Dopóki nie ma potwierdzenia, utrzymuj pending i komunikuj, że wynik nie jest jeszcze ostateczny.

Wywołanie zewnętrzne również potrzebuje stabilnej referencji. Jeśli dostawca obsługuje własny klucz idempotencji, przekaż klucz powiązany z tą samą intencją. Jeśli go nie obsługuje, użyj identyfikatorów handlowca, późniejszego odczytu, okresowego uzgadniania i procedur operacyjnych dla niejednoznacznych przypadków. Żadna lokalna transakcja nie może uczynić atomowymi zapisu w bazie danych i niezależnego zdalnego API.

Czego nie rozwiązuje klucz idempotencji

Idempotencja zapobiega powtórzeniu rozpoznanej intencji; nie decyduje, jak cofnąć nieodwracalny skutek. Fizyczna wysyłka, już rozliczony przelew lub powiadomienie wyświetlone użytkownikowi mogą wymagać kompensacji, anulowania lub ręcznej obsługi. Projektuj te działania jako jawne procesy biznesowe, z uprawnieniami, statusami i audytem.

Nie myl też korekty z ponowieniem. Jeśli użytkownik zmieni adres, kwotę lub produkty po błędzie, powstaje nowa intencja i należy użyć nowego klucza. Ponowne użycie poprzedniego z innym payloadem powinno skutkować konfliktem, a nie cichą aktualizacją pierwotnej operacji.

Testy, obserwowalność i lista kontrolna

Testy, obserwowalność i lista kontrolna — guía visual de DedicatedPHP

Testuj więcej niż tylko ścieżkę sukcesu. Przerwij odpowiedź po zapisaniu wyniku, powtórz ten sam klucz równolegle, zrestartuj worker po zarezerwowaniu rekordu i zasymuluj timeout po wysłaniu żądania zewnętrznego. Zweryfikuj, że istnieje tylko jeden zasób biznesowy, powtórzona odpowiedź zachowuje ten sam wynik, a inny payload z tym samym kluczem nie jest akceptowany.

Rejestruj, bez ujawniania danych wrażliwych, klucz lub bezpieczny identyfikator od niego pochodzący, zakres, status, korelację i referencję zewnętrzną. Metryki konfliktów kluczy, operacji oczekujących zbyt długo i nierozwiązanych uzgodnień pomagają wsparciu oraz zespołom operacyjnym odróżnić zwykłe ponowienie od incydentu.

  • Czy klucz reprezentuje intencję biznesową i ma określony zakres?
  • Czy istnieje unikalne ograniczenie, które uniemożliwia dwie współbieżne rezerwacje?
  • Czy porównywany jest skrót payloadu, a zmiany intencji są odrzucane?
  • Czy zapisywana jest odpowiedź lub wynik, który można spójnie powtórzyć?
  • Czy niepewne stany umożliwiają sprawdzenie i uzgodnienie przed ponowieniem?
  • Czy każdy skutek zewnętrzny ma referencję, mechanizm odzyskiwania i alternatywę operacyjną?
  • Czy przetestowano duplikaty, awarie, ponowienia z kolejki i rzeczywistą współbieżność?

Stosowana w ten sposób idempotencja nie obiecuje, że sieć będzie niezawodna. Sprawia, że nieuniknione awarie mają kontrolowalny, śledzalny i spójny dla biznesu rezultat.

Chcesz zastosować te pomysły w swoim projekcie?Omówmy Twoją platformę PHP.
Zobacz powiązaną usługę