A cautionary tale from the infrastructure side. Our marketing team reported wildly inconsistent CPA across channels, with "Social - Organic" suddenly accounting for 20% of conversions and a massive spend spike. The data pipeline seemed fine, but the business logic was broken.
The root cause was in the UTM parameter ingestion and validation microservice. We parse the `utm_source`, `utm_medium`, and `utm_campaign` from incoming click events. The bug was a simple string comparison that failed to canonicalize values before deduplication in our aggregation layer.
```go
// The problematic logic (simplified)
func bucketCampaign(rawCampaign string) string {
switch rawCampaign {
case "spring_sale", "Spring_Sale", "SPRING_SALE":
return "spring_sale_core"
default:
return rawCampaign // This was the issue
}
}
```
Campaigns "SpringSale" and "spring_sale" were treated as distinct, causing spend to fragment across dozens of "unique" campaign entries in our reporting dashboard. The misallocation obscured the true performance of our paid social efforts.
**The technical breakdown:**
* **Ingestion:** Golang service normalizes and validates UTM params, then publishes to Kafka.
* **Aggregation:** Flink job performs hourly rollups, grouping by normalized params.
* **Storage:** Aggregated data lands in PostgreSQL for reporting.
* **The Bug:** The normalization function had an incomplete mapping list. New campaign variants created by external teams bypassed it, leading to new buckets.
**The fix was twofold:**
1. Implemented a more aggressive normalization (lowercase, strip special chars, predefined alias mapping) in the ingestion service.
2. Added a nightly batch correction job in PostgreSQL to merge historical fragmented buckets.
```sql
-- Example of the correction logic
UPDATE campaign_spend
SET campaign_key = 'spring_sale_core'
WHERE campaign_key IN ('springsale', 'SpringSale2024');
```
Always validate and canonicalize your analytical dimensions at the point of entry, not just in the BI tool. A missing row in a lookup table cost real money.
-- latency
sub-100ms or bust