Idempotencja i klucze
Czym jest idempotencja
Idempotencja jest właściwością operacji, w której powtarzanie z tym samym identyfikatorem nie zmienia ostatecznego efektu. W systemach rozproszonych jest to główny sposób, aby wynik był równoważny „dokładnie jednej obróbce”, pomimo przekaźników, duplikatów wiadomości i terminów.
Kluczowy pomysł: każda potencjalnie powtarzalna operacja powinna być oznaczona kluczem, za pomocą którego system uznaje „to już zostało wykonane” i stosuje wynik nie więcej niż raz.
Gdzie to ma znaczenie
Płatności i salda: odpisy/kredyty według 'operation _ id'.
Rezerwacje/kwoty/limity: ten sam czas na start lub lądowanie/zasób.
Haki/powiadomienia: wielokrotna dostawa nie powinna powielać efektu.
Import/migracja - ponowne uruchamianie plików/pakietów.
Przetwarzanie strumieniowe: duplikaty z brokera/CDC.
Rodzaje kluczy i ich zakres
1. Klucz operacyjny - identyfikator konkretnej próby transakcji gospodarczej
Przykłady: 'idempotence _ key' (HTTP), 'operation _ id' (RPC).
Zakres: usługa/kruszywo; jest przechowywany w tabeli deduplikacji.
2. Klucz zdarzenia - niepowtarzalny identyfikator zdarzenia/komunikatu
Przykłady: 'event _ id' (UUID),' (producer_id, sekwencja) '.
Obszar: grupa konsumentów/konsumentów; chroni projekcje.
3. Klucz biznesowy - klucz domeny naturalnej
Przykłady: 'payment _ id',' faktura _ number ',' (user_id, day) '.
Obszar: kruszywo; używane w kontrolach unikalności/wersji.
Polityka TTL i retencji
Klucze TTL ≥ możliwe okno redo: zatrzymanie dziennika + opóźnienia sieci/procesu.
Dla domen krytycznych (płatności) TTL - dni/tygodnie; dla telemetrii - godziny.
Czyste stoły dedup z tłem pracy; dla audytu - archiwum.
Sklepy kluczy (deduplikacja)
Baza danych transakcji (zalecana): niezawodne indeksy upsert/unique, wspólna transakcja z efektem.
KV/Redis: szybki, wygodny dla krótkiego TTL, ale bez wspólnej transakcji z OLTP - ostrożny.
State store stream processor: locally + changelog in broker; dobry w Flink/KStreams.
- idempotency_keys
'consumer _ id' (ила' service '),' op _ id' (PK на сара), 'applied _ at', 'ttl _ expires _ at', 'result _ hash '/' response _ status' (ова.) .
Indeksy: '(consumer_id, op_id)' - niepowtarzalne.
Podstawowe techniki wdrażania
1) Efekt + Transakcja Progress
Zapisz wynik i przechwytuj postęp odczytu/pozycji w jednej transakcji.
pseudo begin tx if not exists(select 1 from idempotency_keys where consumer=:c and op_id=:id) then
-- apply effect atomically (upsert/merge/increment)
apply_effect(...)
insert into idempotency_keys(consumer, op_id, applied_at)
values(:c,:id, now)
end if
-- record reading progress (offset/position)
upsert offsets set pos=:pos where consumer=:c commit
2) Optymistyczne współistnienie (wersja jednostkowa)
Chroni przed podwójnym efektem podczas wyścigów:sql update account set balance = balance +:delta,
version = version + 1 where id=:account_id and version=:expected_version;
-- if 0 rows are updated → retry/conflict
3) Idempotentne zlewy (upsert/fuzja)
Accrue raz:sql insert into bonuses(user_id, op_id, amount)
values(:u,:op,:amt)
on conflict (user_id, op_id) do nothing;
Idempotencja w protokołach
HTTP/REST
'Idempotency-Key: <uuid' hash> 'nagłówek.
Serwer przechowuje rekord klucza i ponownie zwraca tę samą odpowiedź (lub kod '409 '/' 422' w przypadku niezmiennego konfliktu).
W przypadku „niepewnego” programu POST wymagana jest stabilna polityka „Idempotence-Key” + harmonogram/przekaźnik.
gRPC/RPC
Metadane 'idempotence _ key', 'request _ id' + deadline.
Implementacja serwera - tak jak w REST: tabela deduplikacji w transakcji.
Brokerzy/Streaming (Kafka/NATS/Pulsar)
Producent: stabilny "event _ id'/producent idempotent (tam gdzie jest obsługiwany).
Konsument: potrącenie przez „(consumer_id, event_id)” lub przez wersję biznesową agregatu.
Oddzielny DLQ dla wiadomości nieidempotentnych/uszkodzonych.
Haki internetowe i partnerzy zewnętrzni
Żądanie "Idempotency-Key "/" event _ id' w umowie; ponowna dostawa musi być bezpieczna.
Przechowywanie 'notification _ id' i wysyłanie statusów; na retray - nie duplikuj.
Projekt klucza
Determinizm: Przekaźniki muszą wysyłać ten sam klucz (generować z wyprzedzeniem na klienta/orkiestratora).
Zakres: Formularz 'op _ id' jako' service: aggregate: id: purpose '.
Kolizje: użyj UUIDv7/ULID lub hash z parametrów biznesowych (z solą, jeśli to konieczne).
Hierarchia: ogólne 'operation _ id' na → front jest tłumaczone na wszystkie podoperacje (łańcuch idempotentny).
Aspekty UX i produktu
Powtarzające się żądanie klucza musi zwrócić ten sam wynik (w tym ciało/status) lub wyraźny „już wykonany”.
Pokaż użytkownikowi statusy „operacja jest przetwarzana/zakończona” zamiast próbować ponownie „na szczęście”.
Dla długich operacji - sondaż według klucza ('GET/operations/{ op _ id}').
Obserwowalność
Log 'op _ id',' event _ id', 'trace _ id', wynik:' APPLIED '/' ALREADY _ APPLIED '.
Wskaźniki: szybkość powtarzania, rozmiar stołu dedup, czas transakcji, konflikty wersji, szybkość DLQ.
Ślad: klucz musi przejść przez polecenie → zdarzenie → projekcja → wywołanie zewnętrzne.
Bezpieczeństwo i zgodność
Nie przechowywać PII w klawiszach; klucz - identyfikator, nie ładunek.
Szyfruj pola wrażliwe w rekordach deduplikacji z długim TTL.
Polityka zatrzymywania: TTL i archiwum; prawo do bycia zapomnianym - poprzez krypto-usunięcie odpowiedzi/metadanych (jeśli zawierają PII).
Testowanie
1. Duplikaty: uruchom jeden komunikat/żądanie 2-5 razy - efekt dokładnie jeden.
2. Spadek między krokami: przed/po zapisaniu efektu, przed/po ustawianiu przesunięcia.
3. Ponowne uruchomienie/przywrócenie równowagi konsumenckiej: brak podwójnego zastosowania.
4. Konkurencja: równoległe zapytania z jednym 'op _ id' → jeden efekt, drugi -' JUŻ _ APPLIED/409 '.
5. Długotrwałe klucze: Sprawdzenie wygaśnięcia TTL i ponowne próby po odzyskaniu.
Anty-wzory
Losowy nowy klucz dla każdego przekaźnika: system nie rozpoznaje powtórzeń.
Dwa osobne zobowiązania: najpierw efekt, potem offset - upadek między nimi duplikuje efekt.
Zaufanie tylko maklerowi: brak deduplikacji w siniaku/agregacie.
Brakująca wersja zbiorcza: powtarzające się zdarzenie zmienia się po raz drugi.
Klucze tłuszczowe: klucz obejmuje pola biznesowe/PII → wycieki i skomplikowane indeksy.
Brak powtarzalnych odpowiedzi: Klient nie może bezpiecznie wycofać się.
Przykłady
Płatność POST
Klient: 'POST/płatności' + 'Idempotency-Key: k-789'.
Serwer: transakcja - tworzy 'płatność' i wpis w 'idempotence _ keys'.
Redo: zwraca to samo '201 '/ciało; w przypadku niezmiennego konfliktu - „409”.
Bonus memoriałowy (sink)
sql insert into credits(user_id, op_id, amount, created_at)
values(:u,:op,:amt, now)
on conflict (user_id, op_id) do nothing;
Projekcja z wydarzeń
konsument przechowuje „widziane (event_id)” i „wersję” jednostki; powtarzać - ignorować/idempotent upsert.
Postęp odczytu jest przechwytywany w tej samej transakcji co aktualizacja projekcji.
Lista kontrolna produkcji
- Wszystkie niebezpieczne operacje mają klucz idempotentny i ich zakres określony.
- Istnieją tabele deduplikacji z TTL i unikalne indeksy.
- Efekt i postęp czytania są popełniane atomowo.
- Optymistyczna konkurencja (wersja/sekwencja) jest zawarta w modelu pisma.
- Umowy API przechwytują "Idempotency-Key "/" operation _ id' oraz zachowanie powtórzeń.
- Mierniki i dzienniki zawierają 'op _ id'/' event _ id'/' trace _ id'.
- Testy na duplikaty, upadki i wyścigi - w CI.
- Przestrzegane są zasady TTL/Archiwum oraz bezpieczeństwo PII.
NAJCZĘŚCIEJ ZADAWANE PYTANIA
W jaki sposób 'Idempotence-Key' pochodzi z 'Request-Id'?
„Id żądania” - ślad; można to zmienić na przekładkach. „Idempotency-Key” jest semantycznym identyfikatorem operacji, który musi być taki sam podczas powtórzeń.
Czy można zrobić idempotencję bez bazy danych?
W przypadku krótkiego okna - tak (Redis/in-process cache), ale bez wspólnej transakcji, ryzyko powielania wzrasta. W domenach krytycznych jest lepiej w jednej transakcji bazodanowej.
Co zrobić z partnerami zewnętrznymi?
Negocjuj klucze i powtarzalne odpowiedzi. Jeśli partner nie obsługuje - zawinąć połączenie do warstwy idempotent i przechowywać „już zastosowane”.
Jak wybrać TTL?
Suma maksymalnych opóźnień: zatrzymanie dziennika + najgorszy przypadek netto/rebalance + bufor. Dodać zapasy (× 2).
Razem
Idempotencja jest dyscypliną kluczy, transakcji i wersji. Stabilne identyfikatory pracy + atomowe utrwalenie efektu i postęp odczytu + idempotentne zlewy/projekcje dają „dokładnie jeden efekt” bez magii na poziomie transportu. Sprawiają, że klawisze deterministyczne, TTL realistyczne i testy złośliwe. Wtedy przekłady i duplikaty staną się rutynowe, a nie incydenty.