Skip to content

Apache Kafka

Apache Kafka is a distributed event streaming platform. This sheet covers producers, consumers, topics, partitions, consumer groups, exactly-once semantics, and Spark Structured Streaming integration.

Table of Contents

Installation / Setup

# Install Kafka Python client
pip install kafka-python-ng

# Or use confluent-kafka (recommended)
pip install confluent-kafka
# confluent-kafka (recommended)
from confluent_kafka import Producer, Consumer, KafkaError

# kafka-python
from kafka import KafkaProducer, KafkaConsumer

Core Syntax

Kafka Concepts

Concept Description
Topic Category/feed of messages
Partition Topic is split into partitions for parallelism
Offset Position of a message within a partition
Producer Publishes messages to topics
Consumer Reads messages from topics
Consumer Group Group of consumers that divide partition reading
Broker Kafka server node
Cluster Collection of brokers

Producer (confluent-kafka)

from confluent_kafka import Producer
import json

# Create producer
producer = Producer({
    'bootstrap.servers': 'localhost:9092',
    'acks': 'all',  # wait for all replicas
    'retries': 3,
    'enable.idempotence': True  # exactly-once
})

# Callback for delivery reports
def delivery_callback(err, msg):
    if err:
        print(f"Delivery failed: {err}")
    else:
        print(f"Delivered to {msg.topic()} [{msg.partition()}]")

# Send message
producer.produce(
    topic='events',
    key='user123',
    value=json.dumps({"event": "click", "user": "user123"}).encode('utf-8'),
    callback=delivery_callback
)

# Flush to ensure delivery
producer.flush()

Consumer (confluent-kafka)

from confluent_kafka import Consumer, KafkaError

# Create consumer
consumer = Consumer({
    'bootstrap.servers': 'localhost:9092',
    'group.id': 'my-group',
    'auto.offset.reset': 'earliest',  # or 'latest'
    'enable.auto.commit': False,
})

# Subscribe to topic
consumer.subscribe(['events'])

# Poll for messages
while True:
    msg = consumer.poll(1.0)  # timeout in seconds

    if msg is None:
        continue
    if msg.error():
        if msg.error().code() == KafkaError._PARTITION_EOF:
            continue
        print(f"Error: {msg.error()}")
        break

    # Process message
    key = msg.key().decode('utf-8')
    value = json.loads(msg.value().decode('utf-8'))
    print(f"Key: {key}, Value: {value}")

    # Commit offset
    consumer.commit(msg)

consumer.close()

Kafka-python Producer

from kafka import KafkaProducer
import json

producer = KafkaProducer(
    bootstrap_servers='localhost:9092',
    value_serializer=lambda v: json.dumps(v).encode('utf-8'),
    key_serializer=lambda k: k.encode('utf-8') if k else None,
    acks='all',
    retries=3
)

producer.send('events', key='user123', value={"event": "click"})
producer.flush()

Kafka-python Consumer

from kafka import KafkaConsumer
import json

consumer = KafkaConsumer(
    'events',
    bootstrap_servers=['localhost:9092'],
    group_id='my-group',
    auto_offset_reset='earliest',
    value_deserializer=lambda m: json.loads(m.decode('utf-8')),
    enable_auto_commit=False
)

for message in consumer:
    print(f"Key: {message.key}, Value: {message.value}")
    consumer.commit()

Common Operations

Consumer Groups

# Consumer group divides partitions among consumers
# Each partition is assigned to exactly one consumer in a group
# More consumers than partitions = idle consumers

# Example: 3 partitions, 2 consumers in group
# Consumer 1: partitions 0, 1
# Consumer 2: partition 2

Topic Management

from confluent_kafka.admin import AdminClient, NewTopic

admin = AdminClient({'bootstrap.servers': 'localhost:9092'})

# Create topic
new_topic = NewTopic(
    topic='events',
    num_partitions=3,
    replication_factor=1
)
admin.create_topics([new_topic])

# List topics
topics = admin.list_topics()
print(topics.topics)

# Delete topic
admin.delete_topics(['events'])

Exactly-Once Semantics

# Producer: enable idempotence
producer = Producer({
    'bootstrap.servers': 'localhost:9092',
    'enable.idempotence': True,  # exactly-once producer
    'acks': 'all',
    'max.in.flight.requests.per.connection': 5
})

# Consumer: manual commit
consumer = Consumer({
    'bootstrap.servers': 'localhost:9092',
    'group.id': 'my-group',
    'enable.auto.commit': False,
    'isolation.level': 'read_committed'  # read only committed messages
})

