GithubHelp home page GithubHelp logo

blizzard / node-rdkafka Goto Github PK

View Code? Open in Web Editor NEW
2.1K 59.0 392.0 11.63 MB

Node.js bindings for librdkafka

License: MIT License

Makefile 0.54% JavaScript 52.04% Shell 0.69% Python 1.00% C++ 45.72% PowerShell 0.01%
kafka librdkafka nodejs

node-rdkafka's Introduction

node-rdkafka - Node.js wrapper for Kafka C/C++ library

Copyright (c) 2016 Blizzard Entertainment.

https://github.com/blizzard/node-rdkafka

Build Status

npm version

Looking for Collaborators!

I am looking for your help to make this project even better! If you're interested, check this out

Overview

The node-rdkafka library is a high-performance NodeJS client for Apache Kafka that wraps the native librdkafka library. All the complexity of balancing writes across partitions and managing (possibly ever-changing) brokers should be encapsulated in the library.

This library currently uses librdkafka version 2.3.0.

Reference Docs

To view the reference docs for the current version, go here

Contributing

For guidelines on contributing please see CONTRIBUTING.md

Code of Conduct

Play nice; Play fair.

Requirements

  • Apache Kafka >=0.9
  • Node.js >=16
  • Linux/Mac
  • Windows?! See below
  • OpenSSL

Mac OS High Sierra / Mojave

OpenSSL has been upgraded in High Sierra and homebrew does not overwrite default system libraries. That means when building node-rdkafka, because you are using openssl, you need to tell the linker where to find it:

export CPPFLAGS=-I/usr/local/opt/openssl/include
export LDFLAGS=-L/usr/local/opt/openssl/lib

Then you can run npm install on your application to get it to build correctly.

NOTE: From the librdkafka docs

WARNING: Due to a bug in Apache Kafka 0.9.0.x, the ApiVersionRequest (as sent by the client when connecting to the broker) will be silently ignored by the broker causing the request to time out after 10 seconds. This causes client-broker connections to stall for 10 seconds during connection-setup before librdkafka falls back on the broker.version.fallback protocol features. The workaround is to explicitly configure api.version.request to false on clients communicating with <=0.9.0.x brokers.

Alpine

Using Alpine Linux? Check out the docs.

Windows

Windows build is not compiled from librdkafka source but it is rather linked against the appropriate version of NuGet librdkafka.redist static binary that gets downloaded from https://globalcdn.nuget.org/packages/librdkafka.redist.2.3.0.nupkg during installation. This download link can be changed using the environment variable NODE_RDKAFKA_NUGET_BASE_URL that defaults to https://globalcdn.nuget.org/packages/ when it's no set.

Requirements:

Note: I still do not recommend using node-rdkafka in production on Windows. This feature was in high demand and is provided to help develop, but we do not test against Windows, and windows support may lag behind Linux/Mac support because those platforms are the ones used to develop this library. Contributors are welcome if any Windows issues are found :)

Tests

This project includes two types of unit tests in this project:

  • end-to-end integration tests
  • unit tests

You can run both types of tests by using Makefile. Doing so calls mocha in your locally installed node_modules directory.

  • Before you run the tests, be sure to init and update the submodules:
    1. git submodule init
    2. git submodule update
  • To run the unit tests, you can run make lint or make test.
  • To run the integration tests, you must have a running Kafka installation available. By default, the test tries to connect to localhost:9092; however, you can supply the KAFKA_HOST environment variable to override this default behavior. Run make e2e.

Usage

You can install the node-rdkafka module like any other module:

npm install node-rdkafka

To use the module, you must require it.

const Kafka = require('node-rdkafka');

Configuration

You can pass many configuration options to librdkafka. A full list can be found in librdkafka's Configuration.md

Configuration keys that have the suffix _cb are designated as callbacks. Some of these keys are informational and you can choose to opt-in (for example, dr_cb). Others are callbacks designed to return a value, such as partitioner_cb.

Not all of these options are supported. The library will throw an error if the value you send in is invalid.

The library currently supports the following callbacks:

  • partitioner_cb
  • dr_cb or dr_msg_cb
  • event_cb
  • rebalance_cb (see Rebalancing)
  • offset_commit_cb (see Commits)

Librdkafka Methods

This library includes two utility functions for detecting the status of your installation. Please try to include these when making issue reports where applicable.

You can get the features supported by your compile of librdkafka by reading the variable "features" on the root of the node-rdkafka object.

const Kafka = require('node-rdkafka');
console.log(Kafka.features);

// #=> [ 'gzip', 'snappy', 'ssl', 'sasl', 'regex', 'lz4' ]

You can also get the version of librdkafka

const Kafka = require('node-rdkafka');
console.log(Kafka.librdkafkaVersion);

// #=> 2.3.0

Sending Messages

A Producer sends messages to Kafka. The Producer constructor takes a configuration object, as shown in the following example:

const producer = new Kafka.Producer({
  'metadata.broker.list': 'kafka-host1:9092,kafka-host2:9092'
});

A Producer requires only metadata.broker.list (the Kafka brokers) to be created. The values in this list are separated by commas. For other configuration options, see the Configuration.md file described previously.

The following example illustrates a list with several librdkafka options set.

const producer = new Kafka.Producer({
  'client.id': 'kafka',
  'metadata.broker.list': 'localhost:9092',
  'compression.codec': 'gzip',
  'retry.backoff.ms': 200,
  'message.send.max.retries': 10,
  'socket.keepalive.enable': true,
  'queue.buffering.max.messages': 100000,
  'queue.buffering.max.ms': 1000,
  'batch.num.messages': 1000000,
  'dr_cb': true
});

Stream API

You can easily use the Producer as a writable stream immediately after creation (as shown in the following example):

// Our producer with its Kafka brokers
// This call returns a new writable stream to our topic 'topic-name'
const stream = Kafka.Producer.createWriteStream({
  'metadata.broker.list': 'kafka-host1:9092,kafka-host2:9092'
}, {}, {
  topic: 'topic-name'
});

// Writes a message to the stream
const queuedSuccess = stream.write(Buffer.from('Awesome message'));

if (queuedSuccess) {
  console.log('We queued our message!');
} else {
  // Note that this only tells us if the stream's queue is full,
  // it does NOT tell us if the message got to Kafka!  See below...
  console.log('Too many messages in our queue already');
}

// NOTE: MAKE SURE TO LISTEN TO THIS IF YOU WANT THE STREAM TO BE DURABLE
// Otherwise, any error will bubble up as an uncaught exception.
stream.on('error', (err) => {
  // Here's where we'll know if something went wrong sending to Kafka
  console.error('Error in our kafka stream');
  console.error(err);
})

If you do not want your code to crash when an error happens, ensure you have an error listener on the stream. Most errors are not necessarily fatal, but the ones that are will immediately destroy the stream. If you use autoClose, the stream will close itself at the first sign of a problem.

Standard API

The Standard API is more performant, particularly when handling high volumes of messages. However, it requires more manual setup to use. The following example illustrates its use:

const producer = new Kafka.Producer({
  'metadata.broker.list': 'localhost:9092',
  'dr_cb': true
});

// Connect to the broker manually
producer.connect();

// Wait for the ready event before proceeding
producer.on('ready', () => {
  try {
    producer.produce(
      // Topic to send the message to
      'topic',
      // optionally we can manually specify a partition for the message
      // this defaults to -1 - which will use librdkafka's default partitioner (consistent random for keyed messages, random for unkeyed messages)
      null,
      // Message to send. Must be a buffer
      Buffer.from('Awesome message'),
      // for keyed messages, we also specify the key - note that this field is optional
      'Stormwind',
      // you can send a timestamp here. If your broker version supports it,
      // it will get added. Otherwise, we default to 0
      Date.now(),
      // you can send an opaque token here, which gets passed along
      // to your delivery reports
    );
  } catch (err) {
    console.error('A problem occurred when sending our message');
    console.error(err);
  }
});

