Deduplikacja zdarzeń
1) Dlaczego deduplikowanie
Duplikaty pojawiają się ze względu na przekaźniki, timeouts sieci, awaria i powtórzenie danych historycznych. Jeżeli nie są kontrolowane:- osoby niezmienne są naruszane (podwójne obciążenia, powtarzające się wiadomości e-mail/SMS, „dwukrotnie utworzone”);
- Wzrost kosztów (ponowne zapisy/przeróbki)
- zniekształcona analityka.
Celem deduplikowania jest zapewnienie jednorazowego obserwowanego efektu z możliwymi do zaakceptowania powtórzeniami transportowymi, często wraz z idempotencją.
2) Gdzie umieścić deduplikowanie (poziomy dokładności)
1. Brama krawędzi/API - odcięcie wyraźnych duplikatów przez 'Idempotency-Keu '/body + signature.
2. Broker/stream - logiczne deduplication by key/sequence, coalescing at a miss (rzadziej - ze względu na koszty).
3. Odbiornik zdarzeń (konsument) - główna lokalizacja: skrzynka odbiorcza/tabela klucza/pamięć podręczna.
4. Zlewozmywak (DB/cache) - unikalne klucze/UPSERT/wersje/kompresja.
5. ETL/analysis - deadline by time window and key in column beds.
Zasada: jak najwcześniej, ale biorąc pod uwagę koszt fałszywych pozytywów i potrzebę powtórzenia.
3) Klucze deduplikacji
3. 1 Naturalne (preferowane)
'payment _ id',' order _ id', 'saga _ id # step', 'aggregate _ id # seq'.
Gwarantuje stabilność i znaczenie.
3. 2 Złożony
„(tenant_id, typ, external_id, wersja)” ила „(user_id, event_ts_truncated, payload_hash)”.
3. 3 Odcisk palca
Hash deterministycznego pola (normalizować kolejność/rejestry), opcjonalnie „HMAC (secret, payload)”.
3. 4 sekwencje/wersje
Monotonny kruszywo 'seq' per (optymistyczne blokowanie/wersioning).
Anty-wzór: „losowy UUID” bez połączenia z podmiotem gospodarczym jest niemożliwe.
4) Okna czasowe i zamówienie
Okno deduplikacji - okres, w którym zdarzenie może powtórzyć się (zwykle 24-72 godziny; dla finansów - dłużej).
Nieporządek: bądźmy spóźnieni. W ramach strumieniowych - czas imprezy + znaki wodne.
Sliding/Fix-window deadup: "Czy widziałeś klucz w ostatnich minutach N? ».
Sequence-aware: jeżeli 'seq' ≤ ostatni przetworzony - double/repeat.
5) Struktury i implementacje danych
5. 1 Dokładna księgowość
Redis SET/STRING + TTL: 'SETNX key 1 EX 86400' → 'po raz pierwszy - przetwarzamy, inaczej - SKIP'.
Pamięć podręczna LRU/LFU (in-proc): szybka, ale lotna → lepsza tylko jako pierwsza bariera.
Unikalne indeksy SQL + UPSERT: „wstawić lub zaktualizować” (efekt idempotentny).
5. 2 Przybliżone struktury (probabilistyczne)
Filtr Bloom/Cuckoo: tania pamięć, fałszywe pozytywy są możliwe. Nadaje się do oczywistego „hałaśliwego” spadku (na przykład telemetrii), a nie do finansowania/zamówień.
Count-Min Sketch: Oszacowanie częstotliwości do ochrony przed „gorącym” bierze.
5. 3 Państwa przesyłania strumieniowego
Kafka Streams/Flink: keyed state store z TTL, dedup by key in the window; punkt kontrolny/przywrócenie.
Znak wodny + dozwolone opóźnienie: Zarządza późnym oknem zdarzeń.
6) Wzorce transakcyjne
6. 1 Skrzynka odbiorcza (tabela przychodząca)
Zapisz 'message _ id'/klucz i wynik do efektów ubocznych:pseudo
BEGIN;
ins = INSERT INTO inbox(id, received_at) ON CONFLICT DO NOTHING;
IF ins_not_inserted THEN RETURN cached_result;
result = handle(event);
UPSERT sink with result; -- idempotent sync
UPDATE inbox SET status='done', result_hash=... WHERE id=...;
COMMIT;
Powtórka zobaczy nagranie i nie powtórzy efektu.
6. 2 Skrzynka zewnętrzna
Rekord biznesowy i wydarzenie w jednej transakcji → wydawca wysyła do brokera. Nie eliminuje podwójnego z konsumenta, ale wyklucza „dziury”.
6. 3 Niepowtarzalne indeksy/UPSERT
sql
INSERT INTO payments(id, status, amount)
VALUES ($1, $2, $3)
ON CONFLICT (id) DO NOTHING; -- "create once"
lub uaktualnienie wersji kontrolowanej:
sql
UPDATE orders
SET status = $new, version = version + 1
WHERE id=$id AND version = $expected; -- optimistic blocking
6. 4 Wersioning kruszyw
Zdarzenie ma zastosowanie, jeśli "zdarzenie. wersja = agregat. wersja + 1 '. W przeciwnym razie - podwójny/powtarzający się/konflikt.
7) Deadup i brokerów/strumieni
7. 1 Kafka
Idempotent Producent zmniejsza podwójne wejście.
Transakcje pozwalają na atomowe dokonywanie przesunięć + rekordów wyjściowych.
Zagęszczenie: przechowuje ostatnią wartość na klucz - dedup/koalescing (nie dla płatności).
Strona konsumencka: sklep stanowy/Redis/DB dla kluczy okiennych.
7. 2 NATS/JetStream
Ack/redelivery → co najmniej raz. Dedup w konsument (skrzynka odbiorcza/Redis).
Sekwencja JetStream/praca konsumencka ułatwia identyfikację powtórzeń.
7. 3 kolejki (Królik/SQS)
Wyprzedaż widzialności + wielokrotne dostawy → potrzebujesz klucza + deadstore.
Funkcja SQS FIFO z funkcją „ GroupId ”/„ Dedupl, Id” pomaga, ale okna TTL są ograniczone przez dostawcę - utrzymać klucze dłużej, jeśli wymaga tego biznes.
8) Magazynowanie i analizatory
8. 1 Kliknij Dom/Zapytanie
Dedup przez okno: 'ORDER BY key, ts' i' argMax '/' anyLast 'z warunkiem.
ClickHouse:sql
SELECT key,
anyLast(value) AS v
FROM t
WHERE ts >= now() - INTERVAL 1 DAY
GROUP BY key;
Lub zmaterializowana warstwa „unikalnych” zdarzeń (połączenie przez klucz/wersja).
8. 2 Kłody/telemetria
Powiedzmy approximate-dump (Bloom) na połknięciu → zapisz sieć/dysk.
9) Ponowne przetwarzanie, powtarzanie i zasypywanie
Klawisze Dedup muszą przetrwać powtórkę (TTL ≥ okno powtórzenia).
Dla zasypki, użyj przestrzeni klucza z wersją ('key # source = batch2025') lub oddzielnych „przecieków”, aby nie zakłócać okna online.
Zapisz wyniki artefaktów (hash/wersja) - przyspiesza to „szybkie pomijanie” na powtórkach.
10) Metryka i obserwowalność
'dedup _ hit _ total '/' dedup _ hit _ rate' - odsetek złowionych duplikatów.
'dedup _ fp _ rate' dla filtrów probabilistycznych.
„window _ size _ seconds” (według późnych przylotów telemetrycznych).
'inbox _ conflict _ total', 'upsert _ conflict _ total'.
„replayed _ events _ total”, „skipped _ by _ inbox _ total”.
Profile według najemcy/klucza/typu: gdzie są najbardziej wzięte i dlaczego.
Лова: 'message _ id',' idempotency _ key ',' seq ',' window _ id', 'action = process' skip '.
11) Bezpieczeństwo i prywatność
Nie umieszczać PII w kluczu; używać hashes/aliasów.
Aby podpisać odcisk palca - HMAC (secret, canonical_payload), aby uniknąć kolizji/fałszerstwa.
Koordynacja czasu przechowywania kluczy z zachowaniem zgodności (RODO).
12) Wydajność i koszt
In-proc LRU Redis SQL według opóźnienia/kosztu na operację.
Redis: tanie i szybkie, ale rozważyć objętość klawiszy i TTL; shardy przez 'lokator/hash'.
SQL: drogie przez p99, ale zapewnia silne gwarancje i publiczność.
Filtry probabilistyczne: bardzo tanie, ale FP są możliwe - użyj tam, gdzie „dodatkowe SKIP” nie jest krytyczne.
13) Anty-wzory
"Mamy Kafkę dokładnie raz - nie jest potrzebny klucz. "Potrzebne - w siniaku/warstwie biznesowej.
Zbyt krótki TTL dla kluczy → powtórki/opóźnienie dostarczy podwójne.
Global single dedup → hotspot i SPOF; nie strzelane przez najemcę/klucz.
Dedup tylko w pamięci - utrata procesu = fala zabierania.
Kwitnie za pieniądze/zamówienia - fałszywie pozytywne pozbawi legalną operację.
Niespójna kanonizacja ładunku - różne hashes dla wiadomości, które są identyczne w znaczeniu.
Ignorowanie out-of-order - późne wydarzenia są oznaczone duplikatami błędnie.
14) Lista kontrolna wdrażania
- Zdefiniować klucz naturalny (lub złożony/odcisk palca).
- Ustaw okno dedup i politykę „nieprzewidywalności”.
- Wybierz poziom (-y): krawędź, konsument, zlewozmywak; zapewnić ostrzenie.
- Wdrożenie skrzynki odbiorczej/UPSERT; dla przepływów - stan kluczowy + TTL.
- Jeśli potrzebujesz przybliżonej bariery - Bloom/Cuckoo (tylko dla domen niekrytycznych).
- Konfiguracja kompatybilności powtórki (TTL ≥ okno powtórzenia/zasypki).
- Wskaźniki „dedup _ hit _ rate”, konflikty i opóźnienia okienne; deski rozdzielcze dla jednego najemcy.
- Dzień gry: timeouts/retrays, replay, out-of-order, cache drop.
- Kanonizacja ładunku dokumentów i wersioning kluczy.
- Wykonaj testy obciążenia klawiszy gorących i długich okien.
15) Konfiguracje próbki/Kod
15. 1 Redis SETNX + TTL (bariera)
lua
-- KEYS[1] = "dedup:{tenant}:{key}"
-- ARGV[1] = ttl_seconds local ok = redis. call("SET", KEYS[1], "1", "NX", "EX", ARGV[1])
if ok then return "PROCESS"
else return "SKIP"
end
15. 2 Skrzynka odbiorcza PostgreSQL
sql
CREATE TABLE inbox (
id text PRIMARY KEY,
received_at timestamptz default now(),
status text default 'received',
result_hash text
);
-- In the handler: INSERT... ON CONFLICT DO NOTHING -> check, then UPSERT in blue.
15. 3 strumienie Kafka
java var deduped = input
.selectKey((k,v) -> v.idempotencyKey())
.groupByKey()
.windowedBy(TimeWindows. ofSizeWithNoGrace(Duration. ofHours(24)))
.reduce((oldV,newV) -> oldV) // first wins
.toStream()
.map((wKey,val) -> KeyValue. pair(wKey. key(), val));
15. 4 Flink (stan kluczowy + TTL, pseudo)
java
ValueState<Boolean> seen;
env. enableCheckpointing(10000);
onEvent(e):
if (!seen.value()) { process(e); seen. update(true); }
15. Brama 5 NGINX/API (Idempotency-Key na krawędzi)
nginx map $http_idempotency_key $idkey { default ""; }
Proxy the key to the backend; backend solves deadup (Inbox/Redis).
16) FAQ
P: Co wybrać: deadup lub czysta idempotencja?
Odp.: Zwykle zarówno: deadup jest szybkim „filtrem” (oszczędności), idempotencja jest gwarancją prawidłowego efektu.
P: Który TTL umieścić?
Odp.: ≥ maksymalny możliwy czas ponownej dostawy + inwentaryzacja. Zazwyczaj 24-72 godziny; dla zadań finansowych i odroczonych - dni/tygodnie.
P: Jak radzisz sobie z późnymi wydarzeniami?
A: Skonfigurować 'dozwolone opóźnienie' i 'late _ event' alarm'; później - przez oddzielną gałąź (recompute/skip).
P: Czy cały strumień telemetryczny może zostać zdublowany?
Odp.: Tak, przybliżone filtry (Bloom) na krawędzi, ale rozważyć FP i nie mają zastosowania do krytycznych efektów biznesowych.
P: Deadup staje na drodze zasypki?
Odp.: Oddzielne klucze ('klucz # batch2025') lub wyłączyć barierę na czas zasypki; Klucze TTL powinny obejmować tylko okna online.
17) Kwoty całkowite
Deduplikacja to kompozycja: odpowiedni klucz, struktura okna i stanu + wzorce transakcyjne (Inbox/Outbox/UPSERT) oraz świadome obsługiwanie zamówień i późnych zdarzeń. Umieść bariery, gdzie jest najtańszy, zapewnić idempotencję w siniakach, zmierzyć 'dedup _ hit _ rate' i powtórki testowe/niepowodzenia - w ten sposób otrzymasz „skutecznie dokładnie raz” bez zbędnych ogonów opóźnienia i kosztów.