Skip to content

Prefetching topic metadata on newly created topic interferes with consumer (?) #194

@apeloquin-agilysys

Description

@apeloquin-agilysys

Environment Information

  • OS: Mac M3 Sonoma 14.6.1
  • Node Version: 20.14.0
  • NPM Version: 10.7.0
  • confluent-kafka-javascript version: 0.5.2

Steps to Reproduce

While incorporating use of the producer dependentAdmin() and prefetching topic metadata we ran into an issue where a simple produce/consume test was consistently failing in our build pipeline but was not reproducible locally on our dev machines.

Our build pipeline always starts with a fresh Kafka docker instance when running integration tests and this was the key differentiation that allowed us to narrow down the issue.

The test below uses a topic name with a timestamp component to ensure that each run is for a new topic, as this issue does not replicate with multiple runs using the same topic.

There are three tests:

  1. The first does a prefetch prior to sending to the topic -- and fails.
  2. The second does the same thing but skips the prefetch -- and succeeds.
  3. The third is the same as the first test -- but now succeeds.
import {KafkaJS as Confluent, RdKafka} from "@confluentinc/kafka-javascript";
import {expect} from "chai";

const TOPIC = `test-confluent-topic-${Date.now()}`;
const GROUP_ID = `test-confluent-group-${Date.now()}`;

describe("fetchMetadata", () => {
  let kafka: Confluent.Kafka;
  let admin: Confluent.Admin;
  let consumer: Confluent.Consumer;
  let producer: Confluent.Producer;
  let producerAdmin: Confluent.Admin;

  before(async () => {
    kafka = new Confluent.Kafka({kafkaJS: {brokers: ["localhost:9092"]}});
    admin = kafka.admin();
    await admin.connect();
  });

  beforeEach(async () => {
    await admin.createTopics({topics: [{topic: TOPIC}]});
    producer = kafka.producer();
    await producer.connect();
    producerAdmin = producer.dependentAdmin();
    await producerAdmin.connect();
  });

  afterEach(async () => {
    await producerAdmin.disconnect();
    await producer.disconnect();
    await consumer?.disconnect();
  });

  after(async () => {
    await admin.disconnect();
    await producerAdmin.disconnect();
    await producer.disconnect();
    await consumer?.disconnect();
  });

  it("with prefetch fails", async () => doTest(true));
  it("without prefetch succeeds", async () => doTest(false));
  it("now with prefetch succeeds", async () => doTest(true));

  async function doTest(prefetch: boolean) {
    let ready = false;
    const receivedMessages: string[] = [];
    consumer = kafka.consumer({
      kafkaJS: {groupId: GROUP_ID},
      rebalance_cb: (err: any, assignment: any, consumer: any) => {
        if (err.code !== RdKafka.CODES.ERRORS.ERR__ASSIGN_PARTITIONS) return;
        if (!ready) {
          ready = true;
        }
      }
    });
    await consumer.connect();
    await consumer.subscribe({topic: TOPIC});
    await consumer.run({
      eachMessage: async ({message}) => {
        receivedMessages.push(message.value?.toString() ?? "");
      }
    });

    await until(() => ready);

    if (prefetch) await producerAdmin.fetchTopicMetadata({topics: [TOPIC]});
    await producer.send({
      topic: TOPIC,
      messages: [{value: "one"}]
    });

    await until(() => receivedMessages.length == 1);
    expect(receivedMessages).to.have.members(["one"]);
  }

  async function until(condition: () => boolean) {
    const timeout = 10000;
    const finish = Date.now() + timeout;
    while (Date.now() <= finish) {
      const result = condition();
      if (result) return;
      await new Promise(resolve => setTimeout(resolve, 50));
    }
    throw new Error(`Failed within ${timeout!}ms`);
  }
});

Test results:
image

Note that in the failing test, the call to fetchTopicMetadata does not result in an error, but the consumer appears to never receive the message, although we do see in Kafka that the message was sent.

image

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionA question is asked

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions