Matt Zimmer
Flink Forward Berlin
12 September | 2017
Custom, Complex Windows
at Scale Using Apache Flink
@zimmermatt
Agenda.
● Motivating Use Cases.
● Window Requirements.
● The Solution (Conceptual).
● Event Processing Flow.
● Apache Flink Window API Walk-Through.
● The Solution (Detail).
● Pitfalls to Watch Out For.
● Alternative Implementations.
● Questions.
@zimmermatt
● Motivating Use Cases.
● Window Requirements.
● The Solution (Conceptual).
● Event Processing Flow.
● Apache Flink Window API Walk-Through.
● The Solution in (Detail).
● Pitfalls to Watch Out For.
● Alternative Implementations.
● Questions.
@zimmermatt
@zimmermatt
@zimmermatt
Use Case: Most Simple.
@zimmermatt
Use Case: Most Simple.
@zimmermatt
Use Case: More Complex.
@zimmermatt
Use Case: More Complex.
@zimmermatt
Use Case: Most Complex.
@zimmermatt
Use Case: Most Complex.
● Events
○ Millions per second
○ 100s billions per day
● Data Flowing In
○ 10s of GiB per second
○ Low (single digit) PiB per day
● State
○ 10s of TiB
Targeted Scale.
@zimmermatt
● Motivating Use Cases.
● Window Requirements.
● The Solution (Conceptual).
● Event Processing Flow.
● Apache Flink Window API Walk-Through.
● The Solution in (Detail).
● Pitfalls to Watch Out For.
● Alternative Implementations.
● Questions.
@zimmermatt
● Unaligned windows
● Bounded by event type
● Handle out of order events
● Emit early results
● Capture relevant events; ignore the rest
Window Requirements.
@zimmermatt
Can we use a standard
window type?
@zimmermatt
@zimmermatt
Tumbling Window?
@zimmermatt
Sliding Window?
@zimmermatt
Sliding Window?
@zimmermatt
Sliding Window?
@zimmermatt
Sliding Window?
@zimmermatt
Sliding Window?
@zimmermatt
Apache Beam Session Window?
@zimmermatt
Apache Beam Session Window?
@zimmermatt
Apache Beam Session Window?
@zimmermatt
Apache Beam Session Window?
@zimmermatt
Apache Beam Session Window?
@zimmermatt
Apache Beam Session Window?
@zimmermatt
Apache Beam Session Window?
● Motivating Use Cases.
● Window Requirements.
● The Solution (Conceptual).
● Event Processing Flow.
● Apache Flink Window API Walk-Through.
● The Solution in (Detail).
● Pitfalls to Watch Out For.
● Alternative Implementations.
● Questions.
@zimmermatt
● Unaligned windows
● Bounded by event type
● Handle out of order events
● Emit early results
● Capture relevant events; ignore the rest
@zimmermatt
Window Requirements Redux.
The solution
at 10,000 feet.
@zimmermatt
The solution
at 3,048 feet meters.
@zimmermatt
x
@zimmermatt
The Solution (Conceptual).
Time
User A
User B
User C
t4
t3
t1
t2
t5
t3
t1
t2
t5
@zimmermatt
Time
User A
User B
User C
t4
t3
t1
t2
t5
t3
t1
t2
t5
The Solution (Conceptual).
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
The Solution (Conceptual).
Time
User A
t4
t3
t1
t2
t5
Watermark
● Motivating Use Cases.
● Window Requirements.
● The Solution (Conceptual).
● Event Processing Flow.
● Apache Flink Window API Walk-Through.
● The Solution in (Detail).
● Pitfalls to Watch Out For.
● Alternative Implementations.
● Questions.
@zimmermatt
1. Window assigner.
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
a. Element (onElement).
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
a. Element (onElement).
b. Merge (onMerge).
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
a. Element (onElement).
b. Merge (onMerge).
3. Trigger Timers.
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
a. Element (onElement).
b. Merge (onMerge).
3. Trigger Timers.
a. Processing Time (onProcessingTime).
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
a. Element (onElement).
b. Merge (onMerge).
3. Trigger Timers.
a. Processing Time (onProcessingTime).
b. Event Time (onEventTime).
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
a. Element (onElement).
b. Merge (onMerge).
3. Trigger Timers.
a. Processing Time (onProcessingTime).
b. Event Time (onEventTime).
4. Evictor.
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
a. Element (onElement).
b. Merge (onMerge).
3. Trigger Timers.
a. Processing Time (onProcessingTime).
b. Event Time (onEventTime).
4. Evictor.
a. Before (evictBefore).
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
a. Element (onElement).
b. Merge (onMerge).
3. Trigger Timers.
a. Processing Time (onProcessingTime).
b. Event Time (onEventTime).
4. Evictor.
a. Before (evictBefore).
b. Evaluate Window (WindowFunction#apply).
@zimmermatt
Event processing flow.
1. Window assigner.
a. Assign Event to Window(s) (assignWindows).
b. Merge Windows (mergeWindows).
2. Trigger Handlers.
a. Element (onElement).
b. Merge (onMerge).
3. Trigger Timers.
a. Processing Time (onProcessingTime).
b. Event Time (onEventTime).
4. Evictor.
a. Before (evictBefore).
b. Evaluate Window (WindowFunction#apply).
c. After (evictAfter).
@zimmermatt
Event processing flow.
● Motivating Use Cases.
● Window Requirements.
● The Solution (Conceptual).
● Event Processing Flow.
● Apache Flink Window API Walk-Through.
● The Solution in (Detail).
● Pitfalls to Watch Out For.
● Alternative Implementations.
● Questions.
@zimmermatt
@zimmermatt
Window API: Window.
package org.apache.flink.streaming.api.windowing.windows;
public abstract class Window {
public abstract long maxTimestamp();
}
* If you implement Window, you’ll need to provide a TypeSerializer implementation for it.
package org.apache.flink.streaming.api.windowing.assigners;
public abstract class WindowAssigner<T, W extends Window> implements Serializable {
public abstract Collection<W> assignWindows(T element,
long timestamp,
WindowAssignerContext context);
public abstract Trigger<T, W> getDefaultTrigger(StreamExecutionEnvironment env);
public abstract TypeSerializer<W> getWindowSerializer(ExecutionConfig executionConfig);
public abstract boolean isEventTime();
public abstract static class WindowAssignerContext {
public abstract long getCurrentProcessingTime();
}
}
@zimmermatt
Window API: WindowAssigner.
@zimmermatt
Window API: MergingWindowAssigner.
package org.apache.flink.streaming.api.windowing.assigners;
public abstract class MergingWindowAssigner<T, W extends Window>
extends WindowAssigner<T, W> {
public abstract void mergeWindows(Collection<W> windows,
MergeCallback<W> callback);
public interface MergeCallback<W> {
void merge(Collection<W> toBeMerged, W mergeResult);
}
}
@zimmermatt
Window API: Trigger.
package org.apache.flink.streaming.api.windowing.triggers;
public abstract class Trigger<T, W extends Window> implements Serializable {
...
public abstract TriggerResult onElement(T element,
long timestamp,
W window,
TriggerContext ctx) throws Exception;
public boolean canMerge() { return false; }
public void onMerge(W window,
OnMergeContext ctx) throws Exception { throws by default }
...
}
@zimmermatt
Window API: Trigger.
package org.apache.flink.streaming.api.windowing.triggers;
public abstract class Trigger<T, W extends Window> implements Serializable {
...
public abstract TriggerResult onProcessingTime(long time,
W window,
TriggerContext ctx) throws Exception;
public abstract TriggerResult onEventTime(long time,
W window,
TriggerContext ctx) throws Exception;
...
}
@zimmermatt
Window API: Trigger.
package org.apache.flink.streaming.api.windowing.triggers;
public abstract class Trigger<T, W extends Window> implements Serializable {
...
public abstract void clear(W window, TriggerContext ctx) throws Exception;
public interface TriggerContext { ... }
public interface OnMergeContext extends TriggerContext { ... }
...
}
@zimmermatt
Window API: Trigger.
package org.apache.flink.streaming.api.windowing.triggers;
public abstract class Trigger<T, W extends Window> implements Serializable {
...
public interface TriggerContext {
long getCurrentProcessingTime();
MetricGroup getMetricGroup();
long getCurrentWatermark();
void registerProcessingTimeTimer(long time);
void registerEventTimeTimer(long time);
void deleteProcessingTimeTimer(long time);
void deleteEventTimeTimer(long time);
<S extends State> S getPartitionedState(StateDescriptor<S, ?> stateDescriptor);
}
public interface OnMergeContext extends TriggerContext {
<S extends MergingState<?, ?>> void mergePartitionedState(StateDescriptor<S, ?> stateDescriptor);
}
}
@zimmermatt
package org.apache.flink.streaming.api.windowing.evictors;
public interface Evictor<T, W extends Window> extends Serializable {
void evictBefore(Iterable<TimestampedValue<T>> elements,
int size,
W window,
EvictorContext evictorContext);
void evictAfter(Iterable<TimestampedValue<T>> elements,
int size,
W window,
EvictorContext evictorContext);
...
}
Window API: Evictor.
@zimmermatt
package org.apache.flink.streaming.api.windowing.evictors;
public interface Evictor<T, W extends Window> extends Serializable {
...
interface EvictorContext {
long getCurrentProcessingTime();
MetricGroup getMetricGroup();
long getCurrentWatermark();
}
}
Window API: Evictor.
The solution
in detail.
@zimmermatt
@zimmermatt
Custom Window: WindowAssigner.
public class CustomWindowAssigner<E extends CustomEvent> extends MergingWindowAssigner<E, CustomWindow<E>> {
...
@Override
public Collection<CustomWindow<E>> assignWindows(E element,
long timestamp,
WindowAssignerContext context) {
return Collections.singletonList(new CustomWindow<>(element, timeoutDuration));
}
...
}
@zimmermatt
Custom Window: WindowAssigner.
public class CustomWindowAssigner<E extends CustomEvent> extends MergingWindowAssigner<E, CustomWindow<E>> {
...
@Override
public void mergeWindows(Collection<CustomWindow<E>> mergeCandidates,
MergeCallback<CustomWindow<E>> mergeCallback) {
final CustomWindow<E> sessionWindow = calculateSessionWindow(mergeCandidates);
final Collection<CustomWindow<E>> inWindow = filterWithinWindow(mergeCandidates);
// MergeCallback#merge implementation expects 2 or more.
if (inWindow.size() > 1) {
mergeCallback.merge(inWindow, sessionWindow);
}
}
...
}
@zimmermatt
Custom Window: Window.
public class CustomWindow<E extends CustomEvent> extends Window {
...
@Override
public long maxTimestamp() {
return maxTimestamp;
}
...
}
@zimmermatt
Custom Window: Window.
public class CustomWindow<E extends CustomEvent> extends Window {
...
@Override
public boolean equals(Object o) {
// Important: equals implementation must compare using “value” semantics
}
@Override
public int hashCode() {
// Important: same for hashCode implementation
}
...
}
@zimmermatt
Custom Window: Window.
public class CustomWindow<E extends CustomEvent> extends Window {
...
public static class Serializer<T extends CustomEvent>
extends TypeSerializer<CustomWindow<T>> {
...
}
...
}
@zimmermatt
Custom Window: Window.
public class CustomWindow<E extends CustomEvent> extends Window {
...
public static class Serializer<T extends CustomEvent>
extends TypeSerializer<CustomWindow<T>> {
@Override
public boolean isImmutableType() { return true; }
...
}
...
}
public class CustomWindow<E extends CustomEvent> extends Window {
...
public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> {
...
@Override
public TypeSerializer<CustomWindow<T>> duplicate() { return this; }
@Override
public CustomWindow<T> createInstance() { return null; }
@Override
public CustomWindow<T> copy(CustomWindow<T> from) { return from; }
@Override
public CustomWindow<T> copy(CustomWindow<T> from, CustomWindow<T> reuse) { return from; }
@Override
public int getLength() { return -1; }
}
...
}
@zimmermatt
Custom Window: Window.
@zimmermatt
public class CustomWindow<E extends CustomEvent> extends Window {
...
public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> {
...
public void serialize(CustomWindow<T> record, DataOutputView target)
throws IOException {
serializeStartEvent(record, target);
target.writeLong(record.getDuration().toMillis());
target.writeBoolean(record.evaluate());
final boolean hasEndEventData = record.getEndEventData() != null;
target.writeBoolean(hasEndEventData);
if (hasEndEventData) serializeEndEvent(record, target);
}
}
...
}
Custom Window: Window.
@zimmermatt
public class CustomWindow<E extends CustomEvent> extends Window {
...
public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> {
...
@Override
public CustomWindow<T> deserialize(DataInputView source) throws IOException {
final T startEvent = deserializeStartEvent(source);
final Duration duration = Duration.ofMillis(source.readLong());
final boolean evaluate = source.readBoolean();
final boolean hasEndEventData = source.readBoolean();
final T endEvent = hasEndEventData ? deserializeEndEvent(source) : null;
return new CustomWindow<>(startEvent, duration, endEvent, evaluate);
}
}
...
}
Custom Window: Window.
@zimmermatt
public class CustomWindow<E extends CustomEvent> extends Window {
...
public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> {
...
@Override
public CustomWindow<T> deserialize(CustomWindow<T> reuse,
DataInputView source) throws IOException {
return reuse != null ? reuse : deserialize(source);
}
}
...
}
Custom Window: Window.
@zimmermatt
public class CustomWindow<E extends CustomEvent> extends Window {
...
public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> {
...
@Override
public void copy(DataInputView source, DataOutputView target) throws IOException {
// slightly less efficient, but more maintainable
CustomWindow<T> deserializedWindow = deserialize(source);
serialize(deserializedWindow, target);
}
}
...
}
Custom Window: Window.
@zimmermatt
public class CustomWindow<E extends CustomEvent> extends Window {
...
public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> {
...
@Override
public boolean equals(Object obj) { return obj instanceof Serializer; }
@Override
public boolean canEqual(Object obj) { return obj instanceof Serializer; }
@Override
public int hashCode() { return 0; }
}
...
}
Custom Window: Window.
@zimmermatt
public class CustomWindow<E extends CustomEvent> extends Window {
...
public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> {
...
@Override
public TypeSerializerConfigSnapshot snapshotConfiguration() { ... }
@Override
public CompatibilityResult<CustomWindow<T>> ensureCompatibility(
TypeSerializerConfigSnapshot configSnapshot) {
return CompatibilityResult.requiresMigration();
}
private static class CustomWindowSerializerConfigSnapshot extends TypeSerializerConfigSnapshot {
...
}
}
...
}
Custom Window: Window.
@zimmermatt
Custom Window: Window.
public class CustomWindow<E extends CustomEvent> extends Window {
…
public EventWindow(@Nonnull D primaryEventData,
@Nonnull Duration timeoutDuration,
D endEventData,
boolean evaluate) {
...
this.endTimestamp = endEventData != null ?
endEventData.getTimestamp() : maxTimestamp;
...
}
...
public boolean evaluate() { return evaluate; }
public Instant startTimestamp() { return primaryEventData.getTimestamp(); }
public Instant endTimestamp() { return endTimestamp; }
}
...
}
@zimmermatt
Custom Window: WindowAssigner.
public class CustomWindowAssigner<E extends CustomEvent> extends MergingWindowAssigner<E, CustomWindow<E>> {
...
private CustomWindow<E> calculateSessionWindow(Collection<CustomWindow<E>> mergeCandidates) {
CustomWindow<E> startEventWindow = findStartEventWindow(mergeCandidates);
if (startEventWindow != null) { // valid window
…
} else { // exploratory window
...
}
}
...
}
@zimmermatt
Custom Window: WindowAssigner.
if (startEventWindow != null) { // valid window
CustomWindow<E> endEvent = findEndEventWindow(mergeCandidates); // can return null
return new CustomWindow<>(startEventWindow.getEvent, timeoutDuration, endEvent,
true); // fire (send this one to the WindowFunction)
} else { // exploratory window
...
}
@zimmermatt
Custom Window: WindowAssigner.
if (startEventWindow != null) { // valid window
...
} else { // exploratory window
CustomWindow<E> window = findClosestToMidpointByStartTime(mergeCandidates);
return new CustomWindow(window.getEvent, exploratoryDuration,
false) // just purge without firing
}
@zimmermatt
Custom Window: WindowAssigner.
public class CustomWindowAssigner<E extends CustomEvent> extends MergingWindowAssigner<E, CustomWindow<E>> {
...
@Override
public void mergeWindows(Collection<CustomWindow<E>> mergeCandidates,
MergeCallback<CustomWindow<E>> mergeCallback) {
final CustomWindow<E> sessionWindow = calculateSessionWindow(mergeCandidates);
final Collection<CustomWindow<E>> inWindow = filterWithinWindow(mergeCandidates);
// MergeCallback#merge implementation expects 2 or more.
if (inWindow.size() > 1) {
mergeCallback.merge(inWindow, sessionWindow);
}
}
...
}
@zimmermatt
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
Time
User A
t4
t3
t1
t2
t5
Watermark
@zimmermatt
Time
User A
t4
t3
t1
t2
t5
Watermark
public class CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> {
...
@Override
public boolean canMerge() { return true; }
@Override
public void onMerge(CustomWindow<E> window, OnMergeContext onMergeContext)
throws Exception {
onMergeContext.registerEventTimeTimer(window.endTimestamp().toEpochMilli());
}
...
}
@zimmermatt
Custom Window: Trigger.
@zimmermatt
Custom Window: Trigger.
public class CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> {
...
@Override
public TriggerResult onElement(E element, long timestamp, CustomWindow<E> window,
TriggerContext triggerContext) throws Exception {
final TriggerResult triggerResult;
final ValueState<Boolean> windowClosedState =
triggerContext.getPartitionedState(windowClosedDescriptor);
final long endTimestamp = window.endTimestamp().toEpochMilli();
if (triggerContext.getCurrentWatermark() >= endTimestamp) {
triggerResult = windowClosedState.value() ? TriggerResult.CONTINUE
: triggerWindow(triggerContext, windowClosedState, window);
} else {
...
}
return triggerResult;
}
...
}
@zimmermatt
Custom Window: Trigger.
public class CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> {
...
private TriggerResult triggerWindow(TriggerContext triggerContext,
ValueState<Boolean> windowClosedState,
CustomWindow<E> window) throws IOException {
windowClosedState.update(Boolean.TRUE);
removeEarlyFiringTimer(triggerContext);
return window.evaluate() ? TriggerResult.FIRE_AND_PURGE : TriggerResult.PURGE;
}
private void removeEarlyFiringTimer(TriggerContext triggerContext) throws IOException {
final ValueState<Long> earlyFiringState =
triggerContext.getPartitionedState(earlyFiringDescriptor);
if (earlyFiringState.value() > 0) {
triggerContext.deleteProcessingTimeTimer(earlyFiringState.value());
// set to -1L to differentiate from the default value
earlyFiringState.update(-1L);
}
}
...
}
@zimmermatt
Custom Window: Trigger.
public class CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> {
...
@Override
public TriggerResult onElement(E element, long timestamp, CustomWindow<E> window,
TriggerContext triggerContext) throws Exception {
final TriggerResult triggerResult;
final long endTimestamp = window.endTimestamp().toEpochMilli();
final ValueState<Boolean> windowClosedState =
triggerContext.getPartitionedState(windowClosedDescriptor);
if ...
} else {
windowClosedState.update(Boolean.FALSE);
triggerResult = TriggerResult.CONTINUE;
triggerContext.registerEventTimeTimer(endTimestamp);
registerEarlyFiringTimerIfNecessary(window, triggerContext);
}
return triggerResult;
}
...
}
@zimmermatt
Custom Window: Trigger.
public class CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> {
...
private void registerEarlyFiringTimerIfNecessary(CustomWindow<E> window,
TriggerContext triggerContext)
throws IOException {
if (!window.evaluate() || earlyFiringInterval.toMillis() < 1) return;
final ValueState<Long> earlyFiringState = triggerContext.getPartitionedState(earlyFiringDescriptor);
if (earlyFiringState.value() == Long.MIN_VALUE) {
final Long newEarlyFiringTimestamp = System.currentTimeMillis() + earlyFiringInterval.toMillis();
if (newEarlyFiringTimestamp < window.endTimestamp().toEpochMilli()) {
triggerContext.registerProcessingTimeTimer(newEarlyFiringTimestamp);
earlyFiringState.update(newEarlyFiringTimestamp);
}
}
}
...
}
@zimmermatt
final ValueState<Long> earlyFiringState =
triggerContext.getPartitionedState(earlyFiringDescriptor);
if (earlyFiringState.value() == Long.MIN_VALUE) {
final Long newEarlyFiringTimestamp =
System.currentTimeMillis() + earlyFiringInterval.toMillis();
if (newEarlyFiringTimestamp < window.endTimestamp().toEpochMilli()) {
triggerContext.registerProcessingTimeTimer(newEarlyFiringTimestamp);
earlyFiringState.update(newEarlyFiringTimestamp);
}
Custom Window: Trigger.
@zimmermatt
Custom Window: Trigger.
public class CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> {
...
@Override
public TriggerResult onEventTime(long time,
CustomWindow<E> window,
TriggerContext triggerContext) throws Exception {
if (time != window.endTimestamp().toEpochMilli()) {
return TriggerResult.CONTINUE;
}
final ValueState<Boolean> windowClosedState =
triggerContext.getPartitionedState(windowClosedDescriptor);
if (windowClosedState.value()) {
return TriggerResult.CONTINUE;
}
return triggerWindow(triggerContext, windowClosedState, window);
}
...
}
@zimmermatt
Custom Window: Trigger.
public class CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> {
...
@Override
public TriggerResult onEventTime(long time,
CustomWindow<E> window,
TriggerContext triggerContext) throws Exception {
if (time != window.endTimestamp().toEpochMilli()) {
return TriggerResult.CONTINUE;
}
final ValueState<Boolean> windowClosedState =
triggerContext.getPartitionedState(windowClosedDescriptor);
if (windowClosedState.value()) {
return TriggerResult.CONTINUE;
}
return triggerWindow(triggerContext, windowClosedState, window);
}
...
}
@zimmermatt
Custom Window: Trigger.
public class CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> {
...
@Override
public TriggerResult onProcessingTime(long time,
CustomWindow<E> window,
TriggerContext triggerContext) throws Exception {
TriggerResult triggerResult = TriggerResult.CONTINUE;
if (window.evaluate()) {
...
}
return triggerResult;
}
...
}
@zimmermatt
Custom Window: Trigger.
if (window.evaluate()) { // Update early firing
final ValueState<Long> earlyFiringState =
triggerContext.getPartitionedState(earlyFiringDescriptor);
final Long newEarlyFiringTimestamp =
earlyFiringState.value() + earlyFiringInterval.toMillis();
if (newEarlyFiringTimestamp < window.endTimestamp().toEpochMilli()) {
triggerContext.registerProcessingTimeTimer(newEarlyFiringTimestamp);
earlyFiringState.update(newEarlyFiringTimestamp);
}
triggerResult = TriggerResult.FIRE;
}
return triggerResult;
@zimmermatt
● Motivating Use Cases.
● Window Requirements.
● The Solution (Conceptual).
● Event Processing Flow.
● Apache Flink Window API Walk-Through.
● The Solution (Detail).
● Pitfalls to Watch Out For.
● Alternative Implementations.
● Questions.
Pitfall:
@zimmermatt
Window equals / hashCode.
Pitfall:
@zimmermatt
Metrics and Logs.
Pitfall:
@zimmermatt
Event Design.
Pitfall:
@zimmermatt
Large State.
@zimmermatt
● Motivating Use Cases.
● Window Requirements.
● The Solution (Conceptual).
● Event Processing Flow.
● Apache Flink Window API Walk-Through.
● The Solution (Detail).
● Pitfalls to Watch Out For.
● Alternative Implementations.
● Questions.
Alternative:
@zimmermatt
ProcessFunction
@zimmermatt
● Motivating Use Cases.
● Window Requirements.
● The Solution (Conceptual).
● Event Processing Flow.
● Apache Flink Window API Walk-Through.
● The Solution (Detail).
● Pitfalls to Watch Out For.
● Alternative Implementations.
● Questions.
Thank You!
@zimmermatt

Flink Forward Berlin 2017: Matt Zimmer - Custom, Complex Windows at Scale Using Apache Flink

  • 1.
    Matt Zimmer Flink ForwardBerlin 12 September | 2017 Custom, Complex Windows at Scale Using Apache Flink @zimmermatt
  • 2.
    Agenda. ● Motivating UseCases. ● Window Requirements. ● The Solution (Conceptual). ● Event Processing Flow. ● Apache Flink Window API Walk-Through. ● The Solution (Detail). ● Pitfalls to Watch Out For. ● Alternative Implementations. ● Questions. @zimmermatt
  • 3.
    ● Motivating UseCases. ● Window Requirements. ● The Solution (Conceptual). ● Event Processing Flow. ● Apache Flink Window API Walk-Through. ● The Solution in (Detail). ● Pitfalls to Watch Out For. ● Alternative Implementations. ● Questions. @zimmermatt
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
    ● Events ○ Millionsper second ○ 100s billions per day ● Data Flowing In ○ 10s of GiB per second ○ Low (single digit) PiB per day ● State ○ 10s of TiB Targeted Scale. @zimmermatt
  • 12.
    ● Motivating UseCases. ● Window Requirements. ● The Solution (Conceptual). ● Event Processing Flow. ● Apache Flink Window API Walk-Through. ● The Solution in (Detail). ● Pitfalls to Watch Out For. ● Alternative Implementations. ● Questions. @zimmermatt
  • 13.
    ● Unaligned windows ●Bounded by event type ● Handle out of order events ● Emit early results ● Capture relevant events; ignore the rest Window Requirements. @zimmermatt
  • 14.
    Can we usea standard window type? @zimmermatt
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
    ● Motivating UseCases. ● Window Requirements. ● The Solution (Conceptual). ● Event Processing Flow. ● Apache Flink Window API Walk-Through. ● The Solution in (Detail). ● Pitfalls to Watch Out For. ● Alternative Implementations. ● Questions. @zimmermatt
  • 29.
    ● Unaligned windows ●Bounded by event type ● Handle out of order events ● Emit early results ● Capture relevant events; ignore the rest @zimmermatt Window Requirements Redux.
  • 30.
    The solution at 10,000feet. @zimmermatt
  • 31.
    The solution at 3,048feet meters. @zimmermatt x
  • 32.
    @zimmermatt The Solution (Conceptual). Time UserA User B User C t4 t3 t1 t2 t5 t3 t1 t2 t5
  • 33.
    @zimmermatt Time User A User B UserC t4 t3 t1 t2 t5 t3 t1 t2 t5 The Solution (Conceptual).
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
    ● Motivating UseCases. ● Window Requirements. ● The Solution (Conceptual). ● Event Processing Flow. ● Apache Flink Window API Walk-Through. ● The Solution in (Detail). ● Pitfalls to Watch Out For. ● Alternative Implementations. ● Questions. @zimmermatt
  • 47.
  • 48.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). @zimmermatt Event processing flow.
  • 49.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). @zimmermatt Event processing flow.
  • 50.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. @zimmermatt Event processing flow.
  • 51.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. a. Element (onElement). @zimmermatt Event processing flow.
  • 52.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. a. Element (onElement). b. Merge (onMerge). @zimmermatt Event processing flow.
  • 53.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. a. Element (onElement). b. Merge (onMerge). 3. Trigger Timers. @zimmermatt Event processing flow.
  • 54.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. a. Element (onElement). b. Merge (onMerge). 3. Trigger Timers. a. Processing Time (onProcessingTime). @zimmermatt Event processing flow.
  • 55.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. a. Element (onElement). b. Merge (onMerge). 3. Trigger Timers. a. Processing Time (onProcessingTime). b. Event Time (onEventTime). @zimmermatt Event processing flow.
  • 56.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. a. Element (onElement). b. Merge (onMerge). 3. Trigger Timers. a. Processing Time (onProcessingTime). b. Event Time (onEventTime). 4. Evictor. @zimmermatt Event processing flow.
  • 57.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. a. Element (onElement). b. Merge (onMerge). 3. Trigger Timers. a. Processing Time (onProcessingTime). b. Event Time (onEventTime). 4. Evictor. a. Before (evictBefore). @zimmermatt Event processing flow.
  • 58.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. a. Element (onElement). b. Merge (onMerge). 3. Trigger Timers. a. Processing Time (onProcessingTime). b. Event Time (onEventTime). 4. Evictor. a. Before (evictBefore). b. Evaluate Window (WindowFunction#apply). @zimmermatt Event processing flow.
  • 59.
    1. Window assigner. a.Assign Event to Window(s) (assignWindows). b. Merge Windows (mergeWindows). 2. Trigger Handlers. a. Element (onElement). b. Merge (onMerge). 3. Trigger Timers. a. Processing Time (onProcessingTime). b. Event Time (onEventTime). 4. Evictor. a. Before (evictBefore). b. Evaluate Window (WindowFunction#apply). c. After (evictAfter). @zimmermatt Event processing flow.
  • 60.
    ● Motivating UseCases. ● Window Requirements. ● The Solution (Conceptual). ● Event Processing Flow. ● Apache Flink Window API Walk-Through. ● The Solution in (Detail). ● Pitfalls to Watch Out For. ● Alternative Implementations. ● Questions. @zimmermatt
  • 61.
    @zimmermatt Window API: Window. packageorg.apache.flink.streaming.api.windowing.windows; public abstract class Window { public abstract long maxTimestamp(); } * If you implement Window, you’ll need to provide a TypeSerializer implementation for it.
  • 62.
    package org.apache.flink.streaming.api.windowing.assigners; public abstractclass WindowAssigner<T, W extends Window> implements Serializable { public abstract Collection<W> assignWindows(T element, long timestamp, WindowAssignerContext context); public abstract Trigger<T, W> getDefaultTrigger(StreamExecutionEnvironment env); public abstract TypeSerializer<W> getWindowSerializer(ExecutionConfig executionConfig); public abstract boolean isEventTime(); public abstract static class WindowAssignerContext { public abstract long getCurrentProcessingTime(); } } @zimmermatt Window API: WindowAssigner.
  • 63.
    @zimmermatt Window API: MergingWindowAssigner. packageorg.apache.flink.streaming.api.windowing.assigners; public abstract class MergingWindowAssigner<T, W extends Window> extends WindowAssigner<T, W> { public abstract void mergeWindows(Collection<W> windows, MergeCallback<W> callback); public interface MergeCallback<W> { void merge(Collection<W> toBeMerged, W mergeResult); } }
  • 64.
    @zimmermatt Window API: Trigger. packageorg.apache.flink.streaming.api.windowing.triggers; public abstract class Trigger<T, W extends Window> implements Serializable { ... public abstract TriggerResult onElement(T element, long timestamp, W window, TriggerContext ctx) throws Exception; public boolean canMerge() { return false; } public void onMerge(W window, OnMergeContext ctx) throws Exception { throws by default } ... }
  • 65.
    @zimmermatt Window API: Trigger. packageorg.apache.flink.streaming.api.windowing.triggers; public abstract class Trigger<T, W extends Window> implements Serializable { ... public abstract TriggerResult onProcessingTime(long time, W window, TriggerContext ctx) throws Exception; public abstract TriggerResult onEventTime(long time, W window, TriggerContext ctx) throws Exception; ... }
  • 66.
    @zimmermatt Window API: Trigger. packageorg.apache.flink.streaming.api.windowing.triggers; public abstract class Trigger<T, W extends Window> implements Serializable { ... public abstract void clear(W window, TriggerContext ctx) throws Exception; public interface TriggerContext { ... } public interface OnMergeContext extends TriggerContext { ... } ... }
  • 67.
    @zimmermatt Window API: Trigger. packageorg.apache.flink.streaming.api.windowing.triggers; public abstract class Trigger<T, W extends Window> implements Serializable { ... public interface TriggerContext { long getCurrentProcessingTime(); MetricGroup getMetricGroup(); long getCurrentWatermark(); void registerProcessingTimeTimer(long time); void registerEventTimeTimer(long time); void deleteProcessingTimeTimer(long time); void deleteEventTimeTimer(long time); <S extends State> S getPartitionedState(StateDescriptor<S, ?> stateDescriptor); } public interface OnMergeContext extends TriggerContext { <S extends MergingState<?, ?>> void mergePartitionedState(StateDescriptor<S, ?> stateDescriptor); } }
  • 68.
    @zimmermatt package org.apache.flink.streaming.api.windowing.evictors; public interfaceEvictor<T, W extends Window> extends Serializable { void evictBefore(Iterable<TimestampedValue<T>> elements, int size, W window, EvictorContext evictorContext); void evictAfter(Iterable<TimestampedValue<T>> elements, int size, W window, EvictorContext evictorContext); ... } Window API: Evictor.
  • 69.
    @zimmermatt package org.apache.flink.streaming.api.windowing.evictors; public interfaceEvictor<T, W extends Window> extends Serializable { ... interface EvictorContext { long getCurrentProcessingTime(); MetricGroup getMetricGroup(); long getCurrentWatermark(); } } Window API: Evictor.
  • 70.
  • 71.
    @zimmermatt Custom Window: WindowAssigner. publicclass CustomWindowAssigner<E extends CustomEvent> extends MergingWindowAssigner<E, CustomWindow<E>> { ... @Override public Collection<CustomWindow<E>> assignWindows(E element, long timestamp, WindowAssignerContext context) { return Collections.singletonList(new CustomWindow<>(element, timeoutDuration)); } ... }
  • 72.
    @zimmermatt Custom Window: WindowAssigner. publicclass CustomWindowAssigner<E extends CustomEvent> extends MergingWindowAssigner<E, CustomWindow<E>> { ... @Override public void mergeWindows(Collection<CustomWindow<E>> mergeCandidates, MergeCallback<CustomWindow<E>> mergeCallback) { final CustomWindow<E> sessionWindow = calculateSessionWindow(mergeCandidates); final Collection<CustomWindow<E>> inWindow = filterWithinWindow(mergeCandidates); // MergeCallback#merge implementation expects 2 or more. if (inWindow.size() > 1) { mergeCallback.merge(inWindow, sessionWindow); } } ... }
  • 73.
    @zimmermatt Custom Window: Window. publicclass CustomWindow<E extends CustomEvent> extends Window { ... @Override public long maxTimestamp() { return maxTimestamp; } ... }
  • 74.
    @zimmermatt Custom Window: Window. publicclass CustomWindow<E extends CustomEvent> extends Window { ... @Override public boolean equals(Object o) { // Important: equals implementation must compare using “value” semantics } @Override public int hashCode() { // Important: same for hashCode implementation } ... }
  • 75.
    @zimmermatt Custom Window: Window. publicclass CustomWindow<E extends CustomEvent> extends Window { ... public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> { ... } ... }
  • 76.
    @zimmermatt Custom Window: Window. publicclass CustomWindow<E extends CustomEvent> extends Window { ... public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> { @Override public boolean isImmutableType() { return true; } ... } ... }
  • 77.
    public class CustomWindow<Eextends CustomEvent> extends Window { ... public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> { ... @Override public TypeSerializer<CustomWindow<T>> duplicate() { return this; } @Override public CustomWindow<T> createInstance() { return null; } @Override public CustomWindow<T> copy(CustomWindow<T> from) { return from; } @Override public CustomWindow<T> copy(CustomWindow<T> from, CustomWindow<T> reuse) { return from; } @Override public int getLength() { return -1; } } ... } @zimmermatt Custom Window: Window.
  • 78.
    @zimmermatt public class CustomWindow<Eextends CustomEvent> extends Window { ... public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> { ... public void serialize(CustomWindow<T> record, DataOutputView target) throws IOException { serializeStartEvent(record, target); target.writeLong(record.getDuration().toMillis()); target.writeBoolean(record.evaluate()); final boolean hasEndEventData = record.getEndEventData() != null; target.writeBoolean(hasEndEventData); if (hasEndEventData) serializeEndEvent(record, target); } } ... } Custom Window: Window.
  • 79.
    @zimmermatt public class CustomWindow<Eextends CustomEvent> extends Window { ... public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> { ... @Override public CustomWindow<T> deserialize(DataInputView source) throws IOException { final T startEvent = deserializeStartEvent(source); final Duration duration = Duration.ofMillis(source.readLong()); final boolean evaluate = source.readBoolean(); final boolean hasEndEventData = source.readBoolean(); final T endEvent = hasEndEventData ? deserializeEndEvent(source) : null; return new CustomWindow<>(startEvent, duration, endEvent, evaluate); } } ... } Custom Window: Window.
  • 80.
    @zimmermatt public class CustomWindow<Eextends CustomEvent> extends Window { ... public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> { ... @Override public CustomWindow<T> deserialize(CustomWindow<T> reuse, DataInputView source) throws IOException { return reuse != null ? reuse : deserialize(source); } } ... } Custom Window: Window.
  • 81.
    @zimmermatt public class CustomWindow<Eextends CustomEvent> extends Window { ... public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> { ... @Override public void copy(DataInputView source, DataOutputView target) throws IOException { // slightly less efficient, but more maintainable CustomWindow<T> deserializedWindow = deserialize(source); serialize(deserializedWindow, target); } } ... } Custom Window: Window.
  • 82.
    @zimmermatt public class CustomWindow<Eextends CustomEvent> extends Window { ... public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> { ... @Override public boolean equals(Object obj) { return obj instanceof Serializer; } @Override public boolean canEqual(Object obj) { return obj instanceof Serializer; } @Override public int hashCode() { return 0; } } ... } Custom Window: Window.
  • 83.
    @zimmermatt public class CustomWindow<Eextends CustomEvent> extends Window { ... public static class Serializer<T extends CustomEvent> extends TypeSerializer<CustomWindow<T>> { ... @Override public TypeSerializerConfigSnapshot snapshotConfiguration() { ... } @Override public CompatibilityResult<CustomWindow<T>> ensureCompatibility( TypeSerializerConfigSnapshot configSnapshot) { return CompatibilityResult.requiresMigration(); } private static class CustomWindowSerializerConfigSnapshot extends TypeSerializerConfigSnapshot { ... } } ... } Custom Window: Window.
  • 84.
    @zimmermatt Custom Window: Window. publicclass CustomWindow<E extends CustomEvent> extends Window { … public EventWindow(@Nonnull D primaryEventData, @Nonnull Duration timeoutDuration, D endEventData, boolean evaluate) { ... this.endTimestamp = endEventData != null ? endEventData.getTimestamp() : maxTimestamp; ... } ... public boolean evaluate() { return evaluate; } public Instant startTimestamp() { return primaryEventData.getTimestamp(); } public Instant endTimestamp() { return endTimestamp; } } ... }
  • 85.
    @zimmermatt Custom Window: WindowAssigner. publicclass CustomWindowAssigner<E extends CustomEvent> extends MergingWindowAssigner<E, CustomWindow<E>> { ... private CustomWindow<E> calculateSessionWindow(Collection<CustomWindow<E>> mergeCandidates) { CustomWindow<E> startEventWindow = findStartEventWindow(mergeCandidates); if (startEventWindow != null) { // valid window … } else { // exploratory window ... } } ... }
  • 86.
    @zimmermatt Custom Window: WindowAssigner. if(startEventWindow != null) { // valid window CustomWindow<E> endEvent = findEndEventWindow(mergeCandidates); // can return null return new CustomWindow<>(startEventWindow.getEvent, timeoutDuration, endEvent, true); // fire (send this one to the WindowFunction) } else { // exploratory window ... }
  • 87.
    @zimmermatt Custom Window: WindowAssigner. if(startEventWindow != null) { // valid window ... } else { // exploratory window CustomWindow<E> window = findClosestToMidpointByStartTime(mergeCandidates); return new CustomWindow(window.getEvent, exploratoryDuration, false) // just purge without firing }
  • 88.
    @zimmermatt Custom Window: WindowAssigner. publicclass CustomWindowAssigner<E extends CustomEvent> extends MergingWindowAssigner<E, CustomWindow<E>> { ... @Override public void mergeWindows(Collection<CustomWindow<E>> mergeCandidates, MergeCallback<CustomWindow<E>> mergeCallback) { final CustomWindow<E> sessionWindow = calculateSessionWindow(mergeCandidates); final Collection<CustomWindow<E>> inWindow = filterWithinWindow(mergeCandidates); // MergeCallback#merge implementation expects 2 or more. if (inWindow.size() > 1) { mergeCallback.merge(inWindow, sessionWindow); } } ... }
  • 89.
  • 90.
  • 91.
  • 92.
    public class CustomWindowTrigger<Eextends CustomEvent> extends Trigger<E, CustomWindow<E>> { ... @Override public boolean canMerge() { return true; } @Override public void onMerge(CustomWindow<E> window, OnMergeContext onMergeContext) throws Exception { onMergeContext.registerEventTimeTimer(window.endTimestamp().toEpochMilli()); } ... } @zimmermatt Custom Window: Trigger.
  • 93.
    @zimmermatt Custom Window: Trigger. publicclass CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> { ... @Override public TriggerResult onElement(E element, long timestamp, CustomWindow<E> window, TriggerContext triggerContext) throws Exception { final TriggerResult triggerResult; final ValueState<Boolean> windowClosedState = triggerContext.getPartitionedState(windowClosedDescriptor); final long endTimestamp = window.endTimestamp().toEpochMilli(); if (triggerContext.getCurrentWatermark() >= endTimestamp) { triggerResult = windowClosedState.value() ? TriggerResult.CONTINUE : triggerWindow(triggerContext, windowClosedState, window); } else { ... } return triggerResult; } ... }
  • 94.
    @zimmermatt Custom Window: Trigger. publicclass CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> { ... private TriggerResult triggerWindow(TriggerContext triggerContext, ValueState<Boolean> windowClosedState, CustomWindow<E> window) throws IOException { windowClosedState.update(Boolean.TRUE); removeEarlyFiringTimer(triggerContext); return window.evaluate() ? TriggerResult.FIRE_AND_PURGE : TriggerResult.PURGE; } private void removeEarlyFiringTimer(TriggerContext triggerContext) throws IOException { final ValueState<Long> earlyFiringState = triggerContext.getPartitionedState(earlyFiringDescriptor); if (earlyFiringState.value() > 0) { triggerContext.deleteProcessingTimeTimer(earlyFiringState.value()); // set to -1L to differentiate from the default value earlyFiringState.update(-1L); } } ... }
  • 95.
    @zimmermatt Custom Window: Trigger. publicclass CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> { ... @Override public TriggerResult onElement(E element, long timestamp, CustomWindow<E> window, TriggerContext triggerContext) throws Exception { final TriggerResult triggerResult; final long endTimestamp = window.endTimestamp().toEpochMilli(); final ValueState<Boolean> windowClosedState = triggerContext.getPartitionedState(windowClosedDescriptor); if ... } else { windowClosedState.update(Boolean.FALSE); triggerResult = TriggerResult.CONTINUE; triggerContext.registerEventTimeTimer(endTimestamp); registerEarlyFiringTimerIfNecessary(window, triggerContext); } return triggerResult; } ... }
  • 96.
    @zimmermatt Custom Window: Trigger. publicclass CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> { ... private void registerEarlyFiringTimerIfNecessary(CustomWindow<E> window, TriggerContext triggerContext) throws IOException { if (!window.evaluate() || earlyFiringInterval.toMillis() < 1) return; final ValueState<Long> earlyFiringState = triggerContext.getPartitionedState(earlyFiringDescriptor); if (earlyFiringState.value() == Long.MIN_VALUE) { final Long newEarlyFiringTimestamp = System.currentTimeMillis() + earlyFiringInterval.toMillis(); if (newEarlyFiringTimestamp < window.endTimestamp().toEpochMilli()) { triggerContext.registerProcessingTimeTimer(newEarlyFiringTimestamp); earlyFiringState.update(newEarlyFiringTimestamp); } } } ... }
  • 97.
    @zimmermatt final ValueState<Long> earlyFiringState= triggerContext.getPartitionedState(earlyFiringDescriptor); if (earlyFiringState.value() == Long.MIN_VALUE) { final Long newEarlyFiringTimestamp = System.currentTimeMillis() + earlyFiringInterval.toMillis(); if (newEarlyFiringTimestamp < window.endTimestamp().toEpochMilli()) { triggerContext.registerProcessingTimeTimer(newEarlyFiringTimestamp); earlyFiringState.update(newEarlyFiringTimestamp); } Custom Window: Trigger.
  • 98.
    @zimmermatt Custom Window: Trigger. publicclass CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> { ... @Override public TriggerResult onEventTime(long time, CustomWindow<E> window, TriggerContext triggerContext) throws Exception { if (time != window.endTimestamp().toEpochMilli()) { return TriggerResult.CONTINUE; } final ValueState<Boolean> windowClosedState = triggerContext.getPartitionedState(windowClosedDescriptor); if (windowClosedState.value()) { return TriggerResult.CONTINUE; } return triggerWindow(triggerContext, windowClosedState, window); } ... }
  • 99.
    @zimmermatt Custom Window: Trigger. publicclass CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> { ... @Override public TriggerResult onEventTime(long time, CustomWindow<E> window, TriggerContext triggerContext) throws Exception { if (time != window.endTimestamp().toEpochMilli()) { return TriggerResult.CONTINUE; } final ValueState<Boolean> windowClosedState = triggerContext.getPartitionedState(windowClosedDescriptor); if (windowClosedState.value()) { return TriggerResult.CONTINUE; } return triggerWindow(triggerContext, windowClosedState, window); } ... }
  • 100.
    @zimmermatt Custom Window: Trigger. publicclass CustomWindowTrigger<E extends CustomEvent> extends Trigger<E, CustomWindow<E>> { ... @Override public TriggerResult onProcessingTime(long time, CustomWindow<E> window, TriggerContext triggerContext) throws Exception { TriggerResult triggerResult = TriggerResult.CONTINUE; if (window.evaluate()) { ... } return triggerResult; } ... }
  • 101.
    @zimmermatt Custom Window: Trigger. if(window.evaluate()) { // Update early firing final ValueState<Long> earlyFiringState = triggerContext.getPartitionedState(earlyFiringDescriptor); final Long newEarlyFiringTimestamp = earlyFiringState.value() + earlyFiringInterval.toMillis(); if (newEarlyFiringTimestamp < window.endTimestamp().toEpochMilli()) { triggerContext.registerProcessingTimeTimer(newEarlyFiringTimestamp); earlyFiringState.update(newEarlyFiringTimestamp); } triggerResult = TriggerResult.FIRE; } return triggerResult;
  • 102.
    @zimmermatt ● Motivating UseCases. ● Window Requirements. ● The Solution (Conceptual). ● Event Processing Flow. ● Apache Flink Window API Walk-Through. ● The Solution (Detail). ● Pitfalls to Watch Out For. ● Alternative Implementations. ● Questions.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
    @zimmermatt ● Motivating UseCases. ● Window Requirements. ● The Solution (Conceptual). ● Event Processing Flow. ● Apache Flink Window API Walk-Through. ● The Solution (Detail). ● Pitfalls to Watch Out For. ● Alternative Implementations. ● Questions.
  • 108.
  • 109.
    @zimmermatt ● Motivating UseCases. ● Window Requirements. ● The Solution (Conceptual). ● Event Processing Flow. ● Apache Flink Window API Walk-Through. ● The Solution (Detail). ● Pitfalls to Watch Out For. ● Alternative Implementations. ● Questions.
  • 110.