// Any errors we encounter, including connection errors
producer.on('event.error', (err) => {
  console.error('Error from producer');
  console.error(err);
})

// We must either call .poll() manually after sending messages
// or set the producer to poll on an interval (.setPollInterval).
// Without this, we do not get delivery events and the queue
// will eventually fill up.
producer.setPollInterval(100);

To see the configuration options available to you, see the Configuration section.

Methods
Method Description
producer.connect() Connects to the broker.

The connect() method emits the ready event when it connects successfully. If it does not, the error will be passed through the callback.
producer.disconnect() Disconnects from the broker.

The disconnect() method emits the disconnected event when it has disconnected. If it does not, the error will be passed through the callback.
producer.poll() Polls the producer for delivery reports or other events to be transmitted via the emitter.

In order to get the events in librdkafka's queue to emit, you must call this regularly.
producer.setPollInterval(interval) Polls the producer on this interval, handling disconnections and reconnection. Set it to 0 to turn it off.
producer.produce(topic, partition, msg, key, timestamp, opaque) Sends a message.

The produce() method throws when produce would return an error. Ordinarily, this is just if the queue is full.
producer.flush(timeout, callback) Flush the librdkafka internal queue, sending all messages. Default timeout is 500ms
producer.initTransactions(timeout, callback) Initializes the transactional producer.
producer.beginTransaction(callback) Starts a new transaction.
producer.sendOffsetsToTransaction(offsets, consumer, timeout, callback) Sends consumed topic-partition-offsets to the broker, which will get committed along with the transaction.
producer.abortTransaction(timeout, callback) Aborts the ongoing transaction.
producer.commitTransaction(timeout, callback) Commits the ongoing transaction.
Events

Some configuration properties that end in _cb indicate that an event should be generated for that option. You can either:

  • provide a value of true and react to the event
  • provide a callback function directly

The following example illustrates an event:

const producer = new Kafka.Producer({
  'client.id': 'my-client', // Specifies an identifier to use to help trace activity in Kafka
  'metadata.broker.list': 'localhost:9092', // Connect to a Kafka instance on localhost
  'dr_cb': true // Specifies that we want a delivery-report event to be generated
});

// Poll for events every 100 ms
producer.setPollInterval(100);

producer.on('delivery-report', (err, report) => {
  // Report of delivery statistics here:
  //
  console.log(report);
});

The following table describes types of events.

Event Description
disconnected The disconnected event is emitted when the broker has disconnected.

This event is emitted only when .disconnect is called. The wrapper will always try to reconnect otherwise.
ready The ready event is emitted when the Producer is ready to send messages.
event The event event is emitted when librdkafka reports an event (if you opted in via the event_cb option).
event.log The event.log event is emitted when logging events come in (if you opted into logging via the event_cb option).

You will need to set a value for debug if you want to send information.
event.stats The event.stats event is emitted when librdkafka reports stats (if you opted in by setting the statistics.interval.ms to a non-zero value).
event.error The event.error event is emitted when librdkafka reports an error
event.throttle The event.throttle event emitted when librdkafka reports throttling.
delivery-report The delivery-report event is emitted when a delivery report has been found via polling.

To use this event, you must set request.required.acks to 1 or -1 in topic configuration and dr_cb (or dr_msg_cb if you want the report to contain the message payload) to true in the Producer constructor options.

Higher Level Producer

The higher level producer is a variant of the producer which can propagate callbacks to you upon message delivery.

const producer = new Kafka.HighLevelProducer({
  'metadata.broker.list': 'localhost:9092',
});

This will enrich the produce call so it will have a callback to tell you when the message has been delivered. You lose the ability to specify opaque tokens.

producer.produce(topicName, null, Buffer.from('alliance4ever'), null, Date.now(), (err, offset) => {
  // The offset if our acknowledgement level allows us to receive delivery offsets
  console.log(offset);
});

Additionally you can add serializers to modify the value of a produce for a key or value before it is sent over to Kafka.

producer.setValueSerializer((value) => {
  return Buffer.from(JSON.stringify(value));
});

Otherwise the behavior of the class should be exactly the same.

Kafka.KafkaConsumer

To read messages from Kafka, you use a KafkaConsumer. You instantiate a KafkaConsumer object as follows:

const consumer = new Kafka.KafkaConsumer({
  'group.id': 'kafka',
  'metadata.broker.list': 'localhost:9092',
}, {});

The first parameter is the global config, while the second parameter is the topic config that gets applied to all subscribed topics. To view a list of all supported configuration properties, see the Configuration.md file described previously. Look for the C and * keys.

The group.id and metadata.broker.list properties are required for a consumer.

Rebalancing

Rebalancing is managed internally by librdkafka by default. If you would like to override this functionality, you may provide your own logic as a rebalance callback.

const consumer = new Kafka.KafkaConsumer({
  'group.id': 'kafka',
  'metadata.broker.list': 'localhost:9092',
  'rebalance_cb': (err, assignment) => {

    if (err.code === Kafka.CODES.ERRORS.ERR__ASSIGN_PARTITIONS) {
      // Note: this can throw when you are disconnected. Take care and wrap it in
      // a try catch if that matters to you
      this.assign(assignment);
    } else if (err.code == Kafka.CODES.ERRORS.ERR__REVOKE_PARTITIONS){
      // Same as above
      this.unassign();
    } else {
      // We had a real error
      console.error(err);
    }

  }
})

this is bound to the KafkaConsumer you have created. By specifying a rebalance_cb you can also listen to the rebalance event as an emitted event. This event is not emitted when using the internal librdkafka rebalancer.

Commits

When you commit in node-rdkafka, the standard way is to queue the commit request up with the next librdkafka request to the broker. When doing this, there isn't a way to know the result of the commit. Luckily there is another callback you can listen to to get this information

const consumer = new Kafka.KafkaConsumer({
  'group.id': 'kafka',
  'metadata.broker.list': 'localhost:9092',
  'offset_commit_cb': (err, topicPartitions) => {

    if (err) {
      // There was an error committing
      console.error(err);
    } else {
      // Commit went through. Let's log the topic partitions
      console.log(topicPartitions);
    }

  }
})

this is bound to the KafkaConsumer you have created. By specifying an offset_commit_cb you can also listen to the offset.commit event as an emitted event. It receives an error and the list of topic partitions as argument. This is not emitted unless opted in.

Message Structure

Messages that are returned by the KafkaConsumer have the following structure.

{
  value: Buffer.from('hi'), // message contents as a Buffer
  size: 2, // size of the message, in bytes
  topic: 'librdtesting-01', // topic the message comes from
  offset: 1337, // offset the message was read from
  partition: 1, // partition the message was on
  key: 'someKey', // key of the message if present
  timestamp: 1510325354780 // timestamp of message creation
}

Stream API

The stream API is the easiest way to consume messages. The following example illustrates the use of the stream API:

// Read from the librdtesting-01 topic... note that this creates a new stream on each call!
const stream = KafkaConsumer.createReadStream(globalConfig, topicConfig, {
  topics: ['librdtesting-01']
});

stream.on('data', (message) => {
  console.log('Got message');
  console.log(message.value.toString());
});

You can also get the consumer from the streamConsumer, for using consumer methods. The following example illustrates that:

stream.consumer.commit(); // Commits all locally stored offsets

Standard API

You can also use the Standard API and manage callbacks and events yourself. You can choose different modes for consuming messages:

  • Flowing mode. This mode flows all of the messages it can read by maintaining an infinite loop in the event loop. It only stops when it detects the consumer has issued the unsubscribe or disconnect method.
  • Non-flowing mode. This mode reads a single message from Kafka at a time manually.

