Working with Time-Based Processing
1. Understanding Time Semantics
| Time Type | Description | Use Case |
|---|---|---|
| Event Time | Timestamp when the event occurred in the real world | Ensuring correct ordering and processing of events based on their actual occurrence |
| Ingestion Time | Timestamp when the event was received by the Kafka broker | Simpler processing logic where event order matters less than arrival order |
| Processing Time | Timestamp when the event is processed by a consumer or stream processor | Fast processing without strict time ordering requirements |
Example: Using Event Time in Kafka Streams
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> source = builder.stream("events");
KTable<String, Long> counts = source
.selectKey((key, value) -> value.split(":")[0])
.groupBy((key, value) -> key)
.windowedBy(TimeWindows.of(Duration.ofMinutes(5)))
.count();
2. Understanding Event Time
| Concept | Description | Key Details |
|---|---|---|
| Event Time | Timestamp when the event actually occurred | Used for ordering and processing events based on their real-world occurrence |
| Processing Time | Timestamp when the event is processed by the system | Can lead to incorrect ordering if events arrive out of sequence |
| Ingestion Time | Timestamp when the event is received by the Kafka broker | Between Event Time and Processing Time in terms of accuracy |
Example: Setting Event Time in a Kafka Stream
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.kstream.Produced;
KStream<String, String> inputStream = builder.stream("input-topic");
KStream<String, String> withEventTime = inputStream
.selectKey((key, value) -> {
// Extract timestamp from the event payload
long eventTimestamp = extractTimestamp(value);
return new org.apache.kafka.common.utils.Time().milliseconds();
})
.withTimestamps((key, value, timestamp) -> timestamp);
withEventTime.to("output-topic", Produced.with(Serdes.String(), Serdes.String()));
3. Understanding Processing Time
| Concept | Description | Notes |
|---|---|---|
| Event Time | Timestamp when the event occurred in the source system | Used for ordering and windowing logic |
| Processing Time | Timestamp when the event is processed by Kafka | Based on the system clock of the processing node |
| Ingestion Time | Timestamp when the event is received by Kafka brokers | Between Event Time and Processing Time in terms of freshness |
Example: Processing Time Usage
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.kstream.KStream;
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> source = builder.stream("input-topic");
KStream<String, String> processed = source
.filter((key, value) -> {
// Use processing time for filtering or windowing
long currentTime = System.currentTimeMillis();
return true; // Example logic
});
KafkaStreams streams = new KafkaStreams(builder.build(), config);
streams.start();
4. Understanding Ingestion Time
| Time Type | Description | Usage |
|---|---|---|
| Ingestion Time | Timestamp when a record is received by the Kafka broker | Used for ordering and processing within the broker |
Example: Ingestion Time in Producer Record
ProducerRecord<String, String> record = new ProducerRecord<>(
"my-topic",
"key1",
"message-value"
);
5. Configuring Timestamp Extractors
| Extractor Type | Description | Configuration | Notes |
|---|---|---|---|
| CustomTimestampExtractor | Allows custom logic to extract timestamps from records | |
Useful for legacy systems or custom timestamp formats |
| LogAppendTime | Uses the timestamp when the record was appended to the log | log.append.time |
Default behavior if no timestamp is present |
| CreateTime | Uses the timestamp provided by the producer | create.time |
Most common for event-driven systems |
Example: Using Custom Timestamp Extractor
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "timestamp-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.TIMESTAMP_EXTRACTOR_CLASS_CONFIG, CustomTimestampExtractor.class);
6. Implementing Custom Timestampers
| Property | Description | Default Value | Notes |
|---|---|---|---|
| timestamp.extractor | Class name of the timestamp extractor implementation | org.apache.kafka.common.utils.SystemTime |
Used to extract or generate timestamps for records |
| timestamp.type | Defines how timestamps are handled (LOG_APPEND, CREATE_TIME) | CREATE_TIME |
Controls whether to use the timestamp from the record or the broker's append time |
Example: Custom Timestamp Extractor Implementation
import org.apache.kafka.common.utils.Time;
import org.apache.kafka.common.utils.SystemTime;
public class CustomTimestampExtractor implements TimestampExtractor {
private final Time time = new SystemTime();
@Override
public long extract(ConsumerRecord<String, String> record, long partitionTime) {
// Use a custom timestamp from the record's payload or metadata
String payload = record.value();
if (payload != null && payload.contains("timestamp")) {
// Extract timestamp from payload (example logic)
return parseTimestampFromPayload(payload);
}
return time.milliseconds(); // Fallback to current system time
}
private long parseTimestampFromPayload(String payload) {
// Dummy implementation for illustration
return System.currentTimeMillis();
}
}
TimestampExtractor interface allows you to define custom logic for extracting timestamps from records, which is useful in scenarios where the record's inherent timestamp is not reliable or needs to be derived from other fields.
7. Using Tumbling Windows
Tumbling windows are fixed-size, non-overlapping time-based windows used to process streams of data in Kafka Streams. They are commonly used for aggregations like counts, sums, or averages over fixed time intervals.
| Property | Description | Default Value |
|---|---|---|
| windowSize | Duration of the tumbling window in milliseconds | 60000 (1 minute) |
| timeWindow | Time window type (e.g., TimeWindows.of(...)) |
TimeWindows.of(Duration.ofMinutes(1)) |
Example: Tumbling Window for Counting Events
StreamsBuilder builder = new StreamsBuilder();
KTable<String, Long> windowedCounts = builder.stream("input-topic")
.groupBy((key, value) -> key)
.windowedBy(TimeWindows.of(Duration.ofMinutes(5)))
.count();
8. Using Hopping Windows
Hopping windows in Kafka Streams allow you to process data in fixed-size, overlapping time windows. They are useful for scenarios where you want to analyze data over a sliding time window with a specified hop interval.
| Property | Description | Default Value | Notes |
|---|---|---|---|
| windowSize | Duration of the window in milliseconds | None | Must be specified |
| hop | Time interval between window starts in milliseconds | None | Must be specified and less than or equal to windowSize |
| timeWindow | Time window type (e.g., TumblingWindow, HoppingWindow) |
HoppingWindow |
Used to define the windowing strategy |
Example: Hopping Window Configuration
StreamsBuilder builder = new StreamsBuilder();
KTable<String, Integer> table = builder.table("input-topic");
TimeWindows windows = TimeWindows.of(Duration.ofMinutes(5))
.advanceBy(Duration.ofMinutes(1));
KGroupedStream<String, Integer> grouped = table.groupBy(KeyValue.pair("key", 10));
KTable<Windowed<String>, Long> windowedCount = grouped
.windowedBy(windows)
.count();
9. Using Sliding Windows
Sliding windows in Kafka are used to process data streams over a time-based window of fixed duration. They allow you to aggregate or compute metrics over a continuous, moving time window.
| Property | Description | Default Value | Notes |
|---|---|---|---|
| window.size.ms | Duration of the sliding window in milliseconds | 60000 (1 minute) | Must be a positive integer |
| window.slide.ms | Slide interval in milliseconds | 60000 (1 minute) | Must be a positive integer, less than or equal to window size |
| window.type | Type of window (time-based or count-based) | time | Valid values: time, count |
Example: Sliding Window Configuration in Kafka Streams
StreamsBuilder builder = new StreamsBuilder();
KTable<String, Long> windowedCounts = builder
.stream("input-topic")
.groupBy((key, value) -> key)
.windowedBy(TimeWindows.of(Duration.ofMinutes(5)).advanceBy(Duration.ofMinutes(1)))
.count();
windowedCounts.toStream()
.mapValues((key, value) -> String.format("Key: %s, Count: %d", key.key(), value))
.to("output-topic");
advanceBy parameter defines how often the window slides. If set to 1 minute, a new window is created every minute, and the previous window is discarded after it expires.
10. Using Session Windows
Session windows in Kafka Streams are used to group records that belong to the same session based on a user-defined session timeout. They are particularly useful for aggregating data over periods of activity followed by inactivity.
| Property | Description | Default Value | Notes |
|---|---|---|---|
| session.timeout.ms | Timeout for session inactivity | 300000 (5 minutes) | Defines how long a session can be inactive before it's considered expired |
| session.window.ms | Duration of session window in milliseconds | 300000 (5 minutes) | Used to define the window size for session windows |
Example: Session Windows in Kafka Streams
StreamsBuilder builder = new StreamsBuilder();
KTable<String, Long> sessionTable = builder.table(
"input-topic",
Consumed.with(Serdes.String(), Serdes.Long())
).groupBy(
(key, value) -> key,
Grouped.with(Serdes.String(), Serdes.Long())
).windowedBy(
SessionWindows.of(300000L)
).reduce(
(value1, value2) -> value1 + value2
);
11. Configuring Grace Period
| Property | Default Value | Description |
|---|---|---|
| consumer.graceful.shutdown.timeout.ms | 30000 |
Timeout in milliseconds for graceful shutdown of consumer. |
| producer.graceful.shutdown.timeout.ms | 30000 |
Timeout in milliseconds for graceful shutdown of producer. |
Example: Consumer Graceful Shutdown Configuration
{
"bootstrap.servers": "localhost:9092",
"group.id": "my-consumer-group",
"enable.auto.commit": false,
"graceful.shutdown.timeout.ms": 30000
}
Example: Producer Graceful Shutdown Configuration
{
"bootstrap.servers": "localhost:9092",
"acks": "all",
"retries": 3,
"graceful.shutdown.timeout.ms": 30000
}
12. Handling Out-of-Order Events
When events arrive out of order in Kafka, handling them correctly is crucial for maintaining data consistency and accurate processing. Kafka does not guarantee message ordering within a partition, so applications must implement strategies to manage out-of-order events.
| Strategy | Description | Use Case | Notes |
|---|---|---|---|
| Event Time vs. Ingestion Time | Use event timestamps (event time) instead of when messages are ingested (ingestion time) to maintain logical ordering. | Time-series analytics, stream processing with temporal logic | Requires timestamp extraction from messages or metadata |
| Watermarking | Assign watermarks to track the progress of event time in streams, allowing for late arrival handling. | Event-driven systems with temporal constraints | Used in Apache Flink, Spark Streaming, and Kafka Streams |
| Windowing | Group events into time-based windows to process them in batches and handle out-of-order data within window boundaries. | Aggregations over time windows, real-time dashboards | Supports tumbling, sliding, and session windows |
| State Management | Use Kafka Streams or other stateful processors to maintain state and reorder events based on timestamps. | Stateful stream processing, aggregations | Requires careful design to avoid memory issues |
Example: Using Watermarks in Kafka Streams
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.*;
KafkaStreams streams = new KafkaStreams(
builder.build(),
new StreamsConfig(props)
);
// Define a watermark for event time
KStream<String, MyEvent> events = builder.stream("events-topic");
// Apply windowing with a 5-minute allowed lateness
KTable<String, Long> counts = events
.groupBy(KeyValue::new)
.windowedBy(TimeWindows.of(Duration.ofMinutes(5)).grace(Duration.ofMinutes(1)))
.count();
streams.start();