Flink missing windows generated on some partitions - apache-flink
I am trying to write a small Flink dataflow to understand more how it works and I am facing a strange situation where each time I run it, I am getting inconsistent outputs. Sometimes some records that I am expecting are missing. Keep in mind this is just a toy example I am building to learn the concepts of the DataStream API.
I have a dataset of around 7600 rows in CSV format like that look like this:
Date,Country,City,Specie,count,min,max,median,variance
28/06/2021,GR,Athens,no2,116,0.5,58.9,5.5,2824.39
28/06/2021,GR,Athens,wind-speed,133,0.1,11.2,3,96.69
28/06/2021,GR,Athens,dew,24,14,20,18,35.92
28/06/2021,GR,Athens,temperature,141,24.4,38.4,30.5,123.18
28/06/2021,GR,Athens,pm25,116,34,85,68,702.29
Full dataset here: https://pastebin.com/rknnRnPc
There are no special characters or quotes, so a simple String split will work fine.
The date range for each city spans from 28/06/2021 to 03/10/2021.
I am reading it using the DataStream API:
final DataStream<String> source = env.readTextFile("data.csv");
Each row is mapped to a simple POJO as follows:
public class CityMetric {
private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
private final LocalDate localDate;
private final String country;
private final String city;
private final String reading;
private final int count;
private final double min;
private final double max;
private final double median;
private final double variance;
private CityMetric(LocalDate localDate, String country, String city, String reading, int count, double min, double max, double median, double variance) {
this.localDate = localDate;
this.country = country;
this.city = city;
this.reading = reading;
this.count = count;
this.min = min;
this.max = max;
this.median = median;
this.variance = variance;
}
public static CityMetric fromArray(String[] arr) {
LocalDate date = LocalDate.parse(arr[0], dateFormatter);
int count = Integer.parseInt(arr[4]);
double min = Double.parseDouble(arr[5]);
double max = Double.parseDouble(arr[6]);
double median = Double.parseDouble(arr[7]);
double variance = Double.parseDouble(arr[8]);
return new CityMetric(date, arr[1], arr[2], arr[3], count, min, max, median, variance);
}
public long getTimestamp() {
return getLocalDate()
.atStartOfDay()
.toInstant(ZoneOffset.UTC)
.toEpochMilli();
}
//getters follow
The records are all in order of date, so I have this to set the event time and watermark:
final WatermarkStrategy<CityMetric> cityMetricWatermarkStrategy =
WatermarkStrategy.<CityMetric>forMonotonousTimestamps() //we know they are sorted by time
.withTimestampAssigner((cityMetric, l) -> cityMetric.getTimestamp());
I have a StreamingFileSink on a Tuple4 to output the date range, city and average:
final StreamingFileSink<Tuple4<LocalDate, LocalDate, String, Double>> fileSink =
StreamingFileSink.forRowFormat(
new Path("airquality"),
new SimpleStringEncoder<Tuple4<LocalDate, LocalDate, String, Double>>("UTF-8"))
.build();
And finally I have the dataflow as follows:
source
.map(s -> s.split(",")) //split the CSV row into its fields
.filter(arr -> !arr[0].startsWith("Date")) // if it starts with Date it means it is the top header
.map(CityMetric::fromArray) //create the object from the fields
.assignTimestampsAndWatermarks(cityMetricWatermarkStrategy) // we use the date as the event time
.filter(cm -> cm.getReading().equals("pm25")) // we want air quality of fine particulate matter pm2.5
.keyBy(CityMetric::getCity) // partition by city name
.window(TumblingEventTimeWindows.of(Time.days(7))) //windows of 7 days
.aggregate(new CityAverageAggregate()) // average the values
.name("cityair")
.addSink(fileSink); //output each partition to a file
The CityAverageAggregate just accumulates the sum and count, and keeps track of the earliest and latest dates of the range it is covering.
public class CityAverageAggregate
implements AggregateFunction<
CityMetric, CityAverageAggregate.AverageAccumulator, Tuple4<LocalDate, LocalDate, String, Double>> {
#Override
public AverageAccumulator createAccumulator() {
return new AverageAccumulator();
}
#Override
public AverageAccumulator add(CityMetric cityMetric, AverageAccumulator averageAccumulator) {
return averageAccumulator.add(
cityMetric.getCity(), cityMetric.getLocalDate(), cityMetric.getMedian());
}
#Override
public Tuple4<LocalDate, LocalDate, String, Double> getResult(
AverageAccumulator averageAccumulator) {
return Tuple4.of(
averageAccumulator.getStart(),
averageAccumulator.getEnd(),
averageAccumulator.getCity(),
averageAccumulator.average());
}
#Override
public AverageAccumulator merge(AverageAccumulator acc1, AverageAccumulator acc2) {
return acc1.merge(acc2);
}
public static class AverageAccumulator {
private final String city;
private final LocalDate start;
private final LocalDate end;
private final long count;
private final double sum;
public AverageAccumulator() {
city = "";
count = 0;
sum = 0;
start = null;
end = null;
}
AverageAccumulator(String city, LocalDate start, LocalDate end, long count, double sum) {
this.city = city;
this.count = count;
this.sum = sum;
this.start = start;
this.end = end;
}
public AverageAccumulator add(String city, LocalDate eventDate, double value) {
//make sure our dataflow is correct and we are summing data from the same city
if (!this.city.equals("") && !this.city.equals(city)) {
throw new IllegalArgumentException(city + " does not match " + this.city);
}
return new AverageAccumulator(
city,
earliest(this.start, eventDate),
latest(this.end, eventDate),
this.count + 1,
this.sum + value);
}
public AverageAccumulator merge(AverageAccumulator that) {
LocalDate mergedStart = earliest(this.start, that.start);
LocalDate mergedEnd = latest(this.end, that.end);
return new AverageAccumulator(
this.city, mergedStart, mergedEnd, this.count + that.count, this.sum + that.sum);
}
private LocalDate earliest(LocalDate d1, LocalDate d2) {
if (d1 == null) {
return d2;
} else if (d2 == null) {
return d1;
} else {
return d1.isBefore(d2) ? d1 : d2;
}
}
private LocalDate latest(LocalDate d1, LocalDate d2) {
if (d1 == null) {
return d2;
} else if (d2 == null) {
return d1;
} else {
return d1.isAfter(d2) ? d1 : d2;
}
}
public double average() {
return sum / count;
}
public String getCity() {
return city;
}
public LocalDate getStart() {
return start;
}
public LocalDate getEnd() {
return end;
}
}
}
Problem:
The problem I am facing is that sometimes I do not get all the windows I am expecting. This does not always happen, sometimes consecutive runs output a different result, so I am suspecting there is some race condition somewhere.
For example, in one of the partition file output I sometimes get:
(2021-07-12,2021-07-14,Belgrade,56.666666666666664)
(2021-07-15,2021-07-21,Belgrade,56.0)
(2021-07-22,2021-07-28,Belgrade,57.285714285714285)
(2021-07-29,2021-08-04,Belgrade,43.57142857142857)
(2021-08-05,2021-08-11,Belgrade,35.42857142857143)
(2021-08-12,2021-08-18,Belgrade,43.42857142857143)
(2021-08-19,2021-08-25,Belgrade,36.857142857142854)
(2021-08-26,2021-09-01,Belgrade,50.285714285714285)
(2021-09-02,2021-09-08,Belgrade,46.285714285714285)
(2021-09-09,2021-09-15,Belgrade,54.857142857142854)
(2021-09-16,2021-09-22,Belgrade,56.714285714285715)
(2021-09-23,2021-09-29,Belgrade,59.285714285714285)
(2021-09-30,2021-10-03,Belgrade,61.5)
While sometimes I get the full set:
(2021-06-28,2021-06-30,Belgrade,48.666666666666664)
(2021-07-01,2021-07-07,Belgrade,41.142857142857146)
(2021-07-08,2021-07-14,Belgrade,52.857142857142854)
(2021-07-15,2021-07-21,Belgrade,56.0)
(2021-07-22,2021-07-28,Belgrade,57.285714285714285)
(2021-07-29,2021-08-04,Belgrade,43.57142857142857)
(2021-08-05,2021-08-11,Belgrade,35.42857142857143)
(2021-08-12,2021-08-18,Belgrade,43.42857142857143)
(2021-08-19,2021-08-25,Belgrade,36.857142857142854)
(2021-08-26,2021-09-01,Belgrade,50.285714285714285)
(2021-09-02,2021-09-08,Belgrade,46.285714285714285)
(2021-09-09,2021-09-15,Belgrade,54.857142857142854)
(2021-09-16,2021-09-22,Belgrade,56.714285714285715)
(2021-09-23,2021-09-29,Belgrade,59.285714285714285)
(2021-09-30,2021-10-03,Belgrade,61.5)
Is there anything evidently wrong in my dataflow pipeline? Can't figure out why this would happen. It doesn't always happen on the same city either.
What could be happening?
UPDATE
So it seems that when I disabled Watermarks the problem didn't happen any more. I changed the WatermarkStrategy to the following:
final WatermarkStrategy<CityMetric> cityMetricWatermarkStrategy =
WatermarkStrategy.<CityMetric>noWatermarks()
.withTimestampAssigner((cityMetric, l) -> cityMetric.getTimestamp());
And so far I have been getting consistent results. When I checked the documentation it says that:
static WatermarkStrategy noWatermarks()
Creates a watermark strategy that generates no watermarks at all. This may be useful in scenarios that do pure processing-time based stream processing.
But I am not doing processing-time based stream processing, I am doing event-time processing.
Why would forMonotonousTimestamps() have the strange behaviour I was seeing? Indeed my timestamps are monotonically increasing (the noWatermarks strategy wouldn't work if they weren't), but somehow changing this does not work well with my scenario.
Is there anything I am missing with the way things work in Flink?
Flink doesn't support per-key watermarking. Each parallel task generates watermarks independently, based on observing all of the events flowing through that task.
So the reason this isn't working with the forMonotonousTimestamps watermark strategy is that the input is not actually in order by timestamp. It is temporally sorted within each city, but not globally. This is then going to result in some records being late, but unpredictably so, depending on exactly when watermarks are generated. These late events are being ignored by the windows that should contain them.
You can address this in a number of ways:
(1) Use a forBoundedOutOfOrderness watermark strategy with a duration sufficient to account for the actual out-of-order-ness in the dataset. Given that the data looks something like this:
03/10/2021,GR,Athens,pressure,60,1017.9,1040.6,1020.9,542.4
28/06/2021,US,Atlanta,co,24,1.4,7.3,2.2,19.05
that will require an out-of-order-ness duration of approximately 100 days.
(2) Configure the windows to have sufficient allowed lateness. This will result in some of the windows being triggered multiple times -- once when the watermark indicates they can close, and again each time a late event is added to the window.
(3) Use the noWatermarks strategy. This will lead to the job only producing results if and when it reaches the end of its input file(s). For a continuous streaming job this wouldn't be workable, but for finite (bounded) inputs this can work.
(4) Run the job in RuntimeExecutionMode.BATCH mode. Then the job will only produce results at the end, after having consumed all of its input. This will run the job with a more optimized runtime designed for batch workloads, but the outcome should be the same as with (3).
(5) Change the input so it isn't out-of-order.
Related
Busy time is too high for simple process function
Finally, after a month of research I found the main reason. The main reason was IP2Location. I am using IP2Location java library to search ip address location in the BIN files. In the peak time, it causes a problem. At least i can avoid to problem by passing IP2Proxy.IOModes.IP2PROXY_MEMORY_MAPPED parameter before reading the bin files. And also I just found that a few state object doesn't match with POJO standard which causes high load. I am using flink v1.13, there are 4 task managers (per 16 cpu) with 3800 tasks (default application parallelism is 28) In my application one operator has always high busy time (around %80 - %90). If I restart the flink application, then busy time decreases, but after 5-10 hours running busy time increases again. In the grafana, I can see that busy time for ProcessStream increases. Here is the PromethuesQuery: avg((avg_over_time(flink_taskmanager_job_task_busyTimeMsPerSecond[1m]))) by (task_name) There is no backpressure in the ProcessStream task. To calculate backPressure time, I am using: flink_taskmanager_job_task_backPressuredTimeMsPerSecond But I couldn't find any reason for that. Here is the code : private void processOne(DataStream<KafkaObject> kafkaLog) { kafkaLog .filter(new FilterRequest()) .name(FilterRequest.class.getSimpleName()) .map(new MapToUserIdAndTimeStampMs()) .name(MapToUserIdAndTimeStampMs.class.getSimpleName()) .keyBy(UserObject::getUserId) // returns of type int .process(new ProcessStream()) .name(ProcessStream.class.getSimpleName()) .addSink(...) ; } // ... // ... public class ProcessStream extends KeyedProcessFunction<Integer, UserObject, Output> { private static final long STATE_TIMER = // 5 min in milliseconds; private static final int AVERAGE_REQUEST = 74; private static final int STANDARD_DEVIATION = 32; private static final int MINIMUM_REQUEST = 50; private static final int THRESHOLD = 70; private transient ValueState<Tuple2<Integer, Integer>> state; #Override public void open(Configuration parameters) throws Exception { ValueStateDescriptor<Tuple2<Integer, Integer>> stateDescriptor = new ValueStateDescriptor<Tuple2<Integer, Integer>>( ProcessStream.class.getSimpleName(), TypeInformation.of(new TypeHint<Tuple2<Integer, Integer>>() {})); state = getRuntimeContext().getState(stateDescriptor); } #Override public void processElement(UserObject value, KeyedProcessFunction<Integer, UserObject, Output>.Context ctx, Collector<Output> out) throws Exception { Tuple2<Integer, Integer> stateValue = state.value(); if (Objects.isNull(stateValue)) { stateValue = Tuple2.of(1, 0); ctx.timerService().registerProcessingTimeTimer(value.getTimestampMs() + STATE_TIMER); } int totalRequest = stateValue.f0; int currentScore = stateValue.f1; if (totalRequest >= MINIMUM_REQUEST && currentScore >= THRESHOLD) { out.collect({convert_to_output}); state.clear(); } else { stateValue.f0 = totalRequest + 1; stateValue.f1 = calculateNextScore(stateValue.f0); state.update(stateValue); } } private int calculateNextScore(int totalRequest) { return (totalRequest - AVERAGE_REQUEST ) / STANDARD_DEVIATION; } #Override public void onTimer(long timestamp, KeyedProcessFunction<Integer, UserObject, Output>.OnTimerContext ctx, Collector<Output> out) throws Exception { state.clear(); } }
Since you're using a timestamp value from your incoming record (value.getTimestampMs() + STATE_TIMER), you want to be running with event time, and setting watermarks based on that incoming record's timestamp. Otherwise you have no idea when the timer is actually firing, as the record's timestamp might be something completely different than your current processor time. This means you also want to use .registerEventTimeTimer(). Without these changes you might be filling up TM heap with uncleared state, which can lead to high CPU load.
Flink window function getResult not fired
I am trying to use event time in my Flink job, and using BoundedOutOfOrdernessTimestampExtractor to extract timestamp and generate watermark. But I have some input Kafka having sparse stream, it can have no data for a long time, which makes the getResult in AggregateFunction not called at all. I can see data going into add function. I have set getEnv().getConfig().setAutoWatermarkInterval(1000L); I tried eventsWithKey .keyBy(entry -> (String) entry.get(key)) .window(TumblingEventTimeWindows.of(Time.minutes(windowInMinutes))) .allowedLateness(WINDOW_LATENESS) .aggregate(new CountTask(basicMetricTags, windowInMinutes)) also session window eventsWithKey .keyBy(entry -> (String) entry.get(key)) .window(EventTimeSessionWindows.withGap(Time.seconds(30))) .aggregate(new CountTask(basicMetricTags, windowInMinutes)) All the watermark metics shows No Watermark How can I let Flink to ignore that no watermark thing?
FYI, this is commonly referred to as the "idle source" problem. This occurs because whenever a Flink operator has two or more inputs, its watermark is the minimum of the watermarks from its inputs. If one of those inputs stalls, its watermark no longer advances. Note that Flink does not have per-key watermarking -- a given operator is typically multiplexed across events for many keys. So long as some events are flowing through a given task's input streams, its watermark will advance, and event time timers for idle keys will still fire. For this "idle source" problem to occur, a task has to have an input stream that has become completely idle. If you can arrange for it, the best solution is to have your data sources include keepalive events. This will allow you to advance your watermarks with confidence, knowing that the source is simply idle, rather than, for example, offline. If that's not possible, and if you have some sources that aren't idle, then you could put a rebalance() in front of the BoundedOutOfOrdernessTimestampExtractor (and before the keyBy), so that every instance continues to receive some events and can advance its watermark. This comes at the expense of an extra network shuffle. Perhaps the most commonly used solution is to use a watermark generator that detects idleness and artificially advances the watermark based on a processing time timer. ProcessingTimeTrailingBoundedOutOfOrdernessTimestampExtractor is an example of that.
A new watermark with idleness capability has been introduced. Flink will ignore these idle watermarks while calculating the minimum so the single partition with the data will be considered. https://ci.apache.org/projects/flink/flink-docs-release-1.11/api/java/org/apache/flink/api/common/eventtime/WatermarksWithIdleness.html
I have the same issue - a src that may be inactive for a long time. The solution below is based on WatermarksWithIdleness. It is a standalone Flink job that demonstrate the concept. package com.demo.playground.flink.sleepysrc; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.api.common.eventtime.WatermarksWithIdleness; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.datastream.KeyedStream; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.datastream.WindowedStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.source.SourceFunction; import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction; import org.apache.flink.streaming.api.windowing.assigners.EventTimeSessionWindows; import org.apache.flink.streaming.api.windowing.time.Time; import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import org.apache.flink.util.Collector; import java.time.Duration; public class SleepyJob { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); final EventGenerator eventGenerator = new EventGenerator(); WatermarkStrategy<Event> strategy = WatermarkStrategy. <Event>forBoundedOutOfOrderness(Duration.ofSeconds(5)). withIdleness(Duration.ofSeconds(Constants.IDLE_TIME_SEC)). withTimestampAssigner((event, timestamp) -> event.timestamp); final DataStream<Event> events = env.addSource(eventGenerator).assignTimestampsAndWatermarks(strategy); KeyedStream<Event, String> eventStringKeyedStream = events.keyBy((Event event) -> event.id); WindowedStream<Event, String, TimeWindow> windowedStream = eventStringKeyedStream.window(EventTimeSessionWindows.withGap(Time.milliseconds(Constants.SESSION_WINDOW_GAP))); windowedStream.allowedLateness(Time.milliseconds(1000)); SingleOutputStreamOperator<Object> result = windowedStream.process(new ProcessWindowFunction<Event, Object, String, TimeWindow>() { #Override public void process(String s, Context context, Iterable<Event> events, Collector<Object> collector) { int counter = 0; for (Event e : events) { Utils.print(++counter + ") inside process: " + e); } Utils.print("--- Process Done ----"); } }); result.print(); env.execute("Sleepy flink src demo"); } private static class Event { public Event(String id) { this.timestamp = System.currentTimeMillis(); this.eventData = "not_important_" + this.timestamp; this.id = id; } #Override public String toString() { return "Event{" + "id=" + id + ", timestamp=" + timestamp + ", eventData='" + eventData + '\'' + '}'; } public String id; public long timestamp; public String eventData; } private static class EventGenerator implements SourceFunction<Event> { #Override public void run(SourceContext<Event> ctx) throws Exception { /** * Here is the sleepy src - after NUM_OF_EVENTS events are collected , the code goes to a SHORT_SLEEP_TIME sleep * We would like to detect this inactivity and FIRE the window */ int counter = 0; while (running) { String id = Long.toString(System.currentTimeMillis()); Utils.print(String.format("Generating %d events with id %s", 2 * Constants.NUM_OF_EVENTS, id)); while (counter < Constants.NUM_OF_EVENTS) { Event event = new Event(id); ctx.collect(event); counter++; Thread.sleep(Constants.VERY_SHORT_SLEEP_TIME); } // here we create a delay: // a time of inactivity where // we would like to FIRE the window Thread.sleep(Constants.SHORT_SLEEP_TIME); counter = 0; while (counter < Constants.NUM_OF_EVENTS) { Event event = new Event(id); ctx.collect(event); counter++; Thread.sleep(Constants.VERY_SHORT_SLEEP_TIME); } Thread.sleep(Constants.LONG_SLEEP_TIME); } } #Override public void cancel() { this.running = false; } private volatile boolean running = true; } private static final class Constants { public static final int VERY_SHORT_SLEEP_TIME = 300; public static final int SHORT_SLEEP_TIME = 8000; public static final int IDLE_TIME_SEC = 5; public static final int LONG_SLEEP_TIME = SHORT_SLEEP_TIME * 5; public static final long SESSION_WINDOW_GAP = 60 * 1000; public static final int NUM_OF_EVENTS = 4; } private static final class Utils { public static void print(Object obj) { System.out.println(new java.util.Date() + " > " + obj); } } }
For others, make sure there's data coming out of all your topics' partitions if you're using Kafka I know it sounds dumb, but in my case I had a single source and the problem was still happening, because I was testing with very little data in a single Kafka topic (single source) that had 10 partitions. The dataset was so small that some of the topic's partitions did not have anything to give and, although I had only one source (the one topic), Flink did not increase the Watermark. The moment I switched my source to a topic with a single partition the Watermark started to advance.
Dynamic flink window creation by reading the details from kafka
Let's say Kafka messages contain flink window size configuration. I want to read the message from Kafka and create a global window in flink. Problem Statement: Can we handle the above scenario by using BroadcastStream ? Or Any other approach which will support the above case ?
Flink's window API does not support dynamically changing window sizes. What you'll need to do is to implement your own windowing using a process function. In this case a KeyedBroadcastProcessFunction, where the window configuration is broadcast. You can examine the Flink training for an example of how to implement time windows with a KeyedProcessFunction (copied below): public class PseudoWindow extends KeyedProcessFunction<String, KeyedDataPoint<Double>, KeyedDataPoint<Integer>> { // Keyed, managed state, with an entry for each window. // There is a separate MapState object for each sensor. private MapState<Long, Integer> countInWindow; boolean eventTimeProcessing; int durationMsec; /** * Create the KeyedProcessFunction. * #param eventTime whether or not to use event time processing * #param durationMsec window length */ public PseudoWindow(boolean eventTime, int durationMsec) { this.eventTimeProcessing = eventTime; this.durationMsec = durationMsec; } #Override public void open(Configuration config) { MapStateDescriptor<Long, Integer> countDesc = new MapStateDescriptor<>("countInWindow", Long.class, Integer.class); countInWindow = getRuntimeContext().getMapState(countDesc); } #Override public void processElement( KeyedDataPoint<Double> dataPoint, Context ctx, Collector<KeyedDataPoint<Integer>> out) throws Exception { long endOfWindow = setTimer(dataPoint, ctx.timerService()); Integer count = countInWindow.get(endOfWindow); if (count == null) { count = 0; } count += 1; countInWindow.put(endOfWindow, count); } public long setTimer(KeyedDataPoint<Double> dataPoint, TimerService timerService) { long time; if (eventTimeProcessing) { time = dataPoint.getTimeStampMs(); } else { time = System.currentTimeMillis(); } long endOfWindow = (time - (time % durationMsec) + durationMsec - 1); if (eventTimeProcessing) { timerService.registerEventTimeTimer(endOfWindow); } else { timerService.registerProcessingTimeTimer(endOfWindow); } return endOfWindow; } #Override public void onTimer(long timestamp, OnTimerContext context, Collector<KeyedDataPoint<Integer>> out) throws Exception { // Get the timestamp for this timer and use it to look up the count for that window long ts = context.timestamp(); KeyedDataPoint<Integer> result = new KeyedDataPoint<>(context.getCurrentKey(), ts, countInWindow.get(ts)); out.collect(result); countInWindow.remove(timestamp); } }
CEP issue while checkpointing."Could not find id for entry"
When checkpointing is turned on a simple CEP loop pattern private Pattern<Tuple2<Integer, SimpleBinaryEvent>, ?> alertPattern = Pattern.<Tuple2<Integer, SimpleBinaryEvent>>begin("start").where(checkStatusOn) .followedBy("middle").where(checkStatusOn).times(2) .next("end").where(checkStatusOn).within(Time.minutes(5)) I see failures. SimpleBinaryEvent is public class SimpleBinaryEvent implements Serializable { private int id; private int sequence; private boolean status; private long time; public SimpleBinaryEvent(int id, int sequence, boolean status , long time) { this.id = id; this.sequence = sequence; this.status = status; this.time = time; } public int getId() { return id; } public int getSequence() { return sequence; } public boolean isStatus() { return status; } public long getTime() { return time; } #Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SimpleBinaryEvent that = (SimpleBinaryEvent) o; if (getId() != that.getId()) return false; if (isStatus() != that.isStatus()) return false; if (getSequence() != that.getSequence()) return false; return getTime() == that.getTime(); } #Override public int hashCode() { //return Objects.hash(getId(),isStatus(), getSequence(),getTime()); int result = getId(); result = 31 * result + (isStatus() ? 1 : 0); result = 31 * result + getSequence(); result = 31 * result + (int) (getTime() ^ (getTime() >>> 32)); return result; } #Override public String toString() { return "SimpleBinaryEvent{" + "id='" + id + '\'' + ", status=" + status + ", sequence=" + sequence + ", time=" + time + '}'; } } failure cause: Caused by: java.lang.Exception: Could not materialize checkpoint 2 for operator KeyedCEPPatternOperator -> Map (1/1). ... 6 more Caused by: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Could not find id for entry: SharedBufferEntry(ValueTimeWrapper((1,SimpleBinaryEvent{id='1', status=true, sequence=95, time=1505503380000}), 1505503380000, 0),.... I am sure I have the equals() and hashCode() implemented the way it should be. I have tried the Objects.hashCode too. In other instances I have had CircularReference ( and thus stackOverflow ) on SharedBuffer.toString(), which again points to issues with references ( equality and what not ). Without checkpointing turned on it works as expected. I am running on a local cluster. Is CEP production ready ? I am using 1.3.2 Flink
Thanks a lot for trying out the library and reporting this! The library is under active development as more and more features are added to it. The 1.3 was the first release of the library with such rich semantics, so we expect to see 1) how people use it and 2) if there are any bugs. So I would say that it is not 100% production-ready but it is not far. Now for the problem at hand, I suppose you are using RocksDB for checkpointing, right? The reason I am assuming that is that with RocksDB, at each watermark (in event time) you deserialize the necessary state (e.g. the NFA), process some events and then serialize it again before putting it back in RocksDB. This is not the case for the filesystem state backend, where you only serialize the state upon checkpointing and you read it and deserialize it only upon recovery. So in this case, given that you said that without checkpointing your job works fine, you would only see this problem only after recovering from a failure. The root of the problem can be either that equals()/hashcode() is buggy (which does not seem to be the case), or there is a problem on the way we serialize/deserialize the CEP state. Could you also provide a minimal input sequence of events that produce this to happen? This will be really helpful in order to reproduce the problem. Thanks a lot, Kostas
apache-flink KMeans operation on UnsortedGrouping
I have a flink DataSet (read from a file) that contains sensor readings from many different sensors. I use flinks groupBy() method to organize the data as an UnsortedGrouping per sensor. Next, I would like to run the KMeans algorithm on every UnsortedGrouping in my DataSet in a distributed way. My question is, how to efficiently implement this functionality using flink. Below is my current implementation: I have written my own groupReduce() method that applies the flink KMeans algorithm to every UnsortedGrouping. This code works, but seems very slow and uses high amounts of memory. I think this has to do with the amount of data reorganization I have to do. Multiple data conversions have to be performed to make the code run, because I don't know how to do it more efficiently: UnsortedGrouping to Iterable (start of groupReduce() method) Iterable to LinkedList (need this to use the fromCollection() method) LinkedList to DataSet (required as input to KMeans) resulting KMeans DataSet to LinkedList (to be able to iterate for Collector) Surely, there must be a more efficient and performant way to implement this? Can anybody show me how to implement this in a clean and efficient flink way? // ************************************************************************* // VARIABLES // ************************************************************************* static int numberClusters = 10; static int maxIterations = 10; static int sensorCount = 117; static ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); // ************************************************************************* // PROGRAM // ************************************************************************* public static void main(String[] args) throws Exception { final long startTime = System.currentTimeMillis(); String fileName = "C:/tmp/data.nt"; DataSet<String> text = env.readTextFile(fileName); // filter relevant DataSet from text file input UnsortedGrouping<Tuple2<Integer,Point>> points = text .filter(x -> x.contains("Value") && x.contains("valueLiteral")).filter(x -> !x.contains("#string")) .map(x -> new Tuple2<Integer, Point>( Integer.parseInt(x.substring(x.indexOf("_") + 1, x.indexOf(">"))) % sensorCount, new Point(Double.parseDouble(x.split("\"")[1])))) .filter(x -> x.f0 < 10) .groupBy(0); DataSet<Tuple2<Integer, Point>> output = points.reduceGroup(new DistinctReduce()); output.print(); // print the execution time final long endTime = System.currentTimeMillis(); System.out.println("Total execution time: " + (endTime - startTime) + "ms"); } public static class DistinctReduce implements GroupReduceFunction<Tuple2<Integer, Point>, Tuple2<Integer, Point>> { private static final long serialVersionUID = 1L; #Override public void reduce(Iterable<Tuple2<Integer, Point>> in, Collector<Tuple2<Integer, Point>> out) throws Exception { AtomicInteger counter = new AtomicInteger(0); List<Point> pointsList = new LinkedList<Point>(); for (Tuple2<Integer, Point> t : in) { pointsList.add(new Point(t.f1.x)); } DataSet<Point> points = env.fromCollection(pointsList); DataSet<Centroid> centroids = points .distinct() .first(numberClusters) .map(x -> new Centroid(counter.incrementAndGet(), x)); //DataSet<String> test = centroids.map(x -> String.format("Centroid %s", x)); //test.print(); IterativeDataSet<Centroid> loop = centroids.iterate(maxIterations); DataSet<Centroid> newCentroids = points // compute closest centroid for each point .map(new SelectNearestCenter()).withBroadcastSet(loop,"centroids") // count and sum point coordinates for each centroid .map(new CountAppender()) .groupBy(0) .reduce(new CentroidAccumulator()) // compute new centroids from point counts and coordinate sums .map(new CentroidAverager()); // feed new centroids back into next iteration DataSet<Centroid> finalCentroids = loop.closeWith(newCentroids); DataSet<Tuple2<Integer, Point>> clusteredPoints = points // assign points to final clusters .map(new SelectNearestCenter()).withBroadcastSet(finalCentroids, "centroids"); // emit result System.out.println("Results from the KMeans algorithm:"); clusteredPoints.print(); // emit all unique strings. List<Tuple2<Integer, Point>> clusteredPointsList = clusteredPoints.collect(); for(Tuple2<Integer, Point> t : clusteredPointsList) { out.collect(t); } } }
You have to group the data points and the centroids first. Then you iterate over the centroids and co groups them with the data points. For each point in a group you assign it to the closest centroid. Then you group on the initial group index and the centroid index to reduce all points assigned to the same centroid. That will be the result of one iteration. The code could look the following way: DataSet<Tuple2<Integer, Point>> groupedPoints = ... DataSet<Tuple2<Integer, Centroid>> groupCentroids = ... IterativeDataSet<Tuple2<Integer, Centroid>> groupLoop = groupCentroids.iterate(10); DataSet<Tuple2<Integer, Centroid>> newGroupCentroids = groupLoop .coGroup(groupedPoints).where(0).equalTo(0).with(new CoGroupFunction<Tuple2<Integer,Centroid>, Tuple2<Integer,Point>, Tuple4<Integer, Integer, Point, Integer>>() { #Override public void coGroup(Iterable<Tuple2<Integer, Centroid>> centroidsIterable, Iterable<Tuple2<Integer, Point>> points, Collector<Tuple4<Integer, Integer, Point, Integer>> out) throws Exception { // cache centroids List<Tuple2<Integer, Centroid>> centroids = new ArrayList<>(); Iterator<Tuple2<Integer, Centroid>> centroidIterator = centroidsIterable.iterator(); for (Tuple2<Integer, Point> pointTuple : points) { double minDistance = Double.MAX_VALUE; int minIndex = -1; Point point = pointTuple.f1; while (centroidIterator.hasNext()) { centroids.add(centroidIterator.next()); } for (Tuple2<Integer, Centroid> centroidTuple : centroids) { Centroid centroid = centroidTuple.f1; double distance = point.euclideanDistance(centroid); if (distance < minDistance) { minDistance = distance; minIndex = centroid.id; } } out.collect(Tuple4.of(minIndex, pointTuple.f0, point, 1)); } }}) .groupBy(0, 1).reduce(new ReduceFunction<Tuple4<Integer, Integer, Point, Integer>>() { #Override public Tuple4<Integer, Integer, Point, Integer> reduce(Tuple4<Integer, Integer, Point, Integer> value1, Tuple4<Integer, Integer, Point, Integer> value2) throws Exception { return Tuple4.of(value1.f0, value1.f1, value1.f2.add(value2.f2), value1.f3 + value2.f3); } }).map(new MapFunction<Tuple4<Integer,Integer,Point,Integer>, Tuple2<Integer, Centroid>>() { #Override public Tuple2<Integer, Centroid> map(Tuple4<Integer, Integer, Point, Integer> value) throws Exception { return Tuple2.of(value.f1, new Centroid(value.f0, value.f2.div(value.f3))); } }); DataSet<Tuple2<Integer, Centroid>> result = groupLoop.closeWith(newGroupCentroids);