Apache Flink · FLK-07
Process functions — timers, keyed state & broadcast rules
- 5 h
- requires FLK-06
From your world. a dynamic business rule applied to a session of events, where the rule can change over time and must be applied as of each event
Session timeout + fraud rule with broadcast rule updates
Build a fraud-detection job. The job groups the transactions of each account into sessions. A session ends after a gap of inactivity. Within each session, the job flags transactions above the fraud threshold in force at the time of the transaction. The threshold changes over time and arrives on a broadcast rule stream. So the job must apply the rule as of each event. It must not apply the latest rule.
Implement de.flk.FraudSessions:
public static final long GAP = 5000L; // session gap, in event-time millis
public static Map<String, long[]> run(
List<String[]> events, // {account, amtMinor, tsMillis}
List<String[]> rules) // {thresholdMinor, effFromMillis}
throws Exception
Semantics:
- Sessionize per account. Events of an account that are within
GAPof each other (by event time) belong to one session. A gap>= GAPwith no event starts a new session. The start of a session is the timestamp of its first event. - Flag as-of. A transaction is flagged if its
amtMinoris strictly greater than the fraudthresholdMinor. Use the rule whoseeffFromMillisis the greatest value<= the transaction's tsMillis(the as-of rule). If no rule is effective yet, the transaction is not flagged. - Emit on session timeout. A session ends when an event-time timer at
last_event_ts + GAPfires. Then emit the session summary. Return a map keyed by"account@sessionStartTs". The value islong[]{eventCount, flaggedCount}for that session.
Suggested approach (DataStream, broadcast state)
- Key the events by account. Call
broadcast(ruleDescriptor)on the rule stream. Useconnecton both streams and use aKeyedBroadcastProcessFunction. processBroadcastElementstores each rule version in broadcast state (aMapState<Long effFrom, Long threshold>).processElementbuffers the event in keyedListStateand registers an event-time timer atevent_ts + GAP. It also deletes the previously registered timer, so only the timer of the latest event fires.onTimercollects the buffered events that belong to the session that ends now. It applies the as-of threshold to each event and counts the flagged events. Then it emits the summary and clears the state of the session.
Why the starter fails
The starter returns an empty map. It must sessionize, apply the as-of threshold, and emit on the session-timeout timer. Evaluation against the "current" broadcast value, and not the as-of version, is both wrong and non-deterministic.
Starter code
package de.flk;
import java.util.*;
/**
* Starter: not implemented. Returns an empty map so the tests fail (G1).
*
* Build a KeyedBroadcastProcessFunction that sessionizes each account's events
* (gap = GAP), applies the fraud threshold in force AS OF each event's time
* (greatest effFrom <= event ts), and emits a per-session summary on the
* session-timeout (event-time) timer.
*/
public class FraudSessions {
public static final long GAP = 5000L;
public static Map<String, long[]> run(List<String[]> events, List<String[]> rules)
throws Exception {
// TODO: broadcast the rules, buffer events in keyed state, drive on
// event-time timers, and emit (eventCount, flaggedCount) per session.
return new LinkedHashMap<>();
}
}
Hints
Hint 1
Use a KeyedBroadcastProcessFunction keyed by account. Store the fraud-rule versions in broadcast state (processBroadcastElement) and buffer each event in keyed ListState; drive everything with event-time timers.
Hint 2
Session timeout: track the session's events and register an event-time timer at event_ts + GAP, deleting the previous timer so only the latest event's timer fires. When onTimer fires, the session is complete.
Hint 3
Apply the rule as-of each event: the threshold is the rule version with the greatest effFrom <= the event's ts. Always using the newest threshold, or the live 'current' broadcast value, is the bug (and is non-deterministic).
uv run de start FLK-07/01-fraud-sessions
# edit workspace/FLK-07/01-fraud-sessions/
uv run de test