Practical Examples

Example 1 — Event streaming pipeline

from confluent_kafka import Producer, Consumer
import json
from datetime import datetime

# Producer: send events
def send_event(event_type, data):
    producer = Producer({'bootstrap.servers': 'localhost:9092'})
    event = {
        "event_type": event_type,
        "timestamp": datetime.now().isoformat(),
        "data": data
    }
    producer.produce(
        topic='events',
        key=event_type,
        value=json.dumps(event).encode('utf-8')
    )
    producer.flush()

# Consumer: process events
def process_events():
    consumer = Consumer({
        'bootstrap.servers': 'localhost:9092',
        'group.id': 'analytics',
        'auto.offset.reset': 'earliest'
    })
    consumer.subscribe(['events'])

    while True:
        msg = consumer.poll(1.0)
        if msg and not msg.error():
            event = json.loads(msg.value().decode('utf-8'))
            print(f"Processing: {event}")

Example 2 — Spark Structured Streaming from Kafka

# Read stream from Kafka
stream_df = spark.readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", "localhost:9092") \
    .option("subscribe", "events") \
    .option("startingOffsets", "earliest") \
    .load()

# Parse JSON messages
from pyspark.sql.functions import from_json, col
from pyspark.sql.types import StructType, StringType, TimestampType

schema = StructType() \
    .add("event_type", StringType()) \
    .add("timestamp", TimestampType()) \
    .add("data", StringType())

parsed_df = stream_df \
    .selectExpr("CAST(key AS STRING)", "CAST(value AS STRING)") \
    .withColumn("parsed", from_json(col("value"), schema)) \
    .select("parsed.*")

# Write to Delta
query = parsed_df.writeStream \
    .format("delta") \
    .outputMode("append") \
    .option("checkpointLocation", "dbfs:/checkpoints/events") \
    .toTable("events_table")

Example 3 — Write to Kafka from Spark

# Write DataFrame to Kafka
df.selectExpr(
    "CAST(key AS STRING)",
    "to_json(struct(*)) AS value"
).write \
    .format("kafka") \
    .option("kafka.bootstrap.servers", "localhost:9092") \
    .option("topic", "output_events") \
    .save()

Common Patterns

Pattern: dead letter queue

def process_with_dlq(consumer, processor):
    while True:
        msg = consumer.poll(1.0)
        if msg and not msg.error():
            try:
                process(msg)
                consumer.commit(msg)
            except Exception as e:
                # Send to DLQ
                producer.produce('events_dlq', msg.value())
                consumer.commit(msg)

Pattern: event-driven architecture

# Events trigger downstream actions
# Order created → inventory check → payment → shipping

# Each service is a consumer/producer
# Service A: consumes 'orders', produces 'inventory_checks'
# Service B: consumes 'inventory_checks', produces 'payments'
# Service C: consumes 'payments', produces 'shipments'

Pattern: exactly-once processing

# Use transactional producer + consumer offset tracking
producer = Producer({
    'bootstrap.servers': 'localhost:9092',
    'transactional.id': 'my-transactional-producer'
})

producer.init_transactions()

try:
    producer.begin_transaction()
    # produce messages
    producer.produce('topic', key, value)
    producer.commit_transaction()
except:
    producer.abort_transaction()

Best Practices

  • Use consumer groups — for scalability and fault tolerance.
  • Set appropriate replication factor — minimum 3 for production.
  • Monitor consumer lag — ensure consumers keep up with producers.
  • Use keys for ordering — messages with same key go to same partition.
  • Idempotent consumers — handle duplicate messages gracefully.
  • Use dead letter queues — capture failed messages for reprocessing.
  • Partition by key — ensure related messages are processed together.
  • Tune batch sizes — balance latency vs throughput.

Common Mistakes

Mistake Fix
No consumer group Use consumer groups for scalability
Too many partitions Start with moderate count; increase as needed
Not monitoring lag Set up lag monitoring and alerts
Idempotent consumers not handling duplicates Design for at-least-once delivery
Hardcoding broker addresses Use configuration management
Not using dead letter queues Implement DLQ for failed messages

Troubleshooting

Symptom Cause / Fix
High consumer lag Add consumers; optimize processing
Messages not delivered Check topic exists; check broker connectivity
Duplicate messages Use idempotent consumers; enable exactly-once
Consumer not receiving messages Check consumer group; check offset reset
Slow producers Increase batch size; tune acks setting

Official Documentation

Last Updated

2026-09-06 — Initial version.