Deduplicação de eventos
1) Por que precisa de dedução
As duplicações aparecem devido a retais, temporizações de rede, restaurações de feeds e replicações de dados históricos. Se não os controlar:- invariantes são violados (débitos duplos, e-mail repetido/SMS, pedido «duplamente criado»);
- aumentam os custos (redescobrimento/processamento);
- distorce-se o analista.
O objetivo da deduplicação é fornecer um efeito observável uma vez nas repetições de transporte permitidas, muitas vezes juntamente com a idimpotência.
2) Onde colocar a deduplicação (níveis)
1. Edge/API-passarela - Cortando as duplicações explícitas por 'Idempotency-Keu '/corpo + assinatura.
2. Corretor/estrim - Deduplicação lógica por chave/sequência, coalescing por falha (menos por causa do custo).
3. O receptor de eventos (consumer) é o local principal: Inbox/tabela de chaves/dinheiro.
4. Sink (BD/kesh) - chaves exclusivas/UPSERT/versões/compactação.
5. ETL/análise - Deadup por janela de tempo e chave em estojos invertebrados.
Regra: O mais cedo possível, mas tendo em conta o custo das falsas operações e a necessidade de replicar.
3) Chaves de dedução
3. 1 Natural (preferencialmente)
`payment_id`, `order_id`, `saga_id#step`, `aggregate_id#seq`.
Garantem estabilidade e sentido.
3. 2 Compostos
`(tenant_id, type, external_id, version)` или `(user_id, event_ts_truncated, payload_hash)`.
3. 3 Impressão digital (fingerprint)
Hash um subconjunto de campo determinado (normalizar ordem/registros), opcionalmente 'HMAC (segredo, payload)'.
3. 4 Sequências/versões
Monótonos 'seq' per aggregate (bloqueio/versionagem otimista).
Anti-Pattern: «UUID randômica» sem ligação com a entidade empresarial - o dedão é impossível.
4) Janelas de tempo e ordem
A janela de dedução é o período em que o evento pode voltar a ocorrer (normalmente 24-72h; para as finanças - mais tempo).
Out-of-order: vamos atrasar (lateness). Em quadros de streaming - event time + watermarks.
Sliding/Fix-window deadup: "A chave foi vista nos últimos minutos N? ».
Sequence-aware: Se 'seq' ≤ o último processado for capturado/repetido.
5) Estruturas de dados e implementação
5. 1 Contabilidade exata (exact)
REDIS SET/STRING + TTL: 'SETNX key 1 EX 86400' → 'é a primeira vez que processamos, senão SKIP'.
LRU/LFU cash (in-proc): rápido, mas volátil → melhor apenas como a primeira barreira.
SQL Índice + UPSERT único: «Cole ou atualize» (Efeito Idumpotente).
5. 2 Estruturas próximas (protabilistic)
Bloom/Cutkoo filter: memória barata, pode ser um falso acionamento (falso positivo). Adequado para drop «barulhento» explícito (por exemplo, telemetria), não para finanças/pedidos.
Count-Min Sketch: avaliação de frequências para proteção contra suplentes quentes.
5. 3 Estados de streaming
Kafka Streams/Flink: keyed state store com TTL, a chave da janela; checkpoint/restore.
Watermark + allowed lateness: controla a janela de eventos atrasados.
6) Pattern transaccionais
6. 1 Inbox (tabela de entrada)
Salvamos 'mensagem _ id '/chave e resultado para efeitos colaterais: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;
A repetição verá a gravação e não repete o efeito.
6. 2 Outbox
A gravação de negócios e o evento de uma transação é dado por um corretor. Não elimina a tomada do consumidor, mas exclui «buracos».
6. 3 Índices exclusivos/UPSERT
sql
INSERT INTO payments(id, status, amount)
VALUES ($1, $2, $3)
ON CONFLICT (id) DO NOTHING; -- "create once"
ou atualização controlada da versão:
sql
UPDATE orders
SET status = $new, version = version + 1
WHERE id=$id AND version = $expected; -- optimistic blocking
6. 4 Versionização de unidades
O evento é válido se 'event. version = aggregate. version + 1`. Senão, tiragem/repetição/conflito.
7) Deadup e corretores/striptease
7. 1 Kafka
O Idempotent Producer reduz as duplicações na entrada.
As transmissões permitem que os registos de saída sejam transmitidos de forma atômica.
Competition: armazena o último valor per key - pós-faturamento/coligação (não para pagamentos).
Consumer-side: state store/Redis/DB para a janela de chaves.
7. 2 NATS / JetStream
Ack/raro → at-least-once. Deadup no consumidor (Inbox/Redis).
JetStream sequence/durabote do consumidor facilitam a identificação de repetições.
7. 3 Filas (Rabbit/SQS)
Visibility timeout + reaproveitamento → precisa de chave + deadup stor.
O SQS FIFO com 'MessageGroupId '/' DeduplicationId' ajuda, mas as janelas TTL são restritas ao provedor - armazene as chaves por mais tempo se o negócio exigir.
8) Armazéns e análises
8. 1 ClickHouse/BigQuery
"ORDER BY key, ts 'e' argMax '/' anyLast 'com uma condição.
ClickHouse:sql
SELECT key,
anyLast(value) AS v
FROM t
WHERE ts >= now() - INTERVAL 1 DAY
GROUP BY key;
Ou uma camada materializada de eventos exclusivos (merj por chave/versão).
8. 2 Logi/Telemetria
Digamos approximate-deadup (Bloom) em ingest → economizar rede/disco.
9) Reaproveitamento, replica e backphill
As chaves de dedução devem sobreviver a réplicas (TTL ≥ janela de réplica).
Use o espaço das chaves com versão ('key # fonte = batch2025') ou «ameixas» individuais para não interferir com a janela online.
Armazene os artefatos de resultado (hash/versão) - isso acelera «fast-skip» nas repetições.
10) Métricas e observabilidade
'dedup _ hit _ total '/' dedup _ hit _ rate' é a proporção de dublagens capturadas.
'dedup _ fp _ rate' para filtros prováveis.
'window _ size _ secunds' é real (por telemetry late arrivals).
`inbox_conflict_total`, `upsert_conflict_total`.
`replayed_events_total`, `skipped_by_inbox_total`.
Perfis por tenant/key/tipo: onde há mais dublês e porquê.
Логи: `message_id`, `idempotency_key`, `seq`, `window_id`, `action=process|skip`.
11) Segurança e privacidade
Não coloque PII na chave; use hashi/pseudônimos.
Para assinar a impressão digital - HMAC (segredo, canonical _ payload) para evitar conflitos/falsificação.
Mantenha as chaves em sintonia com a compilação (GDPR Retenshn).
12) Desempenho e custo
In-pro LRU ≪ Redis ≪ SQL de latência/custo de operação.
Redis: barato e rápido, mas leve em conta o volume de chaves e TTL; curte por 'tenant/hash'.
SQL: Caro p99, mas oferece fortes garantias e auditoria.
Filtros Propabilistic: muito barato, mas possível FP - aplique onde o'SKIP extra "não é crítico.
13) Anti-pattern
«Nós temos Kafka exactly-once - a chave não é necessária». Preciso de um sink/camada de negócios.
Um TTL curto demais para as chaves de réplica/atraso levará a tomada.
Global deadup store solitário, hotspot e SPOF; não está chardeado por tenant/chave.
A única memória é a perda do processo = onda de duplicação.
Bloom para dinheiro/encomendas - falso positivo vai tirar a operação legítima.
Canonização de payload discordante - hashs diferentes para mensagens idênticas.
Ignorar out-of-order - Eventos recentes são marcados de forma errada.
14) Folha de cheque de implementação
- Identifique a chave natural (ou a impressão digital composta).
- Instale a janela Deadup e a política 'lateness'.
- Selecione o nível (eu): edge, consumer, sink; preveja o charding.
- Implemente o Inbox/UPSERT; para os fluxos - keyed state + TTL.
- Se você precisar de uma barreira approximate - Bloom/Cutkoo (somente para domínios não-ríticos).
- Configure a réplica de compatibilidade (TTL ≥ janela de replay/backphill).
- Métricas 'dedup _ hit _ rate', conflitos e aberturas de janela; dashboards per-tenant.
- Game Day: timeouts/retrações, réplicas, out-of-order, queda de cachê.
- Documente a canonização payload e a versionização das chaves.
- Faça testes de carga em «chaves quentes» e janelas longas.
15) Exemplos de configuração/código
15. 1 Redis SETNX + TTL (barreira)
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 (deadup na janela)
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, pseudo)
java
ValueState<Boolean> seen;
env. enableCheckpointing(10000);
onEvent(e):
if (!seen.value()) { process(e); seen. update(true); }
15. 5 porta de entrada NGINX/API (Idempotency-Key em edge)
nginx map $http_idempotency_key $idkey { default ""; }
Proxy the key to the backend; backend solves deadup (Inbox/Redis).
16) FAQ
O que escolher, o Dedup ou a pura Idempotidade?
A: Normalmente, o deadup é um «filtro» rápido (economizar), a idempotidade é a garantia do efeito correto.
Q: Que tipo de TTL colocar?
A: ≥ o tempo máximo de uma possível nova entrega + reserva. Típicamente 24-72h; para finanças e tarefas adiadas - dias/semanas.
Q: Como processar eventos recentes?
A: Configure 'allowed lateness' e o alarme 'late _ event'; tardias - através de um ramo separado (recompute/skip).
Podemos deduzir todo o fluxo de telemetria?
A: Sim, os filtros approximate (Bloom) em edge, mas leve em conta FP e não aplique a efeitos empresariais críticos.
O Deadup está a atrapalhar o backfill?
A: Divida o espaço das chaves ('key # batch2025') ou desliga a barreira durante o backphill; As chaves TTL devem cobrir apenas janelas online.
17) Resultados
A dedução é uma composição: chave correta, janela e estrutura de estado + pattern transaccionais (Inbox/Outbox/UPSERT) e trabalho consciente com eventos atrasados. Coloque barreiras onde for mais barato, garanta idempotidade em sinos, mede 'dedup _ hit _ rate' e teste réplicas/feels - de modo que você consiga «efetivamente exactly-once» sem mais caudas de latência e custo.