The following example illustrates flowing mode:

// Flowing mode
consumer.connect();

consumer
  .on('ready', () => {
    consumer.subscribe(['librdtesting-01']);

    // Consume from the librdtesting-01 topic. This is what determines
    // the mode we are running in. By not specifying a callback (or specifying
    // only a callback) we get messages as soon as they are available.
    consumer.consume();
  })
  .on('data', (data) => {
    // Output the actual message contents
    console.log(data.value.toString());
  });

The following example illustrates non-flowing mode:

// Non-flowing mode
consumer.connect();

consumer
  .on('ready', () => {
    // Subscribe to the librdtesting-01 topic
    // This makes subsequent consumes read from that topic.
    consumer.subscribe(['librdtesting-01']);

    // Read one message every 1000 milliseconds
    setInterval(() => {
      consumer.consume(1);
    }, 1000);
  })
  .on('data', (data) => {
    console.log('Message found!  Contents below.');
    console.log(data.value.toString());
  });

The following table lists important methods for this API.

Method Description
consumer.connect() Connects to the broker.

The connect() emits the event ready when it has successfully connected. If it does not, the error will be passed through the callback.
consumer.disconnect() Disconnects from the broker.

The disconnect() method emits disconnected when it has disconnected. If it does not, the error will be passed through the callback.
consumer.subscribe(topics) Subscribes to an array of topics.
consumer.unsubscribe() Unsubscribes from the currently subscribed topics.

You cannot subscribe to different topics without calling the unsubscribe() method first.
consumer.consume(cb) Gets messages from the existing subscription as quickly as possible. If cb is specified, invokes cb(err, message).

This method keeps a background thread running to do the work. Note that the number of threads in nodejs process is limited by UV_THREADPOOL_SIZE (default value is 4) and using up all of them blocks other parts of the application that need threads. If you need multiple consumers then consider increasing UV_THREADPOOL_SIZE or using consumer.consume(number, cb) instead.
consumer.consume(number, cb) Gets number of messages from the existing subscription. If cb is specified, invokes cb(err, message).
consumer.commit() Commits all locally stored offsets
consumer.commit(topicPartition) Commits offsets specified by the topic partition
consumer.commitMessage(message) Commits the offsets specified by the message

The following table lists events for this API.

Event Description
data When using the Standard API consumed messages are emitted in this event.
partition.eof When using Standard API and the configuration option enable.partition.eof is set, partition.eof events are emitted in this event. The event contains topic, partition and offset properties.
warning The event is emitted in case of UNKNOWN_TOPIC_OR_PART or TOPIC_AUTHORIZATION_FAILED errors when consuming in Flowing mode. Since the consumer will continue working if the error is still happening, the warning event should reappear after the next metadata refresh. To control the metadata refresh rate set topic.metadata.refresh.interval.ms property. Once you resolve the error, you can manually call getMetadata to speed up consumer recovery.
rebalance The rebalance event is emitted when the consumer group is rebalanced.

This event is only emitted if the rebalance_cb configuration is set to a function or set to true
disconnected The disconnected event is emitted when the broker disconnects.

This event is only emitted when .disconnect is called. The wrapper will always try to reconnect otherwise.
ready The ready event is emitted when the Consumer is ready to read messages.
event The event event is emitted when librdkafka reports an event (if you opted in via the event_cb option).
event.log The event.log event is emitted when logging events occur (if you opted in for logging via the event_cb option).

You will need to set a value for debug if you want information to send.
event.stats The event.stats event is emitted when librdkafka reports stats (if you opted in by setting the statistics.interval.ms to a non-zero value).
event.error The event.error event is emitted when librdkafka reports an error
event.throttle The event.throttle event is emitted when librdkafka reports throttling.

Reading current offsets from the broker for a topic

Some times you find yourself in the situation where you need to know the latest (and earliest) offset for one of your topics. Connected producers and consumers both allow you to query for these through queryWaterMarkOffsets like follows:

const timeout = 5000, partition = 0;
consumer.queryWatermarkOffsets('my-topic', partition, timeout, (err, offsets) => {
  const high = offsets.highOffset;
  const low = offsets.lowOffset;
});

producer.queryWatermarkOffsets('my-topic', partition, timeout, (err, offsets) => {
  const high = offsets.highOffset;
  const low = offsets.lowOffset;
});

An error will be returned if the client was not connected or the request timed out within the specified interval.

Metadata

Both Kafka.Producer and Kafka.KafkaConsumer include a getMetadata method to retrieve metadata from Kafka.

Getting metadata on any connection returns the following data structure:

{
  orig_broker_id: 1,
  orig_broker_name: "broker_name",
  brokers: [
    {
      id: 1,
      host: 'localhost',
      port: 40
    }
  ],
  topics: [
    {
      name: 'awesome-topic',
      partitions: [
        {
          id: 1,
          leader: 20,
          replicas: [1, 2],
          isrs: [1, 2]
        }
      ]
    }
  ]
}

The following example illustrates how to use the getMetadata method.

When fetching metadata for a specific topic, if a topic reference does not exist, one is created using the default config. Please see the documentation on Client.getMetadata if you want to set configuration parameters, e.g. acks, on a topic to produce messages to.

const opts = {
  topic: 'librdtesting-01',
  timeout: 10000
};

producer.getMetadata(opts, (err, metadata) => {
  if (err) {
    console.error('Error getting metadata');
    console.error(err);
  } else {
    console.log('Got metadata');
    console.log(metadata);
  }
});

Admin Client

node-rdkafka now supports the admin client for creating, deleting, and scaling out topics. The librdkafka APIs also support altering configuration of topics and broker, but that is not currently implemented.

To create an Admin client, you can do as follows:

const Kafka = require('node-rdkafka');

const client = Kafka.AdminClient.create({
  'client.id': 'kafka-admin',
  'metadata.broker.list': 'broker01'
});

This will instantiate the AdminClient, which will allow the calling of the admin methods.

client.createTopic({
  topic: topicName,
  num_partitions: 1,
  replication_factor: 1
}, (err) => {
  // Done!
});

All of the admin api methods can have an optional timeout as their penultimate parameter.

The following table lists important methods for this API.

Method Description
client.disconnect() Destroy the admin client, making it invalid for further use.
client.createTopic(topic, timeout, cb) Create a topic on the broker with the given configuration. See JS doc for more on structure of the topic object
client.deleteTopic(topicName, timeout, cb) Delete a topic of the given name
client.createPartitions(topicName, desiredPartitions, timeout, cb) Create partitions until the topic has the desired number of partitions.

Check the tests for an example of how to use this API!

node-rdkafka's People

Contributors

0x7f avatar alexander-alvarez avatar ankon avatar atamon avatar battlecow avatar cjlarose avatar codeburke avatar dchesterton avatar edoardocomar avatar fabianschmitthenner avatar garywilber avatar geoffreyhervet avatar idangozlan avatar iradul avatar jaaprood avatar jpdstan avatar macabu avatar martijnimhoff avatar mccaig avatar mimaison avatar opsb avatar pchelolo avatar pthm avatar raunc avatar rusty0412 avatar sam-github avatar sgenoud avatar tvainika avatar webmakersteve avatar yunnysunny avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-rdkafka's Issues

README still shows older message structure for message value

Message Structure

Messages that are returned by the KafkaConsumer have the following structure.

{
message: new Buffer('hi'), // message contents as a Buffer <--- should be 'value' not 'message'
size: 2, // size of the message, in bytes
topic: 'librdtesting-01', // topic the message comes from
offset: 1337, // offset the message was read from
partition: 1, // partition the message was on
key: 'someKey' // key of the message if present
}

