ruby-kafka Documentation

repository·main·Indexed 23 days ago

https://github.com/zendesk/ruby-kafka

A Ruby client library for Apache Kafka designed for operational simplicity, providing logging and metrics for debugging. It supports synchronous and asynchronous production, consumer groups, and various partitioning strategies. Note: This library is no longer actively developed and has been superseded by rdkafka-ruby; it is not recommended for production usage.

Tokens
15.3K
Snippets
32
Records
80
Agent score
79%

What's inside ruby-kafka

  1. Understand Partitioning strategies

    main

    Kafka topics are divided into partitions. You can control how messages are assigned to these partitions using several strategies:

    Load Balanced Partitioning

    Distributes messages evenly across partitions to ensure balanced consumer load. If no key is provided, the producer assigns a partition randomly.

    Semantic Partitioning

    Assigns messages to partitions based on a specific property (e.g., a session_id). This ensures that all messages related to a specific entity are processed by the same consumer instance, facilitating local state management.

    Custom Partitioning

    To ensure compatibility with other Kafka clients (like Java-based ones), you can implement a custom partitioner. You can pass a class that responds to call(partition_count, message) or a Proc to the Kafka.new constructor.

  2. How the Consumer works

    main

    The Kafka::Consumer API is designed for flexibility and stability using a loop-based approach rather than a rigid object model.

    Key features include:

    • Automatic Management: It handles group membership, heartbeats, and checkpointing automatically.
    • Processing Guarante_s: Messages are marked as processed as soon as they are successfully yielded to your processing block, which helps minimize the impact of processing errors.
  3. How message delivery guarantees work in ruby-kafka

    main

    ruby-kafka implements at-least-once delivery guarantees for the synchronous producer. This means a message is guaranteed to be delivered, but it may be delivered more than once if a failure occurs during acknowledgement.

    For the synchronous producer, once #deliver_messages returns, you can be certain the message has been received by Kafka.

    Caveats:

    • If required_acks is set to 0, there is no guarantee of delivery.
    • If using the asynchronous producer, #deliver_messages does not guarantee delivery has completed.
    • Cluster/topic configuration can still result in message loss.
  4. Balance throughput and latency in message fetching

    main

    You can tune three parameters to balance the number of messages processed (throughput) against the time it takes to process them (latency):

    1. min_bytes: Minimum bytes to return from a single fetch. Higher values increase throughput. (Default: 1 byte).
    2. max_wait_time: Maximum seconds to wait before returning data. High values increase throughput; low values decrease latency. This overrides min_bytes. (Default: 1 second).
    3. max_bytes_per_partition: Maximum data a broker returns for a single partition. Increasing this improves throughput. (Default: 1MB).

    min_bytes and max_wait_time are passed to #each_message or #each_batch. max_bytes_per_partition is passed to #subscribe.

    # Waits for data for up to 5 seconds on each broker, preferring to fetch at least 5KB at a time.
    consumer.each_message(min_bytes: 1024 * 5, max_wait_time: 5) do |message|
      # ...
    end
    
    # Fetches up to 5MB per partition at a time for better throughput.
    consumer.subscribe("greetings", max_bytes_per_partition: 5 * 1024 * 1024)
  5. How Consumer Groups work

    main

    The Consumer API uses Kafka's Consumer Groups feature to allow multiple consumer processes to coordinate access to a topic.

    Key features:

    • Partition Assignment: Each partition in a topic is assigned to exactly one consumer within the group.
    • Scalability: To handle more messages, simply start more consumer processes with the same group_id.
    • Fault Tolerance: If a consumer fails, its assigned partitions are re-assigned to other members of the group.
    • Checkpointing: Consumers periodically 'checkpoint' (commit) their position by saving the last processed offset. This allows a new consumer to resume from where the previous one left off.
    require "kafka"
    
    kafka = Kafka.new(["kafka1:9092", "kafka2:9092"])
    
    # Consumers with the same group id will form a Consumer Group together.
    consumer = kafka.consumer(group_id: "my-consumer")
    
    # It's possible to subscribe to multiple topics by calling `subscribe`
    # repeatedly.
    consumer.subscribe("greetings")
    
    # Stop the consumer when the SIGTERM signal is sent to the process.
    # It's better to shut down gracefully than to kill it.
    trap("TERM") { consumer.stop }
    
    # This will loop indefinitely, yielding each message in turn.
    consumer.each_message do |message|
      puts message.topic, message.partition
      puts message.offset, message.key, message.value
    end
  6. Serialize message data

    main

    The library is agnostic to serialization formats. It treats both the message value and the key as binary strings. You are responsible for encoding your data (e.g., to JSON or Avro) before passing it to the producer.

    require "json"
    
    event = {
      "name" => "pageview",
      "url" => "https://example.com/posts/123"
    }
    
    data = JSON.dump(event)
    producer.produce(data, topic: "events")
  7. How the synchronous Producer works

    main

    The Kafka::Producer is designed for resilience and observability. It uses two internal data structures: a pending messages list and a message buffer.

    1. #produce: When you call Kafka::Producer#produce, the message is added to the pending list. No network communication occurs at this stage, so network errors are not raised immediately.
    2. #deliver_messages: This method triggers the actual network activity. It:
      • Assigns partitions to pending messages (this may require API calls to Kafka).
      • Moves assignable messages to the message buffer.
      • Routes messages to the correct Kafka brokers.
      • Sends produce requests to brokers.
      • Removes acknowledged messages from the buffer.

    Error Handling: If messages remain in the pending list or the buffer after #deliver_messages completes, a Kafka::DeliveryFailed exception is raised. You must rescue this exception and decide how to retry (e.g., by calling #deliver_messages again later).

  8. Understand the ruby-kafka layered architecture

    main

    The library is organized into several layers. As an end-user, you should primarily interact with the API layer and the configuration layer. The other layers are considered internal and may change without warning.

    • Configuration Layer: Provides setup and entrypoints. Kafka::Client implements the public APIs, and Kafka.new is a convenience method to instantiate it.
    • API Layer: Provides the primary interfaces for users. This includes Kafka::Consumer for consuming messages, and Kafka::Producer or Kafka::AsyncProducer for producing messages.
    • Operational Layer: Handles high-level operations (e.g., Kafka::Cluster or Kafka::Broker).
    • Protocol Layer: Handles encoding/decoding of Kafka protocol structures.
    • Network Layer: Handles low-level connections and reconnections.
  9. How the Asynchronous Producer works

    main

    The Kafka::AsyncProducer provides a hands-off approach compared to the synchronous producer, trading fine-grained control for ease of use and resilience.

    • Mechanism: Instead of writing to a pending list, it writes messages to an internal thread-safe queue and returns immediately.
    • Background Processing: A background thread reads from this queue and passes messages to a synchronous producer.
    • Triggers: Users typically do not call delivery methods manually; instead, they set up automatic triggers, such as a timer, to flush messages.
  10. Thread safety and concurrency in ruby-kafka

    main

    When using ruby-kafka in multi-threaded environments, follow these concurrency rules:

    • Kafka Client: Do not share a single Kafka client object between threads. Network communication is not synchronized.
    • Consumers: Avoid using threads inside a consumer unless you ensure all work completes before returning from the #each_message or #each_batch block. Returning from the block triggers checkpointing, which assumes the yielded messages have been successfully processed.
    • Synchronous Producers: Do not share a synchronous producer between threads; its internal buffers are not thread-safe.
    • Asynchronous Producers: These are safe to share between threads. The asynchronous producer uses a single background thread to handle non-thread-safe data (like network sockets), while foreground threads communicate with it via a safe queue.
  11. Install ruby-kafka

    main

    You can install ruby-kafka using Bundler by adding it to your Gemfile, or by installing the gem directly via the command line.

    Using Bundler

    Add this line to your Gemfile:

    gem 'ruby-kafka'

    Then run:

    bundle

    Using gem install

    gem install ruby-kafka