Kafka Streams is a library for building microservices and distributed applications on Kafka. The most surprising thing about Kafka Streams is that its performance is often limited not by Kafka itself, but by the application logic developers write.
Let’s look at how a simple Kafka Streams application processes data. Imagine a WordCount application. It reads raw text messages from an input Kafka topic, splits them into individual words, and then aggregates the count of each word into an output topic.
StreamsBuilder builder = new StreamsBuilder();
KStream<String, Long> wordCounts = builder
.stream("input-topic", Consumed.with(Serdes.String(), Serdes.String()))
.flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
.groupBy((key, value) -> value)
.count(Materialized.as("counts-store"))
.toStream();
wordCounts.to("output-topic", Produced.with(Serdes.String(), Serdes.Long()));
KafkaStreams streams = new KafkaStreams(builder.build(), streamsConfig);
streams.start();
In this example, flatMapValues is where the text is split. groupBy and count handle the aggregation. The Materialized.as("counts-store") part is crucial: it tells Kafka Streams to maintain a local state store (a RocksDB instance by default) to keep track of the counts. This state store is what allows Kafka Streams to perform aggregations efficiently.
The mental model for Kafka Streams performance involves several key components:
- Source Processors: These read data from Kafka topics. Their performance is generally limited by Kafka broker throughput and network.
- Stream Processors: These perform transformations (like
flatMapValues,filter,map). The efficiency here depends on the complexity of the logic. A CPU-boundflatMapValuesthat does heavy string manipulation can become a bottleneck. - State Stores: These are local, persistent stores (like RocksDB) that hold intermediate data for aggregations (
count,reduce,aggregate). They enable efficient stateful operations. Performance is affected by disk I/O and memory. - Kafka Brokers: These are the ultimate source and destination for data. Network bandwidth and broker load are always factors.
- Serdes (Serializers/Deserializers): The efficiency of converting data to and from bytes for Kafka. JSON or Avro can be more expensive than simple formats like String or Long.
Benchmarking your Kafka Streams application involves understanding these components and identifying potential bottlenecks. A common approach is to use tools like Kafka Streams Test Driver for unit testing or to deploy your application with metrics enabled and monitor them using tools like Prometheus and Grafana. Key metrics to watch are:
- Record-in/Record-out Rate: How many records are processed per second by each task.
- Process Latency: The time it takes for a record to go through your processing logic.
- State Store Read/Write Latency: For stateful operations, how long it takes to access the local state.
- Thread CPU Utilization: High CPU usage on your application instances can indicate a CPU-bound processing logic.
- Network I/O: While often not the bottleneck for Kafka Streams itself, it’s good to monitor.
When benchmarking, start with a realistic load that mimics your expected production traffic. Gradually increase the load and observe where performance degrades. If your Process Latency starts climbing, it’s time to dive into the specific Stream Processors and State Stores involved in that part of the topology.
The one thing most people don’t know is that Kafka Streams’ count() and aggregate() operations, when using a state store, are not just about reading and writing to Kafka. They involve a local read-modify-write cycle on the state store (typically RocksDB). If your aggregation key distribution is highly skewed (e.g., one key gets 90% of the traffic), that single key’s record in the state store becomes a massive bottleneck, even if Kafka itself is humming along. This is because the local state store operation for that key has to serialize writes and potentially compete for disk I/O and memory.
After you’ve tuned your application logic and ensured your state stores are performing well, the next challenge is often managing Kafka consumer lag and ensuring your application scales horizontally across multiple instances.