Strategie powtarzania i idempotencja
1) Dlaczego go potrzebujesz
W sieciach awarie są normą: timeouts, przejściowe błędy, klapki sieciowe, przeciążenie. Rekolekcje poprawiają niezawodność tylko wtedy, gdy:1. powtarzać bezpieczne (idempotent),
2. obserwuje się opóźnienia między powtórzeniami,
3. przestrzegane są limity/kwoty i uzależnienia „zdrowie”.
Celem jest skuteczne zachowanie raz na poziomie operacji biznesowych bez fałszywych zabiegów i wyścigów.
2) Taksonomia semantyki dostawy
Najwyższy czas: brak powtarzania, ryzyko utraty (wyrębu, pożaru i zapomnienia).
Przynajmniej raz: możliwe są duplikaty → potrzebna jest idempotencja konsumentów (większość kolejek, haków internetowych).
Effectively-once: duplikaty są możliwe, ale deduplicated poprawnie (klucze, transakcje, outbox).
3) Kiedy wycofać i kiedy nie
Odwrót ma sens: '408', '429' (obserwując 'Retry-After'), '425' (Too Early), '499' (klient zamknięty na obwodzie), '5xx', '504', czasy/przerwy sieciowe, '502' na bramce, 'reset połączenia'.
Nie wycofuj się bez zmiany zapytania: '400/ 401/403/404/422'.
Kontrowersyjne przypadki: „409 konflikt” (zwykle nie retrayim; najpierw odczytujemy stan operacji/potwierdzamy zamiar).
4) Timeouts, backoff i jitter
4. 1 Zasady
Najpierw czas, potem retro: każdy wniosek musi mieć „termin”.
Wykładniczy backoff: 'delay _ n = base 2 ^ n', limit' max _ delay '.
Jitter jest wymagany: dodać losowość decouple „nudne fale synchroniczne”.
4. 2 wzory Jittera
Full jitter: 'sleep = rand (0, base2 ^ n)' to najlepszy wybór ogólny.
Udekorowany jitter: 'sleep = min (max_delay, rand (base, sleep_prev3))' - dla długich dialogów.
Równy jitter: 'sen = base2 ^ n/2 + rand (0, base2 ^ n/2)' - odmiana miękka.
4. 3 Retry-budget
Ograniczyć odsetek przekładni:- „retry _ budget _ per _ min = max (α success_rps, floor β)”; zwykle 'α = 0. 1–0. 2`.
- Jeśli budżet jest wyczerpany, przełączyć na awaryjny szybki/wyłącznik „otwarty”.
5) Interakcja z ograniczaniem prędkości i wyłącznikiem
Szanuj 'Retry-After', 'Limit-Reset' i zliczaj go w cofnięciu.
Przy wysokim '5xx '/timeouts - obniżyć częstotliwość retray i ogólną równoczesność.
- Półotwarte: Pozwala na ograniczone pobieranie próbek.
- Otwórz: natychmiast odrzuca (zapisuje zasób).
- Zamknięte: zwykła praca.
- W przypadku operacji pisania, lepiej jest zwrócić 409/503 z wyraźną wskazówką niż przekręty agresywnych retras.
6) Idempotencja operacji pisania
6. 1 Koncepcja ogólna
Te same intencje → jeden wynik. Podstawą jest klucz idempotencji i przechowywanie zapisów wykonania.
6. 2 Umowa HTTP
Klient wysyła nagłówek:
Idempotency-Key: 7a6b7f9e-2a46-4d0b-9c3a-2b30e1c3c9e3
Idempotency-Key-Expiry: 24h # optional
Serwer:
- Zapisy (klucz, wynik → status, hash ciała) na pierwszy sukces
- w przypadku powtórzenia zwraca starą odpowiedź i nagłówek 'Idempotence-Replay: true';
- w przypadku konfliktu ciała (ten sam klucz, ale inny ładunek) - „409 konflikt”.
6. 3 Przechowywanie i TTL
Klucz tabeli/wartości: 'idempotence _ key', 'request _ hash', 'result', 'status', 'expiry _ at'.
TTL = okno możliwych powtórzeń i późnych dostaw (zwykle 24-72 godziny na płatności).
Indeksy według 'idempotence _ key'; dla wysokiego obciążenia - shading hash.
6. 4 Przykładowy schemat (SQL)
sql
CREATE TABLE idempo_store (
key UUID PRIMARY KEY,
req_hash BYTEA NOT NULL,
status INT NOT NULL,
response JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expiry_at TIMESTAMPTZ NOT NULL
);
6. Pseudokod 5 Handlera
pseudo handle_write(req):
k = req. headers["Idempotency-Key"]
h = hash(req. body)
rec = idempo_store. get(k)
if rec and rec. req_hash == h:
return rec. status, rec. response, {"Idempotency-Replay": "true"}
if rec and rec. req_hash!= h:
return 409, problem("IDEMPOTENT_CONFLICT")
begin tx result = apply_business_mutation (req) # change status upsert once (idempo_store, key = k, req_hash=h, status = 201, response = result, expiry = now () + 2d)
commit
return 201, result
7) wzory „skutecznie raz”
Transactional Outbox: nagrywanie zdarzenia biznesowego i wysyłanie wiadomości z tej samej transakcji w bazie danych przez przekaźnik tła; konsument jest idempotentem.
Skrzynka odbiorcza/tabela przetworzona u konsumenta: zapisz 'event _ id', aby zignorować duplikaty.
Dokładnie raz na Kafce "dokładnie raz w biznesie: nawet z producentem/konsumentem EOS, zastosowana logika nadal powinna być idempotentna.
Transakcje kompensacyjne (Saga): jeśli kroki wycofują się i powodują skutki uboczne, zwracamy system do niezmiennego.
8) Przypadki specjalne: płatności i transakcje finansowe
Silna idempotencja: klucz jest związany z logiką operacji (np. "external _ payment _ id').
Deduplication on PSP - Store 'merchant _ reference' → Jeśli zostanie powtórzone, PSP zwróci ten sam wynik.
Przekłady „od klienta”: zezwalają tylko wtedy, gdy 'Idempotence-Key', w przeciwnym razie ryzyko podwójnego odpisu.
Konkurencja: zamki „na rachunek/narzędzie/kontrakt” na czas realizacji; po powtórzeniu, powrót 409/423.
Obserwowalność: metryki 'idempo _ replay _ total', 'idempo _ conflict _ total'.
9) Haki internetowe i wyzwania zewnętrzne
podpisy HMAC i okno czasowe; najpierw weryfikacja, a następnie przetwarzanie.
Przekładki nadawcy: wykładnicze backoff + jitter, 'max _ attempts' i DLQ.
Konsument - idempotent: 'event _ id' → table/in-memory cache; Porządek „porządny” nie jest gwarantowany.
Kody: 2xx = udany, 4xx = nie powtarzaj, 5xx/timeout = powtórz.
10) Kolejki i zadania podstawowe
Co najmniej raz domyślnie → duplikaty są nieuniknione.
Zapisz 'task _ id'/' event _ id' i status wykonania; z duplikatami - krótka ścieżka „replay”.
DLQ i trucizny: próba licznika, kwarantanna, ręczne parsowanie.
Limity konkurencji (semafory) i pracowników idempotentnych.
11) Wersioning i „naturalne” klucze
Klucze naturalne (numer konta + data + numer dokumentu) zwiększają odporność na powtarzanie.
Przy zmianie schematu/wersji należy umieścić klucz wersji w 'Idempotence-Key' lub w hashu zapytania.
12) nagłówki HTTP i linki do klienta
'Idempotency-Key', 'Idempotence-Replay', 'Retry-After', 'Preferer: wait = <sec>' (na długich operacjach), 'If-Match '/' ETag' (blokady optymistyczne).
409 dla kluczowego konfliktu 425/429/503 z ważnym 'Retry-After'.
Dla operacji „długich” - odbiór statusu asynchronicznego ('202 Accepted' + 'Location' na zasób stanu).
13) Scenariusze testowania i chaosu
Testy negatywne: podwójne wysyłanie, powtarzanie z innym ciałem, desynchronizacja zegara.
Brak zamówienia: 't2' jest przed 't1'.
Wtrysk czasu/' RST '/' EOF ', połowa wniosków (slow-POST).
Upadła magazyn idempotencji → nieudane zachowanie (lepsza awaria niż podwójne odpisanie).
14) Mierniki i wpisy
'retries _ total {reason}', 'retry _ budget _ used {route}', 'backoff _ seconds _ bucket'.
'idempo _ replay _ total', 'idempo _ conflict _ total', 'duplikat _ detected _ total'.
Udostępnianie 409/425/429/5xx na trasach; p95/p99 „czas do sukcesu” z rekolekcjami.
Alerty: budżet przekaźnika spalin, gwałtowny wzrost konfliktów idempotencji, wzrost DLQ.
15) Antypattery
Wycofać wszystkie błędy z rzędu.
Brak jitter → synchroniczne fale retras.
Długotrwałe klucze bez TTL i czyszczenia.
Zapisanie wyniku po popełnieniu efektu ubocznego (naruszenie skrzynki zewnętrznej).
Dzienniki bez 'trace _ id'/' idempotence _ key' są → niemożliwe do wygenerowania.
Agresywny równoległy przekaz operacji pisania.
16) Lista kontrolna gotowości Prod
- Ujednolicona polityka: co retrayim, co nie; kody i instrukcje klienta.
- Wykładnicze backoff + full jitter; „retry _ budget” określone.
- Kontrakt „Idempotency-Key” + wyniki przechowywania z TTL.
- Skrzynka odbiorcza/skrzynka odbiorcza dla wydarzeń; DLQ; ograniczenia konkurencyjne.
- Integracja z wyłącznikiem, szacunek „Retry-After”.
- Metrics/Alerts by Retray/Duplikat/Conflict.
- Zestaw testów chaosu i emulacji awarii sieci.
- Dokumentacja klienta - przykłady kopii zapasowych i statusów.
17) TL; DR
Rekolekcje są przydatne tylko wraz z idempotencją. Wprowadź 'Idempotence-Key' i przechowywanie wyników, zastosuj wykładnicze backoff z jitter i retry-budget, przestrzegaj 'Retry-After', zintegruj z wyłącznikiem. Dla zdarzeń - skrzynka odbiorcza/skrzynka odbiorcza; w przypadku płatności, ścisłej deduplikacji i zamków. Pomiar przekładów i konfliktów, duplikaty testów i timeouts.