Producer/Consumer not respecting message/key null vs empty

When sending a message with an empty key (""), it is received as an undefined key by the Javascript consumer and a null key by the Java consumer. It should be received as an empty string.

Also currently, the producer doesn't allow sending messages with a null payload (can be done in Java). Similarly the consumer sees messages with a null payload (sent from Java) as empty.

Committing offsets manually

I would like to perform each message commit to Kafka manually, I'm using the commit function like this :

consumer.commit({ topic: metadata.topic, partition: metadata.partition, offset: metadata.offset }, function (err, data) { ... }

Could you tell me if this is a good way of using it ?

The problem is that even if the callback is triggered without errors, if I restart the consumer, it consumes the last committed message again (restart is made by calling disconnect and then reconnect or simply calling process.exit() function).

producer acks property may not be honored

Even when creating a producer Topic object with code like this

        // Create a topic object for the Producer to allow passing topic settings
        var topicOpts = { 'request.required.acks': -1 };
        var topic = producer.Topic(topicName, topicOpts);
        console.log('Topic object created with opts ' + JSON.stringify(topicOpts));

                producer.once('delivery-report', deliveryReportListener);

                var value = new Buffer('This is a test message #' + counter);
                var key = 'key';
                counter++;
                var partition = -1;

                    producer.produce(topic, partition, value, key);
                    // Keep calling poll() to get delivery reports

the produce request over the wire is sending acks=1 (the default).
We also tried setting the property value as a string '-1' or 'all'. No joy.

Note that the property set in code is validated, i.e. an Error is thrown if the name or value are unknown. However valid values 0 and -1 are not honored.

We could verify this enabling tracing on the broker's kafkaApi
sample log4j conf line:

log4j.logger.kafka.server.KafkaApis=TRACE, requestAppender

sample broker log:

[2016-11-29 17:53:10,103] TRACE [KafkaApi-0] Handling request:{api_key=0,api_version=1,correlation_id=114,client_id=mh-node-console-sample-producer} -- {acks=1,timeout=5000,topic_data=[{topic=mh-nodejs-console-sample-topic,data=[{partition=0,record_set=java.nio.HeapByteBuffer[pos=0 lim=56 cap=56]}]}]} from connection 192.168.1.67:9092-192.168.1.67:52624;securityProtocol:PLAINTEXT,principal:User:ANONYMOUS (kafka.server.KafkaApis)

can't use consumeLoop with assignments

In kafka-consumer.js consume([topics]) calls subscribe([topics])
therefore client code like this

  consumer.assign([{ topic:topicName, partition:0, offset:2950}]);
  consumer.consume([ topicName ]); 

does not work, because the assignment (i.e. unbalanced consumer with manual partition assignment) is overridden by the hidden subscribe (i.e. causing a shift to the balanced consumer)

the consumer API should allow both these use cases:

  consumer.assign([{ topic:topicName, partition:0, offset:2950}]);
  consumer.consume();  //invoke the loop

and

  consumer.subscribe([topics]);
  consumer.consume(); //invoke the loop

The consume method should not require again the list of topics that have been subscribed or assigned to.

This behavior looks more consistent with the Java client, though removing the argument from consume currently invokes consumeOne

What's the properly way to get the last offset

My use case : I want to get an history of messages, for example get the N last logs in a particular topic.
So I need to know the last offset for use this.assign().

How can I do that cleanly ?

:)

Unable to specify topic configurations in produce()

Following the latest update, producer.produce() now only accepts topic as a string. This prevents us from setting topic properties like request.required.acks.

Looking at the code, it all boils down to the way produce() evaluates if it's called from the old API or the new one.

Clarification: Consumer `error` event handling

The Consumer instance emits an error event. As per the examples and documentation, this is an error originating from Kafka and we should handle it as we see fit.

I just wanted to clarify that the above statement is correct as in node, the error event means that the runtime entered an unrecoverable state and the process should be terminated.

If that's the case, would you consider renaming the event to kafkaError for clarity and disambiguation?

Consumer.position() returns an empty array

I Expected consumer.position() to return a non empty array, when connected and consuming.

