Kafka Streams
KStream vs KTable — Key Differences
Understanding streams of facts vs tables of latest updates.
Introduction
In Kafka Streams, data streams are modeled using two primary abstractions: KStream and KTable. A KStream represents an append-only stream of independent events, whereas a KTable represents a changelog stream representing the latest state of keys.
Why It Matters
Failing to distinguish between KStream and KTable lead to serious design errors. If you use a KStream to track user profile status, you must scan millions of obsolete historical update events to retrieve a user's current city. Using KTable for transactional payments, on the other hand, risks dropping intermediate transfers if updates arrive too fast.
Real-World Analogy
Think of a checking account transaction history: "+$100", "-$30", "+$20". This append-only sequence is a KStream. Think of your current account balance: "$90". This represents the cumulative state of updates and is a KTable. You can reconstruct the balance (KTable) from transactions (KStream), showing the stream-table duality.
How It Works
The dual systems behave as follows:
- KStream (Facts): If you publish
(Alice, Boston)followed by(Alice, Paris), a consumer reading a KStream sees both records as distinct facts. - KTable (States): If you publish the same updates, a consumer querying the KTable only sees the active state
(Alice, Paris). The previous city is overwritten.
Internal Architecture
Under the hood, a KTable leverages a local RocksDB state store to perform key lookups and handle updates (upserts/tombstones). If a record has a key and a null value, the KTable treats it as a deletion (Tombstone) and removes the key from the state store.
Visual Explanation
Below is the interactive visual diagram showing how the KStream and KTable dualities process updates over time:
Practical Example
Here is a Java snippet showing how to create a KStream and a KTable from topics and join them to enrich transactions with active user profiles:
Common Mistakes
- Joining KStreams without windowing: Attempting to join two KStreams without defining a time window causes an exception, because streams are infinite and cannot be kept in memory forever.
- Using KStream for master data tables: Reading a static configurations topic into a KStream instead of a KTable forces your application to duplicate lookup memory and write complex lookup logic.
Quick Quiz
Q1: What is the primary functional difference between a KStream and a KTable?
KStream represents an append-only stream of updates (inserts), while KTable represents the latest update status per key (upserts).
Q2: How does a GlobalKTable differ from a regular KTable?
A KTable only loads partitions assigned to the local instance's tasks, while a GlobalKTable replicates all topic partitions to every instance.
Scenario-Based Challenge
The Challenge: Joining location logs with delivery transactions
You receive courier location coordinates every 10 seconds, and delivery requests periodically. You want to tag every delivery request with the courier's *most recent* location. How do you design this?
Solution Tip: Read courier location coordinate events into a KTable (since you only care about the latest location coordinates per courier ID). Read delivery request events as a KStream. Perform a Stream-Table join to look up location states instantaneously on transaction arrival.
Debugging Exercise
You execute a Stream-Table join, but no events are outputting to the enriched topic, even though match keys are published to both topics.
The Fix: Check partition counts. For co-partitioning rules in Kafka Streams, both join topics must have the *same number of partitions*. If they differ, the record key will hash to different broker node tasks, causing join failures. Re-create topics with matching partition counts.
Interview Questions
1. Explain what Stream-Table Duality means.
It is the concept that a stream can be viewed as a table (by taking the sum/latest updates over time), and a table can be viewed as a stream (by capturing its modification logs).
2. What is co-partitioning, and why is it a requirement for regular KStream-KTable joins?
Co-partitioning means that both topics have matching partition counts and use the same key partitioner. This ensures matching keys reside on the same broker task, avoiding cross-network join calls.
Production Considerations
Use cache.max.bytes.buffering to control KTable output updates. High buffer cache values deduplicate records before writing them to downstream changelogs, reducing traffic.