Complete Pipeline
Copy pipeline YAML
Review it for your environment before you deploy.
This pipeline combines all deduplication techniques from this tutorial:
- Hash-based deduplication - Use content hash to detect exact duplicates
- Fingerprint deduplication - Detect near-duplicates with key field matching
- ID-based deduplication - Use event_id for idempotent processing
- Time-windowed dedup - Limit cache comparison to the configured time window
Full Configuration
Complete pipeline
deduplicate-events.yaml
name: deduplicate-events-complete
type: pipeline
description: Complete Event Deduplication Pipeline - Detects and filters duplicate events using cache-based deduplication.
namespace: production
labels:
category: data-transformation
pattern: deduplication
config:
cache_resources:
- label: dedup_cache
memory:
default_ttl: 1h
input:
http_server:
address: '0.0.0.0:8080'
path: /events
allowed_verbs: ['POST']
pipeline:
threads: 1
processors:
# Step 1: Parse and validate
- mapping: root = content().parse_json()
- mapping: |
root = this
root.received_at = now()
# Validate required fields
root = if !this.exists("event_id") {
throw("Missing required field: event_id")
} else {
this
}
# Step 2: Generate deduplication key
- mapping: |
root = this
# Create composite key for deduplication
# Options: event_id only, or event_id + type + timestamp window
# Composite: event_type + source + 1-minute window
root.dedup_key = if this.dedup_strategy.or("event_id") == "composite" {
this.event_type.or("unknown") + ":" + this.source.or("unknown") + ":" + (this.timestamp.or(now()).ts_unix() / 60).floor().string()
} else {
# Simple: just event_id
this.event_id
}
meta dedup_key = root.dedup_key
- group_by_value:
value: '${! this.dedup_key }'
- mapping: |
root = this
meta batch_duplicate = batch_index() > 0
# Steps 3-5 run serially per message so duplicate keys in the same input
# batch cannot race between the cache lookup and write.
- for_each:
- branch:
request_map: root = this.dedup_key
processors:
- cache:
resource: dedup_cache
operator: exists
key: '${! content() }'
result_map: |
root.is_duplicate = meta("batch_duplicate").bool() || content().string().bool()
meta cache_result = content().string()
meta is_duplicate = root.is_duplicate
- catch:
- mapping: |
root = this
root.is_duplicate = false
meta cache_result = ""
meta is_duplicate = false
- mapping: |
root = this
root.dedup_result = {
"is_duplicate": this.is_duplicate,
"dedup_key": meta("dedup_key"),
"checked_at": now()
}
root.duplicate_metadata = if this.is_duplicate {
{
"detected_at": now(),
"original_hash": this.dedup_key,
"strategy": this.dedup_strategy.or("event_id")
}
} else { deleted() }
- switch:
- check: '!this.is_duplicate'
processors:
- cache:
resource: dedup_cache
operator: set
key: '${! meta("dedup_key") }'
value: '${! json("received_at") }'
output:
switch:
cases:
- check: '!this.is_duplicate'
output:
kafka:
addresses: ['${KAFKA_BROKERS:localhost:9092}']
topic: unique-events
batching:
count: 100
period: 5s
- output:
file:
path: "/var/expanso/duplicates/${!timestamp_unix_date('2006-01-02')}/dupes.jsonl"
codec: lines
logger:
level: INFO
format: json
metrics:
prometheus:
path: /metrics
Quick Test
# Send an event
curl -X POST http://localhost:8080/events \
-H "Content-Type: application/json" \
-d '{"event_id": "evt_001", "data": "hello"}'
# Send the same event again (will be deduplicated)
curl -X POST http://localhost:8080/events \
-H "Content-Type: application/json" \
-d '{"event_id": "evt_001", "data": "hello"}'
# Intended result: the repeated key is dropped
Deploy
# Deploy to Expanso orchestrator
expanso-cli job deploy deduplicate-events.yaml
# Or run locally with expanso-edge
expanso-edge run --config deduplicate-events.yaml
Download
Download deduplicate-events.yaml
What's Next?
- Troubleshooting - Common issues and solutions
- Normalize Timestamps - Standardize time formats