Оқиғаларды қайталау
1) Неге дедупликация қажет
Дубликаттар ретрациялардың, желілік таймауттардың, фейлден кейін қалпына келтірулердің және тарихи деректердің репликасынан пайда болады. Егер оларды бақыламаса:- инварианттар бұзылады (қосарлы есептен шығару, қайталама email/SMS, «екі рет жасалған» тапсырыс);
- шығындар өседі (қайталама жазбалар/өңдеу);
- талдау бұрмаланады.
Дедупликацияның мақсаты - көлiктiң қайталануына жол берiлетiн бiр реттiк бақыланатын тиiмдiлiктi, көбiнесе iндеттiлiкпен бiрге қамтамасыз ету.
2) Дедупликацияны қайда орналастыру керек (деңгейлер)
1. Edge/API-шлюз - 'Idempotency-Кеу '/денесі + қолы бойынша анық дүңгіршектерді кесіп тастаймыз.
2. Брокер/стрим - кілт/секвенс бойынша логикалық дедупликация, қате кеткен кезде coalescing (сирек - құнына байланысты).
3. Оқиғалар қабылдағышы (consumer) - негізгі орын: Inbox/Кілттер кестесі/Кэш.
4. Синк (ДБ/кэш) - бірегей кілттер/UPSERT/нұсқалар/компакция.
5. ETL/талдау - бағаналы сөрелердегі уақыт терезесі мен кілті бойынша дедуп.
Ереже: мүмкіндігінше ерте, бірақ жалған іске қосылулардың құнын және реплидің қажеттілігін ескере отырып.
3) Дедупликация кілттері
3. 1 Табиғи (дұрыс)
`payment_id`, `order_id`, `saga_id#step`, `aggregate_id#seq`.
Тұрақтылық пен мағынаға кепілдік береді.
3. 2 Құрамдас
`(tenant_id, type, external_id, version)` или `(user_id, event_ts_truncated, payload_hash)`.
3. 3 Таңба (fingerprint)
«HMAC (secret, payload)» опциондық өрістердің детерминирленген кіші жиынының хэштері.
3. 4 дәйектілігі/нұсқасы
Монотонды 'seq' per aggregate (оптимистік бұғаттау/нұсқалау).
Анти-паттерн: «рандомды UUID» бизнес мәнімен байланыссыз - дедуп мүмкін емес.
4) Уақытша терезелер және тәртіп
Дедупликация терезесі - оқиға қайтадан келе алатын кезең (әдетте 24-72 сағат; қаржы үшін - ұзағырақ).
Out-of-order: кешігуге жол береміз (lateness). Ағынды фреймворкаларда - event time + watermarks.
Sliding/Fix-window дедуп: "Соңғы N минутта кілтті көрдіңіз бе? ».
Sequence-aware: егер 'seq' соңғы өңделгеннен ≤ - қайталау/қайталау.
5) Деректер мен іске асыру құрылымдары
5. 1 Нақты есеп (exact)
Redis SET/STRING + TTL: 'SETNX key 1 EX 86400' → «бірінші рет өңдейміз, әйтпесе - SKIP».
LRU/LFU кэш (in-proc): жылдам, бірақ volatile → тек бірінші кедергі ретінде жақсы.
SQL бірегей индекстері + UPSERT: «қою немесе жаңарту» (идемпотенттік әсер).
5. 2 Жақын құрылымдар (probabilistic)
Bloom/Cuckoo filter: арзан жады, жалған іске қосылуы мүмкін (false positive). Қаржы/тапсырыстар үшін емес, анық «шулы» дропқа (мысалы, телеметрия) жарамды.
Count-Min Sketch: «ыстық» қосарланулардан қорғау үшін жиіліктерді бағалау.
5. 3 Ағындық күй
Kafka Streams/Flink: keyed state store c TTL, терезедегі кілт бойынша дедуп; checkpoint/restore.
Watermark + allowed lateness: кешіктірілген оқиғалар терезесін басқарады.
6) Транзакциялық паттерндер
6. 1 Inbox (кіріс кестесі)
'message _ id '/кілтін және нәтижесін жанама әсерлерге дейін сақтаңыз: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;
Қайталау жазбаны көреді және әсер қайталанбайды.
6. 2 Outbox
Бір транзакциядағы бизнес-жазба және оқиға → жариялаушы брокерге береді. Тұтынушыдан қосарларды жоймайды, бірақ «тесіктерді» болдырмайды.
6. 3 Бірегей индекстер/UPSERT
sql
INSERT INTO payments(id, status, amount)
VALUES ($1, $2, $3)
ON CONFLICT (id) DO NOTHING; -- "create once"
немесе бақыланатын нұсқаны жаңарту:
sql
UPDATE orders
SET status = $new, version = version + 1
WHERE id=$id AND version = $expected; -- optimistic blocking
6. 4 Агрегаттарды нұсқалау
Егер 'event' болса, оқиға қолданылады. version = aggregate. version + 1`. Әйтпесе - дубль/қайталау/қақтығыс.
7) Дедуп және брокерлер/стримдер
7. 1 Kafka
Idempotent Producer кіре берістегі екеуін азайтады.
Transactions атомарлық офсеттер + шығыс жазбаларын біріктіруге мүмкіндік береді.
Compaction: соңғы per key мәнін сақтайды - пост-фактум дедуп/коалицесинг (төлемдер үшін емес).
Consumer-side: state store/Redis/DB терезе кілттері үшін.
7. 2 NATS / JetStream
Ack/қайта жеткізу → at-least-once. Тұтынушыдағы дедуп (Inbox/Redis).
JetStream sequence/дюработ қайталауларды анықтауды жеңілдетеді.
7. 3 Кезектер (Rabbit/SQS)
Visibility timeout + қайта жеткізу → кілт + дедуп-стор қажет.
SQS FIFO 'MessageGroupId '/' DeduplicationId' көмегімен көмектеседі, бірақ TTL терезелері провайдермен шектелген - егер бизнес талап етсе, кілттерді ұзақ сақтаңыз.
8) Қоймалар және талдаулар
8. 1 ClickHouse/BigQuery
Терезе бойынша дедуп: 'ORDER BY key, ts' және 'argMax '/' anyLast' шарты бар.
ClickHouse:sql
SELECT key,
anyLast(value) AS v
FROM t
WHERE ts >= now() - INTERVAL 1 DAY
GROUP BY key;
Немесе «бірегей» оқиғалардың материалданған қабаты (кілт/нұсқа бойынша мердж).
8. 2 Логи/телеметрия
Мысалы, approximate-дедуп (Bloom) ingest → желіні/дискіні үнемдейміз.
9) Қайта өңдеу, реплика және бэкфилл
Дедуп-кілттер репликаны бастан кешіруі тиіс (TTL ≥ реплика терезесі).
Бэкфилл үшін онлайн терезеге кедергі келтірмеу үшін ('key #source = batch2025') немесе жеке «өрік» нұсқасы бар кілттер кеңістігін пайдаланыңыз.
Нәтиже артефактілерін (hash/нұсқа) сақтаңыз - бұл қайталауларда «fast-skip» жылдамдатады.
10) Метрика және бақылау
'dedup _ hit _ total '/' dedup _ hit _ rate' - ұсталған дубльдердің үлесі.
Мүмкін сүзгілер үшін 'dedup _ fp _ rate'.
'window _ size _ seconds' нақты (telemetry late arrivals бойынша).
`inbox_conflict_total`, `upsert_conflict_total`.
`replayed_events_total`, `skipped_by_inbox_total`.
tenant/key/type профильдері: қай жерде көбірек және неліктен.
Логи: `message_id`, `idempotency_key`, `seq`, `window_id`, `action=process|skip`.
11) Қауіпсіздік және құпиялылық
PII кілтіне қоймаңыз; хэштерді/бүркеншік атауларды пайдаланыңыз.
Таңбаға қол қою үшін - HMAC (secret, canonical_payload), коллизияларды/қолдан жасауды болдырмау үшін.
Кілттерді сақтау мерзімін комплаенспен (GDPR ретеншн) келісіңіз.
12) Өнімділік және құн
In-proc LRU ≪ Redis ≪ SQL операция жасырындылығы/құны бойынша.
Redis: арзан және жылдам, бірақ кілттер мен TTL көлемін ескеріңіз; 'tenant/hash' бойынша шардалаңыз.
SQL: p99 бойынша қымбат, бірақ күшті кепілдіктер мен аудиторияны береді.
Probabilistic сүзгілері: өте арзан, бірақ мүмкін FP - «артық SKIP» сыни емес жерде қолданыңыз.
13) Қарсы үлгілер
«Бізде Kafka exactly-once - кілт қажет емес». Қажет - синка/бизнес-қабатта.
Кілттер үшін тым қысқа TTL → репликалар/кідіріс қос жеткізеді.
Жаһандық жалғыз дедуп-стор → hotspot және SPOF; tenant/кілт бойынша шардаланбаған.
Дедуп тек жадта - процестің жоғалуы = дубль толқыны.
Ақша/тапсырыстар үшін Bloom - false positive заңды операциядан айырады.
Келісілмеген payload канонизациясы - мағынасы бойынша бірдей хабарламаларға әртүрлі хэштер.
out-of-order елемеу - кеш оқиғалар қате қосарланып белгіленеді.
14) Енгізу чек-парағы
- Табиғи кілтті (немесе құрамдас/таңба) анықтаңыз.
- Дедуп терезесін және 'lateness' саясатын орнатыңыз.
- Деңгейді таңдаңыз: edge, consumer, sink; шардалануды көздеңіз.
- Inbox/UPSERT; ағындар үшін - keyed state + TTL.
- approximate-кедергісі қажет болса - Bloom/Cuckoo (тек критикалық емес домендер үшін).
- Үйлесімділік репликасын теңшеңіз (TTL ≥ реплика/бэкфилл терезесі).
- 'dedup _ hit _ rate' өлшемдері, қайшылықтар мен терезе лагтары; per-tenant дашбордтары.
- Game Day: таймауттар/ретрациялар, реплика, out-of-order, кэштің құлауы.
- Payload канонизациясын және кілттерді нұсқалауды құжаттаңыз.
- «Ыстық кілттер» мен ұзын терезелерде жүктеме сынақтарын өткізіңіз.
15) Конфигурация/код мысалдары
15. 1 Redis SETNX + TTL (тосқауыл)
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 PostgreSQL Inbox
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 Kafka Streams (терезедегі дедуп)
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 (keyed state + TTL, псевдо)
java
ValueState<Boolean> seen;
env. enableCheckpointing(10000);
onEvent(e):
if (!seen.value()) { process(e); seen. update(true); }
15. 5 NGINX/API-шлюз (Idempotency-Key на edge)
nginx map $http_idempotency_key $idkey { default ""; }
Proxy the key to the backend; backend solves deadup (Inbox/Redis).
16) FAQ
Q: Не таңдау керек: дедуп немесе таза идемпотенттік?
A: Әдетте екеуі де: дедуп - жылдам «сүзгі» (үнемдеу), іспеттілік - дұрыс әсердің кепілі.
Q: Қандай TTL қою?
А: ықтимал қайта жеткізудің ең ұзақ уақытының ≥ + қор. Әдеттегідей 24-72с; қаржы және кейінге қалдырылған міндеттер үшін - күндер/апталар.
Q: Кеш оқиғаларды қалай өңдеу керек?
A: 'allowed lateness' және 'late _ event' дабылын баптаңыз; кейінгілері - жеке тармақ арқылы (recompute/skip).
Q: Телеметрияның барлық ағынын дедуплициялауға бола ма?
A: Иә, edge-дегі approximate-сүзгілермен (Bloom), бірақ FP-ді ескеріңіз және сындарлы бизнес-әсерлерге қолданбаңыз.
Q: Дедуп бэкфилге кедергі келтіре ме?
A: Кілттер кеңістігін бөліңіз ('key #batch2025') немесе бэкфилл уақытында кедергіні өшіріңіз; Кілттердің TTL тек онлайн терезелерді жабуы керек.
17) Қорытынды
Дедупликация - бұл композиция: дұрыс кілт, терезе және жай-күй құрылымы + транзакциялық паттерндер (Inbox/Outbox/UPSERT) және ретпен және кешігіп қалған оқиғалармен саналы жұмыс. Тосқауылдарды ең арзан жерге орналастырыңыз, синккадағы іспеттілікті қамтамасыз етіңіз, 'dedup _ hit _ rate' өлшеңіз және репликаны/фейлді тестілеңіз - осылайша сіз «тиімді exactly-once» аласыз.