However, even in a handlerconsumer.on('data', function()
when messages are being received, the array is always empty.

Issue when a broker is down

Hi,

My setup is as follows, I have two instances of Kafka running in docker containers, a producer and a consumer script.
When I kill the first broker and restart the producer / consumer script they timeout and "ready" event is never emitted.
{"message":"Local: Timed out","code":-185,"errno":-185,"origin":"kafka"}

The issue is that when calling rd_kafka_metadata, there is a moment when both brokers' state is NOT up and the function that decides which broker to talk to, rd_kafka_broker_any, returns the first broker, rd_kafka_metadata returns a timeout error (because that broker is down).
In the JS code's connect method we return and never emit "ready" event.

This error seems to be fixed on librdkafka's side in the current master version as rd_kafka_metadata's function was partly rewritten.

Did you observer this behavior during your tests or am I doing something wrong?

Consumer can't read committed offsets

The array of topicPartitions in the commited(...) callback is always of zero length
this happens both after a sync or asynch commit, e.g.

     consumer.commit(message, function(err) {
          t.ifError(err);
          consumer.committed(10000, function(err, tp){
            t.ifError(err);
            t.equal(1, tp.length); ///booom

test is commented out in PR #66

Producer sends message with key length 0 even when key is present - only on Ubuntu 14.04 LTS

Hi we found a really mind boggling problem

Using the following sample program, the key is sent with a length of 0 on Ubuntu 14.04.5
(just retested on a clean VM!)
Note that the code works on both Mac OS (10.11) and Ubuntu 16.04

var Kafka = require('../');

var producer = new Kafka.Producer({
  //'debug' : 'all',  
  'metadata.broker.list': ...,
  'dr_cb': true
});

console.log('producer created');

//Connect to the broker manually
producer.connect();

var counter = 0;
producer.on('delivery-report', function(report) {
  // Report of delivery statistics here:
  console.log(report);
  counter++;
  if (counter === 10) producer.disconnect();
});

//Any errors we encounter, including connection errors
producer.on('error', function(err) {
  console.error('Error from producer');
  console.error(err);
});

//Wait for the ready event before proceeding
producer.on('ready', function() {
  console.log('producer ready');
  // Create a Topic object with any options our Producer
  // should use when writing to that topic.
  var topic = producer.Topic('testtopic', {
    // Make the Kafka broker acknowledge our message (optional)
    'request.required.acks': 1
  });

  console.log('topic created');

  for (var i = 0; i < 10; i++) {
    var message = new Buffer('message' +i);
    var key = "k-"+i;
    var partition = -1;
    producer.produce(topic, partition, message, key);
  }

  for (var j = 0; j < 50; j++) {
    setTimeout(function() {
      producer.poll();
    }, 1000);
  }
});

we could see that in NAN_METHOD(Producer::NodeProduce) in producer.cpp
if we add a debug after the key is created, around line 330:

    // This will just go out of scope and we don't send it anywhere,
    // since it is copied there is no need to delete it
    key = &keyString;
  }
 //add debug printf:
  printf("key->c_str=%s key->size=%d\n", key->c_str(), key->size());

  Producer* producer = ObjectWrap::Unwrap<Producer>(info.This());

then we can see that
key->c_str() will print the expected string ... but key->size() is 0 !!

sample output:

ubuntu@ubuntu:~/node-rdkafka/mickael-examples$ node producer2.js
producer created
producer ready
topic created
key->c_str=k-0 key->size=0
key->c_str=k-1 key->size=0
key->c_str=k-2 key->size=0
key->c_str=k-3 key->size=0
key->c_str=k-4 key->size=0
key->c_str=k-5 key->size=0
key->c_str=k-6 key->size=0
key->c_str=k-7 key->size=0
key->c_str=k-8 key->size=0
key->c_str=k-9 key->size=0
{ topic_name: 'testtopic', partition: 0, offset: 0, key: null }
{ topic_name: 'testtopic', partition: 0, offset: 0, key: null }
{ topic_name: 'testtopic', partition: 0, offset: 0, key: null }
{ topic_name: 'testtopic', partition: 0, offset: 0, key: null }
{ topic_name: 'testtopic', partition: 0, offset: 0, key: null }
{ topic_name: 'testtopic', partition: 0, offset: 0, key: null }
{ topic_name: 'testtopic', partition: 0, offset: 0, key: null }
{ topic_name: 'testtopic', partition: 0, offset: 0, key: null }
{ topic_name: 'testtopic', partition: 0, offset: 0, key: null }
{ topic_name: 'testtopic',
  partition: 0,
  offset: 171866,
  key: null }

After having seen this on a real machine, I would not believe it if I didn't replicate in a VM myself.

Verified with node 6.9.1 and 6.8.0 and 4.6.1

node-rdkafka does not compile on Windows

Installing on windows does not work, and there is no way to make it work currently.

This is a low priority feature, but keeping it here so it can be tracked properly.

Producer delivery reports - cannot figure out which message they relate to

The current delivery-report object contains key, offset, topic ... but this is not enough to figure out about which produced message they refer to.
Unless you wait for every single report after sending a single message.

Looking under the covers, node-rdkafka is using the full dr_cb_msg librdkafka callback, but does not put the message payload in the delivery report.

Investigated with @mimaison

sasl.username / password not supported

Please consider this a feature request, I suppose.

Although cited in https://raw.githubusercontent.com/edenhill/librdkafka/2213fb29f98a7a73f22da21ef85e0783f6fd67c4/CONFIGURATION.md, they throw an error as per below.

/node-rdkafka/lib/client.js:59
  this._client = new SubClientType(globalConf, topicConf);
                 ^

Error: No such configuration property: "sasl.username"
    at Error (native)
    at Producer.Client (/Users/eshao/wsp/tty1/workers/node_modules/node-rdkafka/lib/client.js:59:18)
    at new Producer (/Users/eshao/wsp/tty1/workers/node_modules/node-rdkafka/lib/producer.js:71:10)
    at Object.<anonymous> (/Users/eshao/wsp/tty1/workers/k2.js:9:16)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)
    at Module.runMain (module.js:575:10)

Consumer issues with `conf.rebalance_cb = true`

if conf.rebalance_cb is set to true
the consumer does not emit the rebalance event - which could be used to mimick seek by using assign as suggested by @edenhill

BTW with conf.rebalance_cb = true, the consumer calls an undefined unassign` JS function

kafka-consumer.js line 54

        self.unassign(e.assignment);

Improvements to the delivery report using the opaque

The addition of the opaque is great (performance/flexibility) but:

  1. In order to have the value in the delivery report, it forces the user to make a "clunky" API call.
    For example:
var value = new Buffer('...');
var key = 'key';
producer.produce('topic', null, value, key, {value: value, key: key});

It feels a lot more natural to automatically get the opaque populated in case the user has not passed in a custom value. And this does not cause any performance penalty as it reuses the opaque mechanism.

  1. The key member of the delivery report object still suffers from the null/empty issue. It can be correctly retrieve for free from the opaque as described above, hence removing the need to have native code handling the key.

Although people generally consider to be small, in Kafka it is actually the same datatype as the payload and can be arbitrary big. So reusing the opaque to pass it along the payload make sense.

node-rdkafka not using default partitioner when none set.

I had made a minor change in #22 that i thought would fix the default partitioning behavior, but some subsequent tests seem to indicate that this fix hasn't worked.

Manually setting partition to -1 in the produce function works, but when not specifying a partition, all my messages are still being put into partition 0.

Note that i need to verify that i haven't done something boneheaded in my own code (like using the wrong branch..) and will review in more detail this evening, but wanted to get this in here in case anyone else sees this issue. Im not experienced in c++ but will see if i can at least add some unit tests for the behaviour.

Producer e2e tests, deadlock after using Topic object in produce

As discussed in #62 (comment), if the testcase called should produce a message to a Topic object is executed before should get 100% deliverability, the latter testcase hangs.
If in the first testcase we change the topic object to be a string, then the second testcase doesn't hang.

We've also managed to reproduce this issue in a small testcase script (see enclosed). It's basically the same logic from the tests but inlined and without any mocha deps to be run with node directly. Note that if we enable DEBUG the script doesn't hang. Like in the e2e tests, changing the topic object to a string makes it pass.

testcase.txt

Consumed message has no key

Hi, we noticed that a message as received by a consumer has no key field.
Looking at the code, it seem not that complicated to add, we'll open a PR

Assign / Unassign issue

Hi,

I've been trying to resolve an issue when adding consumer scripts with the rebalance callback.

  1. Run X instances of the Consumer
  2. Run another Consumer
  3. Wait for rebalance and repeat 2.
  4. From time to time one of the Consumers can't rebalance and it starts receiving {"message":"Local: Erroneous state","code":-172,"errno":-172,"origin":"kafka"} errors

This is what I get when running in gdb.

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7fe7780 (LWP 22887)]
0x00007ffff490b884 in NodeKafka::Consumer::NodeGetAssignments (info=...) at ../src/consumer.cc:493
493 Nan::Newv8::String(part->topic()).ToLocalChecked());

(I'm not entirely sure this is the reason)

Error during compilation

Hello guys,

I try to install node-rdkafka

$ npm i node-rdkafka

And get the following error

CC(target) Release/obj.target/librdkafka/deps/librdkafka/src/rdkafka_sasl.o
../deps/librdkafka/src/rdkafka_sasl.c:35:23: fatal error: sasl/sasl.h: Aucun fichier ou dossier de ce type
 #include <sasl/sasl.h>
                       ^
compilation terminated.
deps/librdkafka.target.mk:147: recipe for target 'Release/obj.target/librdkafka/deps/librdkafka/src/rdkafka_sasl.o' failed
make: *** [Release/obj.target/librdkafka/deps/librdkafka/src/rdkafka_sasl.o] Error 1
make: Leaving directory '/home/tristan/Documents/node_modules/node-rdkafka/build'
gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:276:23)
gyp ERR! stack     at emitTwo (events.js:106:13)
gyp ERR! stack     at ChildProcess.emit (events.js:191:7)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:215:12)
gyp ERR! System Linux 3.16.0-4-amd64
gyp ERR! command "/usr/local/bin/node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /home/tristan/Documents/node_modules/node-rdkafka
gyp ERR! node -v v7.4.0
gyp ERR! node-gyp -v v3.4.0
gyp ERR! not ok

I use node 7.4.0 and npm 4.0.5

Producer core dump

Am in the process of testing PR #42 as a fix forย Issue #5, and ran into this:

const Kafka = require('node-rdkafka');
const producer = new Kafka.Producer({
    'metadata.broker.list': 'localhost:9092',
});
producer.connect(undefined, () => {
    producer.produce({
        topic: 'test',
        message: '{"f": 1}'
    });
});
nodejs: ../node_modules/nan/nan_object_wrap.h:33: static T* Nan::ObjectWrap::Unwrap(v8::Local<v8::Object>) [with T = NodeKafka::Topic]: Assertion `object->InternalFieldCount() > 0' failed.
Aborted (core dumped)

node-rdkafka compiled at commit 62e53c22cafe93b9697fc9ff6fc0daa7c2670da2.

Please expose topic name on Producer.Topic instance

Hello,

I am developing a kafka-avro library that depends on your library and it adds a layer on top for Avro Schemas validation and serialization. The Producer.Topic class will return a magic object which has no properties, I can see it is used with rdkafka and local bindings.

I'd like to kindly ask if it's possible that you expose the topic name as a string on the returned instance so I can use it on my end to apply Avro schema validations and serialization.

Because of that, I had to change the signature of the produce() method to 5 arguments to also include the topic name as a string.

librdkafka assertion on subscribe

This is just FYI since I don't think it's a bug in librdkafka, not here. When I create many processes with consumers that belong to the same consumer group, sometimes I get librdkafka assert on partition assignment: confluentinc/librdkafka#761 For now I've solved the problem by commenting out the code that sets up the rebalance_cb as it seem to fix that and use my own fork, but it would be nice to understand it better and fix. No particular steps to reproduce as well as no small test case unfortunately.

Issue with key parameter when using Producer

I'm trying to produce keyed messages to Kafka but for some reason the key value I get back during delivery report events is null

produce({ message: data.content, key: data.key, topic: ... }, ... )

{"topic_name":"vpn","partition":0,"offset":1137763,"key":null}

Any ideas?

Incorrect ISR list in Metadata response

Calling getMetadata(), we are getting an invalid list for the ISRs:

{"orig_broker_id":0,"orig_broker_name":"sasl_ssl://kafka01-prod01.messagehub.services.us-south.bluemix.net:9093/0","topics":[{"name":"mh-nodejs-console-sample-topic","partitions":[{"id":0,"leader":0,"replicas":[0,1,4],"isrs":[null,null,null,1]}]}],"brokers":[{"id":2,"host":"kafka03-prod01.messagehub.services.us-south.bluemix.net","port":9093},{"id":4,"host":"kafka05-prod01.messagehub.services.us-south.bluemix.net","port":9093},{"id":1,"host":"kafka02-prod01.messagehub.services.us-south.bluemix.net","port":9093},{"id":3,"host":"kafka04-prod01.messagehub.services.us-south.bluemix.net","port":9093},{"id":0,"host":"kafka01-prod01.messagehub.services.us-south.bluemix.net","port":9093}]}

At the time, all the replicas were in-sync according to Kafka

Question about behavior of unsubscribe()

I see that Consumer::Unsubscribe returns RdKafka::ERR__STATE if it is called on a not-yet-subscribed consumer. However, there doesn't seem to be a way to infer subscription state via the node client. Should I manage this state myself, or can we add a consumer.subscriptions() method to get the current subscriptions? And/or, perhaps Consumer::Unsubscribe should just be a no-op if the consumer has no current subscriptions.

While looking into this, I noticed that consumer.subscribe() takes a cb, but as far as I can tell it is not invoked. Should it be?

Native crash when running npm test with node 6

That's weird, but I get a native crash simply by running npm test in the repository when using node.js 6.0.0. It's not completely clear where is it coming from, but here's a relevant part of the crash dump:

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_CRASH (SIGABRT)
Exception Codes:       0x0000000000000000, 0x0000000000000000

Application Specific Information:
abort() called
*** error for object 0x7f9602e014f0: pointer being freed was not allocated


Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib          0x00007fff8f610f06 __pthread_kill + 10
1   libsystem_pthread.dylib         0x00007fff9cd414ec pthread_kill + 90
2   libsystem_c.dylib               0x00007fff9e9cb6e7 abort + 129
3   libsystem_malloc.dylib          0x00007fff8aef5041 free + 425
4   node-librdkafka.node            0x0000000109855578 RdKafka::ConfImpl::~ConfImpl() + 14 (rdkafkacpp_int.h:228)
5   node-librdkafka.node            0x0000000109849ffb NodeKafka::Connection::~Connection() + 67 (connection.cc:67)
6   node-librdkafka.node            0x000000010984fa36 NodeKafka::Producer::~Producer() + 14 (producer.cc:40)
7   node                            0x0000000107ddde43 v8::internal::GlobalHandles::DispatchPendingPhantomCallbacks(bool) + 163
8   node                            0x0000000107dde171 v8::internal::GlobalHandles::PostGarbageCollectionProcessing(v8::internal::GarbageCollector, v8::GCCallbackFlags) + 49
9   node                            0x0000000107deb35f v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) + 1871
10  node                            0x0000000107dea79d v8::internal::Heap::CollectGarbage(v8::internal::GarbageCollector, char const*, char const*, v8::GCCallbackFlags) + 717
11  node                            0x0000000107dae6bb v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationSpace) + 107
12  node                            0x0000000107ff31bb v8::internal::Runtime_AllocateInTargetSpace(int, v8::internal::Object**, v8::internal::Isolate*) + 139

Seems like the 'RdKafka::Conf' object deletion crashes internally - perhaps some of the properties were garbage-collected by v8?

Questions about topic consumption

Using the standard API,

  • Is it possible to know, after calling consumer.subscribe(topics), when the consumer is actually "ready" to consume those topics? Since there is no relevant callback, is manually parsing librdkafka's log events the only way?
  • When subscribing to another topic, we have to call consumer.unsubscribe() first. Assuming commits are turned off, does that mean we could miss some messages between the call to unsubscribe() and the next call to subscribe(topics)?

Thank you.

Lock contention in flowing mode for certain event rates

For certain event rates (for me it happens with about 10 events per second) some contention is happening and the consumer in the flowing mode starts to lag. The lag goes larger and larger and it seems unbound. I'm not sure if you'd be able to reproduce with the exact test case, if not please try playing with the number of events produced in each loop iteration, there's some sweet spot when the contention is happening.

My limited investigation shows that the amount of time ConsumerLoop worker is waiting after reaching the end of partition has critical impact on this issue.

Here's a test case:

const consumer = new kafka.KafkaConsumer({
    'group.id': 'my_test_consumer_group5 ',
    'metadata.broker.list': 'localhost:9092',
    'enable.auto.commit': 'false',
    'client.id': '1234567'
}, {
    'auto.offset.reset': 'largest'
});
consumer.connect();
consumer
.on('ready', () => {
    consumer.consume([ 'test_dc.mediawiki.revision-create' ], (e, kafkaMessage) => {
        if (e) {
            console.log(e);
        }

        console.log('Message', new Date(), new Date(kafkaMessage.value.toString()), new Date() - new Date(kafkaMessage.value.toString()));
    });
});

Producer script:

#/bin/bash
while :
do
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
     	date | kafkacat -b localhost:9092 -t test_dc.mediawiki.revision-create -p 0	
	sleep 1
done

Example output:

Message Wed Dec 14 2016 15:51:03 GMT-0800 (PST) Wed Dec 14 2016 15:50:57 GMT-0800 (PST) 6564
Message Wed Dec 14 2016 15:51:04 GMT-0800 (PST) Wed Dec 14 2016 15:50:58 GMT-0800 (PST) 6568
Message Wed Dec 14 2016 15:51:04 GMT-0800 (PST) Wed Dec 14 2016 15:50:58 GMT-0800 (PST) 6568
Message Wed Dec 14 2016 15:51:04 GMT-0800 (PST) Wed Dec 14 2016 15:50:58 GMT-0800 (PST) 6568
Message Wed Dec 14 2016 15:51:05 GMT-0800 (PST) Wed Dec 14 2016 15:51:00 GMT-0800 (PST) 5573
Message Wed Dec 14 2016 15:51:06 GMT-0800 (PST) Wed Dec 14 2016 15:51:00 GMT-0800 (PST) 6576
Message Wed Dec 14 2016 15:51:06 GMT-0800 (PST) Wed Dec 14 2016 15:51:00 GMT-0800 (PST) 6576
Message Wed Dec 14 2016 15:51:07 GMT-0800 (PST) Wed Dec 14 2016 15:51:00 GMT-0800 (PST) 7580
Message Wed Dec 14 2016 15:51:07 GMT-0800 (PST) Wed Dec 14 2016 15:51:01 GMT-0800 (PST) 6581
Message Wed Dec 14 2016 15:51:08 GMT-0800 (PST) Wed Dec 14 2016 15:51:01 GMT-0800 (PST) 7581
Message Wed Dec 14 2016 15:51:08 GMT-0800 (PST) Wed Dec 14 2016 15:51:01 GMT-0800 (PST) 7581
Message Wed Dec 14 2016 15:51:09 GMT-0800 (PST) Wed Dec 14 2016 15:51:01 GMT-0800 (PST) 8585
Message Wed Dec 14 2016 15:51:09 GMT-0800 (PST) Wed Dec 14 2016 15:51:02 GMT-0800 (PST) 7585
Message Wed Dec 14 2016 15:51:09 GMT-0800 (PST) Wed Dec 14 2016 15:51:02 GMT-0800 (PST) 7585

Kafka version 0.9.0.1, node-rdkafka version 0.6.1

Application doesn't finish gracefully after client.disconnect

I expect that after a producer/consumer is disconnected it should destroy all RdKafka objects and let the application exit, however it doesn't happen.

Code example:

const Kafka = require('node-rdkafka');
const producer = new Kafka.Producer({
    'metadata.broker.list': 'localhost:9092'
});
producer.connect(undefined, () => {
    producer.produce({
        topic: 'test_dc.resource_change',
        message: 'test'
    }, () => {
        producer.disconnect(() => {
            console.log('Disconnected');
        });
    });
});

Versions: kafkaOS X El Capitan
Expected behaviour: application sends one message to the topic and shuts down.
Actual behaviour: application sends one message to the topic but continues running.

A bit of investigation: gdb shows that we have 8 threads running after disconnect was called:

  Id   Target Id         Frame 
* 1    Thread 0x1203 of process 55925 0x00007fff8f611eca in kevent () from /usr/lib/system/libsystem_kernel.dylib
  2    Thread 0x1303 of process 55925 0x00007fff8f60afae in semaphore_wait_trap () from /usr/lib/system/libsystem_kernel.dylib
  3    Thread 0x1403 of process 55925 0x00007fff8f60afae in semaphore_wait_trap () from /usr/lib/system/libsystem_kernel.dylib
  4    Thread 0x1503 of process 55925 0x00007fff8f60afae in semaphore_wait_trap () from /usr/lib/system/libsystem_kernel.dylib
  5    Thread 0x1603 of process 55925 0x00007fff8f60afae in semaphore_wait_trap () from /usr/lib/system/libsystem_kernel.dylib
  6    Thread 0x1703 of process 55925 0x00007fff8f610db6 in __psynch_cvwait () from /usr/lib/system/libsystem_kernel.dylib
  7    Thread 0x1803 of process 55925 0x00007fff8f61107a in select$DARWIN_EXTSN () from /usr/lib/system/libsystem_kernel.dylib
  8    Thread 0x1903 of process 55925 0x00007fff8f61107a in select$DARWIN_EXTSN () from /usr/lib/system/libsystem_kernel.dylib

Threads 1-5 are normal, so whatever prevents shutdown is in threads 6-8, I suspect it's on thread 6, here's the backtrace:

#0  0x00007fff8f610db6 in __psynch_cvwait () from /usr/lib/system/libsystem_kernel.dylib
#1  0x00007fff9cd3f728 in _pthread_cond_wait () from /usr/lib/system/libsystem_pthread.dylib
#2  0x000000010079630b in uv_cond_wait ()
#3  0x000000010078a2ab in worker ()
#4  0x0000000100796000 in uv.thread_start ()
#5  0x00007fff9cd3e99d in _pthread_body () from /usr/lib/system/libsystem_pthread.dylib
#6  0x00007fff9cd3e91a in _pthread_start () from /usr/lib/system/libsystem_pthread.dylib
#7  0x00007fff9cd3c351 in thread_start () from /usr/lib/system/libsystem_pthread.dylib
#8  0x0000000000000000 in ?? ()

Any ideas where could the source of a problem be?

Consumer delay when used with Producer on same runtime

Hello, I have created a script based on your tutorial to produce and consume on a single topic a single message.

https://gist.github.com/thanpolas/ed14e3db69646fefe268639ae069bbd5

When I run the script I get weird and unpredictable outcomes, sometimes there will be no data incoming at all, sometimes it will take several seconds to get the produced outcome (~25") and sometimes it'll work immediately. See screenshot of outcomes here: http://than.pol.as/iuB3

I understand that not cleanly shutting down consumers might be a reason why I observe these behaviors, would you confirm that too?

During development it is inevitable that a consumer will shutdown uncleanly, is there a solution for this, even if it is only for development env.

add support for seek

rdkafka has support for seeking to a specific offset using rd_kafka_seek(). It would be nice to expose this in node-rdkafka as well. I could not see this supported anywhere today.

No such configuration property: "auto.offset.reset"

I am trying to have my consumer start from the smallest/earliest offset when there is none yet stored, using the option "auto.offset.reset": "smallest", as according to Configuration.md, but am getting the errorNo such configuration property: "auto.offset.reset". Other configuration options do work, e.g. "auto.commit.enable": false.

Could Producer.produce() be blocking NodeJS thread?

Hi,

While doing some fail tests, I've discovered that when I use this library and it can't connect a broker during application startup (i.e. the first connect - we supply multiple brokers via options), the Producer.produce() method will work the first time and then, upon subsequent call to the same method, NodeJS thread will hang until error about all brokers being offline is emitted.

Is this something that's related to node-rdkafka, or could this actually be librdkafka behaving this way?

Thanks!

Question about commit and disconnect after connection is lost

I noticed that when commit is called while connection to kafka is lost, the function never returns or throws any error.

How should I recover from this state?

I could set a timeout and assume the connection is lost if the function does not return within that period and try to establish connection with a different kafka server. However, the disconnect function does not return either when connection is lost. How do I perform the necessary clean up without the disconnect function?

Thanks in advance

Manual offset and partition assignment?

This might be a feature request, and it might not be possible.

My use case needs 'simple consumer' like behavior. I don't want Kafka to manage consumer group rebalancing, and I won't be doing any offset commits. I do want consumers to be able to provide topic, partition, and offset from which they will begin consuming, so that clients can manage their own offsets. When the protocol eventually supports consuming based on event timestamps, I'd like to support that as well.

Is this possible? I see KafkaConsumer.prototype.assign is a function, but I don't see anything using it, and my attempts to do so leave me in Erroneous state from librdkafka. I think this is either because the offsets aren't actually passed to librdkafka's assign, or because of some internal subscribed state that is not being updated.

Is doing something like consumer.assign([ { topic: 'test', partition: 0, offset: 1102583 } ]) possible?

Also, if can I disable auto consumer group rebalancing? I'm looking for a way to override the rebalance_cb, but I don't see that.

Error in delivery report should provide topic/partition/key info

In case there's been an error in the delivery report, currently the plain Error object is passed to the delivery-report callback and there's no way to correlate it with the exact message that has been sent. The Error object should has all the same properties as the normal delivery report contains.

enable.auto.commit: false and commit()

Hi!

I was trying to explicit use commit(), like this:

var stream = consumer.getReadStream(config.kafka_topic, {
    fetchSize: 10
});

stream.on('data', msgs => {

    msgs.map(msg => {

        console.log('commiting...')
        consumer.commit(msg, err => {
            console.log('   commited!', err)
        })

    })
})

Noticed that the 'commited' callback, takes quite some time to be called (2-3 seconds for each one of them)

Is it correct to send commits like this?
Is so, why would it be so slow?

Thanks!!

Synchronous .produce

I was wondering if someone could explain to me the synchronous nature of the produce function and why it is synchronous. Let's take an example. If I were to set 'request.required.acks' to -1 (i.e. write to all replicas before responding), does that mean that the produce function will be blocking until kafka returns with a success?

By looking at the code I see one other possibility. That is you are synchronously writing to a write stream, and then relying on the 'delivery-report' event to notify of actual success. If this is the case, then I can just provide a function in the 'dr_cb' parameter when creating the producer, correct? This callback will let me know the result from the kafka response.

Using a callback that is global to a Producer instance instead of a callback for a single request means that I cannot link a success message from a 'delivery-report' back to the specific call chain that initiated the request. Am I correct? If I am wrong here, an example would be great on how this can be achieved.

Can someone please explain to me when the this driver will give control back to the user? i.e., What does the synchronous call do? Thanks again for your help. I really appreciate someone taking the time to clear this up.

EDIT: One more question. The new producer uses 'acks' in place of 'request.required.acks'. Does the C++ module use the old producer (v8 or earlier) or is the documentation just out of date?

Limited number of consumers per process

Since the driver uses Nan::AsyncQueueWorker for background job scheduling, we end up using built-in libuv thread pool. That means, that by default there's only 4 threads in the pool, and this number couldn't be increased more then to 128 threads.

Each consumer in the flowing mode submits a ConsumerConsumeLoop job, which is blocking, so it occupies one background thread completely - this means that number of consumers per process is limited to 4 (3 actually, since we need to save at least 1 thread from the pool for other tasks)

Another possible way to use up all the threads in the pool is to call consume(cb) in non-flowing mode many-many times on a topic without any messages coming - each call would create a ConsumerConsume work that will occupy the thread from the pool until the message is there, so it will block all the other operations which could be going on (producing, metadata requests etc etc etc).

I'm wondering if you think this might be a problem? In our use-case we don't fork a worker per consumer group, but instead create all of the consumers in every worker process and rely on kafka rebalancing for assigning individual partitions to workers since it's easier and better for failover (every worker is replaceable but any other one)

Do you think this will hit you too at some point? I've created this issue mostly to get the understanding on your thoughts on this.

Native crash when producer is closed

Here's a relevant part of the crash report:

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x0000000000000000

VM Regions Near 0:
--> 
    __TEXT                 00000001022f8000-00000001032f9000 [ 16.0M] r-x/rwx SM=COW  /Users/USER/*

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libc++abi.dylib                 0x00007fff9c7afb1e __dynamic_cast + 34
1   node-librdkafka.node            0x00000001044a1bea RdKafka::Topic::create(RdKafka::Handle*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, RdKafka::Conf*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) + 232 (TopicImpl.cpp:102)
2   node-librdkafka.node            0x000000010448fb0e NodeKafka::Topic::New(Nan::FunctionCallbackInfo<v8::Value> const&) + 1380 (topic.cc:33)
3   node-librdkafka.node            0x000000010448ff4d Nan::imp::FunctionCallbackWrapper(v8::FunctionCallbackInfo<v8::Value> const&) + 131 (nan_callbacks_12_inl.h:175)
4   node                            0x000000010245f8b5 v8::internal::FunctionCallbackArguments::Call(void (*)(v8::FunctionCallbackInfo<v8::Value> const&)) + 373
5   node                            0x00000001024b901f v8::internal::Builtin_HandleApiCallConstruct(int, v8::internal::Object**, v8::internal::Isolate*) + 1375
6   ???                             0x000004463bb0961b 0 + 4699695650331

Unfortunately I couldn't create a small tests case to reliably reproduce, but the cause seem to be quite clear. When produce and disconnect calls are placed with very unlucky timing, we've got a race:

  1. EvenThread: disconnect called, ProducerDisconnect work is placed.
  2. EventThread: produce called - since we're updating the _isConnected only after the disconnect happened, the produce call goes to maybeTopic and then to native code and gets all the way till here
  3. ProducerDisconnect work executed on background thread and deletes the m_client
  4. EventThread: calls RdKafka::Topic::create -> boom, SIGSEGV

I think the easiest solution is to update JS _isConnected property before the actual disconnect happens - then all these racy code paths will be protected. However, maybe it's better to invest time in fixing those races on the native level, not sure which path do you wanna choose.

Unable to consume messages

Following your latest updates, we are not able to consume messages anymore.

This is the sample we used:

var Kafka = require('../');

var topic = 'testtopic';

var consumer = new Kafka.KafkaConsumer({
  'debug': 'all',
  'metadata.broker.list': 'localhost:9092',
  'group.id': 'node-rdkafka-consumer' + new Date().getTime(),
  'enable.auto.commit': false
}, {
  'auto.offset.reset': 'latest'
});

// Flowing mode
consumer.connect();

consumer.on('event.log', function(log) {
  console.log(log);
});

consumer.on('data', function(m) {
  console.log('Received a message:');
  console.log('  message: ' + m.payload.toString());
  console.log('  key: ' + m.key);
  console.log('  topic: ' + m.topic);
  console.log('  offset: ' + m.offset);
  console.log('  partition: ' + m.partition);
});

consumer.on('ready', function() {
    console.log('ready');
    consumer.consume([topic]);
});

We've tried adding calls to subscribe(), adding a callback to consume(), nothing seems to work.

Running with debug set to all, we can see it's actually fetching messages but the 'data' callback is never invoked.

{ severity: 7,
  fac: 'SEND',
  message: 'mickael-ThinkPad-W530:9092/0: Sent FetchRequest (v1, 68 bytes @ 0, CorrId 46)' }
{ severity: 7,
  fac: 'RECV',
  message: 'mickael-ThinkPad-W530:9092/0: Received FetchResponse (v1, 351 bytes, CorrId 46, rtt 91.23ms)' }
{ severity: 7,
  fac: 'FETCH',
  message: 'mickael-ThinkPad-W530:9092/0: Topic testtopic [0] MessageSet size 310, error "Success", MaxOffset 20, Ver 3/3' }
{ severity: 7,
  fac: 'CONSUME',
  message: 'mickael-ThinkPad-W530:9092/0: Enqueue 10 messages on testtopic [0] fetch queue (qlen 1, v3)' }
{ severity: 7,
  fac: 'FETCH',
  message: 'mickael-ThinkPad-W530:9092/0: Fetch reply: Success' }

Standardize use of topic vs. topic_name

It seems some interfaces use topic when refering to a string topic name, and others use topic_name. E.g. consumer assign() expects the assignment object to use topic and assignments() returns an object with topic, but message objects from consume() calls use topic_name. I understand that sometimes a topic can be a Topic instance, and sometimes just a string topic name, but it'd be nice if the usage of the two was consistent.

I'm reusing the message metadata for future assign() calls, and I'd like to not have to translate between topic and topic_name.

timeout with consume()

Hi!

Is it also possible to build a consumer that polls for data with a timeout?

From documentation and tests I can see the infinite loop ("flowing mode") and the non flowing mode (in which a "hard" consume is called in a certain interval).

"consumer.consume" does not offer a timeout parameter, so it is blocking, right?

Is there also a possibility to use your library with a poll-like-consume like "consumer.consume(timeout)" that might either return a message or timeout and return null in that case?

Thanks

Tino

Does rdKafka expose the topic creation from the c++ client?

Hello, I was wondering if topic creation is exposed as well as the ability to configure partitions and replication factor on create.

Also, is topic altering exposed as well as the ability to alter the number of partitions when altering a topic.

I see a topic creation function in the producer but it also says that it only creates / manages this object in V8. So I am not sure this is actually creating the topic in Kafka.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.