Migrating from Apache Kafka to StreamFlow
Table of Contents
- Why Migrate?
- Compatibility
- Migration Plan
- Migration by Language
- Kafka Connect → StreamFlow Connectors
- Kafka Streams → Agent Graphs
- Migration Checklist
- FAQ
1. Why Migrate?
| Aspect | Apache Kafka | StreamFlow |
|---|---|---|
| Consumer throughput | 462K msg/s | 743K msg/s (+61%) |
| Producer throughput | 447K msg/s | 358K msg/s (80%, gap = pd-ssd) |
| Native engine (SDK embedded) | N/A | 3.8M evt/s |
| Built-in AI Agents | No (requires Kafka Streams + external service) | Yes (LLM, rules, MCP tools) |
| Agent Graphs | No | Yes (fluent DSL, conditional routing) |
| Elastic partitioning | No (fixed partitions) | Yes (16384 slots, auto-rebalance) |
| Geo-replication | MirrorMaker (separate tool) | Built-in (active-passive + active-active) |
| io_uring | No (mmap + page cache) | Yes (SQPOLL, DMA buffers) |
| Deployment | ZooKeeper or KRaft (complex) | Single binary (built-in Raft) |
2. Compatibility
StreamFlow implements the Kafka wire protocol (TCP, port 9092). Your existing applications connect without any code change:
┌──────────────────────┐ ┌──────────────────────┐
│ Your application │ │ Your application │
│ KafkaProducer │ │ KafkaProducer │ ← SAME CODE
│ KafkaConsumer │ │ KafkaConsumer │
│ bootstrap=kafka:9092│ │ bootstrap=sf:9092 │ ← JUST CHANGE THE ADDRESS
└──────────┬───────────┘ └──────────┬───────────┘
│ │
Apache Kafka StreamFlowWhat works without changes
| Kafka Feature | Supported | Notes |
|---|---|---|
| KafkaProducer (Java, Python, Go, Node.js) | ✅ | All Kafka libs |
| KafkaConsumer with consumer groups | ✅ | Rebalancing, offset commit |
| acks=0, acks=1, acks=all | ✅ | Same semantics |
| batch.size, linger.ms | ✅ | Same configs |
| compression.type (snappy, lz4, zstd) | ✅ | Transparent decompression |
| Topic auto-creation | ✅ | Configurable |
| kafka-console-producer.sh | ✅ | Standard CLI tools |
| kafka-console-consumer.sh | ✅ | |
| kafka-producer-perf-test.sh | ✅ | |
| kafka-consumer-perf-test.sh | ✅ | |
| Idempotent producer | ✅ | InitProducerId supported |
| SASL PLAIN authentication | ✅ |
What is not (yet) supported
| Kafka Feature | Status | StreamFlow Alternative |
|---|---|---|
| Kafka Transactions | ❌ | acks=all + Raft replication |
| Kafka Streams | ❌ | Agent graphs (more powerful) |
| Kafka Connect | ❌ | Source/Sink Connector SDK (19 built-in) |
| Schema Registry | ❌ | Headers + SDK validation |
| Exactly-once semantics (EOS) | Partial | Idempotent producer + at-least-once consumer |
| ACL via kafka-acls.sh | ❌ | REST API + TopicAclPolicy |
| Log compaction | ✅ | Built-in LogCompactor |
3. Migration Plan
Phase 1: Parallel Testing (1 day)
Run StreamFlow alongside Kafka without touching production.
1# 1. Start StreamFlow (single node for testing)
2./scripts/start-streamflow.sh
3
4# 2. Create the same topic as in Kafka
5kafka-topics.sh --create --topic orders \
6 --partitions 16 --bootstrap-server localhost:9092
7
8# 3. Run the same benchmark as Kafka
9kafka-producer-perf-test.sh --topic orders \
10 --num-records 1000000 --record-size 256 \
11 --throughput -1 \
12 --producer-props bootstrap.servers=localhost:9092 \
13 batch.size=65536 linger.ms=5 acks=1
14
15kafka-consumer-perf-test.sh --topic orders \
16 --messages 1000000 --broker-list localhost:9092 --timeout 60000
17
18# 4. Compare results with KafkaPhase 2: Dual-Write (1-2 weeks)
Your producers write to both clusters. Consumers read from StreamFlow.
1// Dual-write producer (during migration)
2public class DualWriteProducer {
3 private final KafkaProducer<String, byte[]> kafkaProducer; // old
4 private final StreamFlowProducer streamFlowProducer; // new
5
6 public void send(String topic, String key, byte[] value) {
7 // Write to both
8 kafkaProducer.send(new ProducerRecord<>(topic, key, value));
9 streamFlowProducer.send(topic, key, value);
10 }
11}Verify data parity:
1# Count events in each system
2kafka-run-class.sh kafka.tools.GetOffsetShell \
3 --broker-list kafka:9092 --topic orders
4# vs
5kafka-run-class.sh kafka.tools.GetOffsetShell \
6 --broker-list streamflow:9092 --topic ordersPhase 3: Switchover (1 day)
1# 1. Stop Kafka consumers
2# 2. Change bootstrap.servers in your app config
3sed -i 's/kafka-cluster:9092/streamflow-cluster:9092/' application.yml
4
5# 3. Redeploy apps
6kubectl rollout restart deployment/my-app
7
8# 4. Verify everything works
9curl http://streamflow:8080/metrics | grep events_publishedPhase 4: Decommission Kafka (after 1 month)
1# Once StreamFlow is stable in production
2docker compose -f kafka-cluster.yml down
3# or
4kubectl delete statefulset kafka4. Migration by Language
Java (KafkaProducer/KafkaConsumer)
Before (Kafka):
1Properties props = new Properties();
2props.put("bootstrap.servers", "kafka-cluster:9092");
3props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
4props.put("value.serializer", "org.apache.kafka.common.serialization.ByteArraySerializer");
5KafkaProducer<String, byte[]> producer = new KafkaProducer<>(props);
6producer.send(new ProducerRecord<>("orders", "key-1", payload));After (StreamFlow SDK):
1StreamFlowClient client = StreamFlowClient.remote("streamflow-cluster:9092");
2StreamFlowProducer producer = client.producer();
3producer.send("orders", "key-1", payload);Or without changing code at all:
1// ONLY CHANGE: the cluster address
2props.put("bootstrap.servers", "streamflow-cluster:9092");
3// The rest of the KafkaProducer code works as-isPython (confluent-kafka)
Before:
1producer = Producer({'bootstrap.servers': 'kafka-cluster:9092'})After:
1producer = Producer({'bootstrap.servers': 'streamflow-cluster:9092'})
2# That's it. Same code, same lib.Node.js (KafkaJS)
Before:
1const kafka = new Kafka({ brokers: ['kafka-cluster:9092'] });After:
1const kafka = new Kafka({ brokers: ['streamflow-cluster:9092'] });Go (kafka-go)
Before:
1writer := &kafka.Writer{Addr: kafka.TCP("kafka-cluster:9092"), Topic: "orders"}After:
1writer := &kafka.Writer{Addr: kafka.TCP("streamflow-cluster:9092"), Topic: "orders"}5. Kafka Connect → StreamFlow Connectors
Kafka Connect (before)
1{
2 "name": "jdbc-source",
3 "config": {
4 "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector",
5 "connection.url": "jdbc:postgresql://db:5432/shop",
6 "table.whitelist": "orders",
7 "mode": "incrementing",
8 "incrementing.column.name": "id",
9 "topic.prefix": "cdc-"
10 }
11}StreamFlow Connector (after)
1# connect-source.properties
2source.jdbc-orders.type=jdbc
3source.jdbc-orders.topic=cdc-orders
4source.jdbc-orders.connection.url=jdbc:postgresql://db:5432/shop
5source.jdbc-orders.table=orders
6source.jdbc-orders.mode=incrementing
7source.jdbc-orders.incrementing.column=id
8source.jdbc-orders.poll.interval.ms=1000Connector Mapping
| Kafka Connect | StreamFlow | Config type |
|---|---|---|
| JdbcSourceConnector | JdbcSourceConnector | jdbc |
| JdbcSinkConnector | JdbcSinkConnector | jdbc |
| FileStreamSourceConnector | HttpSourceConnector | http |
| ElasticsearchSinkConnector | ElasticsearchSinkConnector | elasticsearch |
| MongoSinkConnector | MongoDbSinkConnector | mongodb |
| S3SinkConnector | ❌ (not yet) | — |
| DebeziumSourceConnector | CdcSourceConnector | cdc |
6. Kafka Streams → Agent Graphs
Kafka Streams (before)
1StreamsBuilder builder = new StreamsBuilder();
2
3builder.stream("transactions")
4 .filter((key, value) -> value.getAmount() > 10000)
5 .mapValues(tx -> new FraudCheck(tx))
6 .to("fraud-alerts");
7
8KafkaStreams streams = new KafkaStreams(builder.build(), props);
9streams.start();StreamFlow Agent Graph (after)
1CompiledGraph graph = StreamflowGraph.define("fraud-detection")
2 .start("filter")
3 .agent("amount-filter")
4 .prompt("Filter: keep only transactions with amount > 10000. " +
5 "Return the transaction unchanged or null to drop.")
6 .then("analyze")
7 .agent("fraud-analyzer")
8 .prompt("Analyze this high-value transaction for fraud indicators. " +
9 "Return {fraud: true/false, confidence: 0-1, reason: '...'}")
10 .end("alert")
11 .agent("alert-generator")
12 .prompt("Generate a fraud alert for the security team. " +
13 "Include transaction details and risk assessment.")
14 .compile();
15
16GraphRunner runner = new GraphRunner();
17runner.register(graph);7. Migration Checklist
Phase 1: Test
- Install StreamFlow (1 node)
- Run benchmark with kafka-producer-perf-test
- Compare results with Kafka
- Test consumer groups
Phase 2: Dual-write
- Configure dual-write in producers
- Switch consumers to StreamFlow
- Verify data parity (offsets, counts)
- Monitor for 1-2 weeks
Phase 3: Switchover
- Change bootstrap.servers in all apps
- Redeploy
- Verify /metrics (throughput, latency, lag)
- Configure Prometheus alerts
Phase 4: Decommission Kafka
- Wait 1 month of stability
- Export offsets if needed
- Shut down Kafka cluster
- Delete ZooKeeper/KRaft volumes
Bonus: Enable StreamFlow features
- Create AI agents (Agent Guide)
- Configure geo-replication
- Enable elastic partitioning
- Migrate Kafka Connect to StreamFlow connectors
- Replace Kafka Streams with Agent Graphs
8. FAQ
Q: Do I need to change my code?
R: No. Just change bootstrap.servers. All Kafka libs (Java, Python, Go, Node.js) work.
Q: What about my Kafka offsets?
R: StreamFlow offsets start from 0. Use auto.offset.reset=earliest to re-read from the beginning, or latest to read only new messages.
Q: Do my consumer groups work?
R: Yes. Rebalancing, offset commit, and session timeout work just like Kafka.
Q: Performance comparison?
R: Consumer 61% faster than Kafka. Producer at 80% of Kafka on pd-ssd (gap due to network disk latency, not code). On local NVMe, StreamFlow should match or exceed Kafka.
Q: What about durability?
R: With acks=all, data is replicated via Raft across 3 nodes before ACK. Same guarantee as Kafka with replication.factor=3.
Q: Can I go back to Kafka?
R: Yes. During the dual-write phase, both clusters have the same data. Just change bootstrap.servers back.