aiokafka Documentation

repository·master·Indexed 23 days ago

https://github.com/aio-libs/aiokafka

An asynchronous Kafka client for Python built on top of asyncio. It provides high-level abstractions via AIOKafkaProducer for sending messages and AIOKafkaConsumer for reading messages with support for consumer groups and offset management. Additionally, it offers AIOKafkaClient for low-level cluster operations and an experimental AIOKafkaAdminClient for administrative tasks such as managing topics, configurations, and consumer groups.

Tokens
22.6K
Snippets
36
Records
98
Agent score
79%

What's inside aiokafka

  1. How Consumer Groups work in aiokafka

    master

    Kafka uses Consumer Groups to allow a pool of processes to divide the work of consuming and processing records. All AIOKafkaConsumer instances sharing the same group_id are part of the same group.

    Kafka balances the partitions between all members in the group so that each partition is assigned to exactly one consumer in the group. This provides scalability and fault tolerance:

    • Rebalancing: If a process fails, its partitions are reassigned to other consumers. If a new consumer joins, partitions are moved from existing consumers to the new one.
    • Scalability: If a topic has four partitions and a group has two processes, each process consumes from two partitions.

    Conceptually, a Consumer Group acts as a single logical subscriber made up of multiple processes.

    # Process 1
    consumer = AIOKafkaConsumer(
        "my_topic", bootstrap_servers='localhost:9092',
        group_id="MyGreatConsumerGroup"  # This enables Consumer Groups
    )
    await consumer.start()
    async for msg in consumer:
        print("Process %s consumed msg from partition %s" % (os.getpid(), msg.partition))
    
    # Process 2
    consumer2 = AIOKafkaConsumer(
        "my_topic", bootstrap_servers='localhost:9092',
        group_id="MyGreatConsumerGroup"  # Part of the same group
    )
    await consumer2.start()
    async for msg in consumer2:
        print("Process %s consumed msg from partition %s" % (os.getpid(), msg.partition))
  2. How rebalancing and heartbeating work in aiokafka

    master

    In aiokafka, heartbeating and group rebalancing are delegated to a background Task. This task sends heartbeats to the Coordinator as long as the event loop is running, similar to the Java client. This means rebalancing is not directly affected by the time taken between getmany() calls.

    Because rebalancing and heartbeating are decoupled from the main processing loop, aiokafka provides two distinct configuration settings that are often conflated in the Java client:

    • rebalance_timeout_ms: Controls the rebalance timeout. If you use a ConsumerRebalanceListener to control rebalance start/end moments, set this to the maximum time your application might spend waiting in the callback.
    • max_poll_interval_ms: Controls the consumer processing timeout.

    Note: If you use a ConsumerRebalanceListener that waits for the last getmany() result to be processed, it is safe to set rebalance_timeout_ms equal to max_poll_interval_ms.

  3. Handle OffsetOutOfRangeError for outdated local state

    master

    When managing offsets externally, your stored offset might become invalid (e.g., the log segment was deleted in Kafka). By setting auto_offset_reset="none", aiokafka will raise an OffsetOutOfRangeError.

    You should catch this error, discard your local state for the affected partitions, and then use consumer.seek_to_beginning(*tps) to restart processing from the earliest available data.

  4. How Group Consumers work in aiokafka

    master

    In Kafka, multiple consumers can consume from the same topic simultaneously by coordinating through a broker node (the coordinator). This coordination synchronizes partition assignment. Consumers will only return messages for the partitions currently assigned to them.

    Important Note for autocommit=False mode: If you have disabled automatic offset committing, you should re-check your partition assignment before processing the next message returned by AIOKafkaConsumer.getmany(). This ensures you are still the owner of the partition for the message you are about to process.

  5. How aiokafka handles consumer liveness and heartbeats

    master

    Unlike kafka-python or the Java Client, where the poll API is used to ensure consumer liveness, aiokafka manages group membership and liveness automatically in the background.

    • Joining Groups: The consumer joins the group during the .start() call.
    • Heartbeats: Heartbeats are sent in the background to keep the consumer group alive, similar to the Java Client.
    • Rebalancing: Rebalancing processes are also handled in the background.
    • Autocommit: In autocommit mode, offset commits are performed strictly by time in the background. This differs from the Java client, where autocommit requires a subsequent call to poll to trigger.
  6. Understand aiokafka prefetching behavior

    master

    Unlike the Java client or kafka-python which perform simple prefetches (fetching more data only when the current buffer is nearly empty), aiokafka implements more sophisticated per-partition prefetching.

    This is particularly beneficial for semantic partitioning (per-partition processing), as it prevents latency from being bound by the slowest partition. If the consumer processes all pending data for a specific partition, aiokafka will attempt to prefetch new data for that partition immediately.

    Behavioral Note: If you call AIOKafkaConsumer.getmany() without specifying specific partitions, the prefetch behavior will match the simpler model used by kafka-python's poll().

  7. How message buffering and batching work

    master

    When you call send(), messages are not sent immediately but are added to a buffer space. A background task then sends batches of messages to the cluster.

    Key behaviors:

    • Per-partition batches: Batches are created per partition with a maximum size defined by max_batch_size.
    • Ordering: Messages in a batch are strictly in append order. Only one batch per partition is sent at a time, guaranteeing strict message order within a partition.
    • Linger time: By default, a new batch is sent immediately after the previous one. You can set linger_ms to a non-zero value to introduce a delay, allowing more messages to accumulate in a single batch for higher throughput and better compression.
    • Async sending: Using send() returns a future. You can await this future to get the RecordMetadata once the message is delivered.
    # Will add the message to 1st partition's batch. If this method times out, 
    # we can say for sure that message will never be sent.
    fut = await producer.send("my_topic", b"Super message", partition=1)
    
    # Message will either be delivered or an unrecoverable error will occur.
    # Cancelling this future will not cancel the send.
    msg = await fut
  8. Understand the difference between aiokafka and kafka-python

    master

    While kafka-python is designed for threaded environments and mimics the Java Client API, it contains many blocking behaviors (e.g., blocking socket usage, blocking bootstrap in the constructor, and blocking produce requests when buffers are full) that make it unsuitable for asynchronous event loops.

    aiokafka is built specifically for asynchronous environments. It provides a non-blocking interface based on coroutines and concurrent.futures.Future, allowing it to integrate seamlessly with asyncio event loops.

  9. Setup Kafka topic for testing group consumers

    master

    Before running consumer/producer examples, create a topic with multiple partitions using the standard Kafka utility to demonstrate partition distribution across a group.

    bin/kafka-topics.sh --create \
      --zookeeper localhost:2181 \
      --replication-factor 1 \
      --partitions 2 \
      --topic some-topic
  10. Produce messages with AIOKafkaProducer

    master

    The AIOKafkaProducer is a high-level, asynchronous message producer.

    To use it:

    1. Initialize AIOKafkaProducer with bootstrap_servers.
    2. Call await producer.start() to get cluster layout and initial topic/partition leadership information.
    3. Use await producer.send_and_wait(topic, value) to produce a message and wait for delivery.
    4. Call await producer.stop() to ensure all pending messages are delivered or expire before shutting down.
    from aiokafka import AIOKafkaProducer
    import asyncio
    
    async def send_one():
        producer = AIOKafkaProducer(
            bootstrap_servers='localhost:9092')
        # Get cluster layout and initial topic/partition leadership information
        await producer.start()
        try:
            # Produce message
            await producer.send_and_wait("my_topic", b"Super message")
        finally:
            # Wait for all pending messages to be delivered or expire.
            await producer.stop()
    
    asyncio.run(send_one())
  11. Use Transactional Producer for Atomic Writes

    master

    Transactional producers allow sending messages to one or more topics such that they only become visible to consumers after the transaction is committed. To use this, you must set a transactional_id.

    Requirements and Behavior:

    • Setting transactional_id automatically enables idempotence.
    • For durability, topics used in transactions should have replication.factor $\ge 3$ and min.insync.replicas $\ge 2$.
    • Consumers must be configured to read only committed messages.
    • transactional_id enables recovery across multiple sessions. It should be unique to each producer instance. If a new instance uses the same ID, the previous instance will raise a non-retriable ProducerFenced error.
    • You can also commit consumer offsets as part of the same transaction using send_offsets_to_transaction.
    # Basic Transaction
    producer = aiokafka.AIOKafkaProducer(
        bootstrap_servers='localhost:9092',
        transactional_id="transactional_test")
    await producer.start()
    try:
        async with producer.transaction():
            res = await producer.send_and_wait(
                "test-topic", b"Super transactional message")
    finally:
        await producer.stop()
    
    # Committing offsets within a transaction
    async with producer.transaction():
        commit_offsets = {
            TopicPartition("some-topic", 0): 100
        }
        await producer.send_offsets_to_transaction(
            commit_offsets, "some-consumer-group")
  12. Handle consumer rebalancing with ConsumerRebalanceListener

    master

    When group reassignment (rebalancing) occurs, you can use a ConsumerRebalanceListener to perform application-level logic like state cleanup or manual offset commits.

    Warning: Avoid Deadlocks The consumer awaits the listener's handlers and will block subsequent calls to getmany() or getone(). If your listener attempts to acquire a lock that is already held by the main loop (which is currently awaiting the consumer), a deadlock will occur. Always ensure calls like consumer.getmany() are outside the lock used within the listener.

    lock = asyncio.Lock()
    consumer = AIOKafkaConsumer(...)
    
    class MyRebalancer(aiokafka.ConsumerRebalanceListener):
        async def on_partitions_revoked(self, revoked):
            async with lock:
                pass  # Perform cleanup
    
        async def on_partitions_assigned(self, assigned):
            pass
    
    async def main():
        consumer.subscribe("topic", listener=MyRebalancer())
        while True:
            # IMPORTANT: The consumer call must be outside the lock
            async with lock:
                # process other shared state
                pass
            msgs = await consumer.getmany(timeout_ms=1000)
            # process messages