Kafka Processor API
Key Management
Source Key
StateStore Key
Programming

Kafka Processor API Different key for Source and StateStore?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Apache Kafka's Stream Processing API, commonly known as Kafka Streams, offers a range of utilities for building real-time streaming applications. One of its core components, the Processor API, provides a low-level API that can be used to create custom processors tailored to specific needs, unlike the high-level DSL (Domain Specific Language). This article explores a critical aspect of Kafka Stream’s Processor API — the use of different keys for sources and state stores.

Understanding Kafka Processor API

The Processor API allows developers to define and connect processors in a topology. Each processor node in this topology can read from and write to Kafka topics, and carry out processing on a per-message basis. Processors can transform, filter, aggregate, or enrich incoming data streams in any way required.

Different Keys for Source and StateStore

When using Kafka Streams, it's common for stream processing requirements to necessitate the transformation of message keys, or to enrich messages by joining them with data from another source, like a StateStore. However, challenges arise when the keys of the input Kafka topic (source) do not directly match the keys used in a StateStore.

Problem Example

Consider a case where you're processing financial transactions from a Kafka topic where each message has a transactionId as the key. You might want to enrich these transactions with customer data stored in a StateStore, where the key is customerId. This mismatch requires transforming or mapping transactionId to customerId to access the appropriate state.

Implementing Different Keys for Source and StateStore

1. Key Transformation

Before you can query the StateStore, you may need to transform the key from transactionId to customerId. This can be achieved using a custom Processor:

java
1public class KeyMapperProcessor extends AbstractProcessor<byte[], byte[]> {
2    private KeyValueStore<String, String> stateStore;
3
4    @Override
5    public void init(ProcessorContext context) {
6        super.init(context);
7        this.stateStore = (KeyValueStore<String, String>) context.getStateStore("customerStore");
8    }
9
10    @Override
11    public void process(byte[] key, byte[] value) {
12        String customerId = getCustomerIdFromTransaction(value);
13        String customerData = stateStore.get(customerId);
14        // Process and forward, perhaps enrich the original message with customer data
15        context().forward(key, enrichTransaction(value, customerData));
16    }
17
18    private String getCustomerIdFromTransaction(byte[] transaction) {
19        // Extract customerId from transaction
20        return new String(transaction); // Simplified for example
21    }
22
23    private byte[] enrichTransaction(byte[] transaction, String customerData) {
24        // Enrich transaction data with customer data
25        return transaction; // Simplified for example
26    }
27}

2. StateStore Integration

Your processor needs to access a StateStore that is not keyed by the same key as the incoming records. It is crucial to ensure that the StateStore is queried with the correct keys, as demonstrated in the code above.

Tips and Challenges

Handling different keys between the source and the StateStore presents unique challenges:

  • Maintaining Consistency: Ensure that your key mapping logic is consistent and can handle all edge cases, as inconsistencies can lead to missing or incorrect data.
  • Performance: Key transformations and state lookups can add latency, especially if the StateStore is not co-partitioned and co-located. Optimal placement and partitioning of state stores can alleviate some performance concerns.

Summary Table

FeatureDescription
Source KeyOriginal key from Kafka Topic, e.g., transactionId.
StateStore KeyKey used in StateStore, e.g., customerId.
TransformationRequired processing to map from Source Key to StateStore Key.
Use caseEnhancing, aggregating, and performing look-ups against enriching datasets. Maintain relational data across streams.
ChallengesPerformance overhead, maintaining consistency and accuracy.

Conclusion

Using different keys for sources and state stores involves customized processing logic within Kafka’s Processor API but provides the flexibility needed for complex stream-processing tasks. This feature is pivotal in scenarios requiring enrichment or transformations that span across different data models or key spaces. By understanding and implementing these patterns, developers can greatly enhance the power and efficiency of their Kafka Streams applications.


Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.