PATTERN Cited by 1 source
KeyedCoProcessFunction over Interval Join¶
Pattern¶
For time-bounded event joins where keys are unique (or very low cardinality per partition), replace Flink's built-in Interval Join with a custom KeyedCoProcessFunction that uses direct ValueState point lookups and RocksDB TTL compaction for state expiry.
Problem¶
Flink's Interval Join is algorithmically suited for partitions with multiple entries — it maintains sorted state with iterators and explicit timer queues for expiration. When each key has exactly one entry (unique IDs), the generic algorithm becomes pathological: - One seek per element (expensive disk positioning for a single-entry read) - Millions of timer registrations that are never amortised - 15–30% CPU consumed on seek overhead alone at high throughput
Solution structure¶
Stream A (bids) ──┐
├─ keyBy(bidId) → KeyedCoProcessFunction
Stream B (clicks) ┘
├── ValueState<BidEvent> bidState
├── ValueState<ClickEvent> clickState
└── No timers — TTL compaction handles expiry
On each element: 1. Point-get the other stream's state for this key 2. If found → emit match, clear both states 3. If not found → store this element in its own ValueState
State expiry: RocksDB TTL configured to the join window duration; background compaction removes expired entries.
Seen in¶
- Zalando Ad Platform (2026-07): 15-minute join window, ~200 MB/s, bid IDs unique. Impact: subtasks 30–60 → 10 max, memory 150–300 GB → 50 GB, CPU 30–60 → 10 cores for the join operator (Source: sources/2026-07-23-zalando-from-homegrown-to-flink-migrating-a-stateful-ad-event-join).
When to use¶
- Keys are unique or very low-cardinality per partition
- High throughput where per-element overhead compounds
- You have Java/Kotlin expertise to implement and maintain custom join logic
When NOT to use¶
- Many-to-many joins where iterator-based matching is genuinely needed
- Low throughput where Interval Join's overhead is negligible
- Teams without Flink internals knowledge for debugging custom state logic
Related patterns¶
- patterns/stream-union-plus-keyed-process-function — same primitive (KeyedProcessFunction), different shape (N-way union into single processor vs 2-way connected streams)
- patterns/single-valuestate-over-chained-joins — flattening multiple join operators into one state object