When the exception is thrown from processing these...
# general
d
When the exception is thrown from processing these messages, do you negatively acknowledge them?
f
I do it looks something like this:
Copy code
/**
 * Create a consumer with a handle to process messages from the ingress topic. *
 * @returns A consumer instance.
 */
export async function createIngressConsumerPulsar(
  onMsgReceived: (msg: PulsarMessage) => void,
  onMsgProcessed: (msg: PulsarMessage) => void,
  onMsgError: (error: Error) => void,
): Promise<PulsarConsumer> {
  const ACK_TIMEOUT_MILLIS = 10_000;
  const NACK_REDELIVER_TIMEOUT_MILLIS = 30_000;
  const DEAD_LETTER_MAX_REDELIVER_COUNT = 3;

  const TOPIC = process.env.INTEGRATION_BROKER_INGRESS_PULSAR_TOPIC as string;
  const SUBSCRIPTION_NAME = process.env
    .INTEGRATION_BROKER_INGRESS_PULSAR_SUBSCRIPTION as string;

  const DEAD_LETTER_TOPIC = process.env
    .INTEGRATION_BROKER_INGRESS_PULSAR_DLQ_TOPIC as string | undefined;
  const DEAD_LETTER_SUBSCRIPTION = process.env
    .INTEGRATION_BROKER_INGRESS_PULSAR_DLQ_SUBSCRIPTION as string | undefined;
  const IS_DEAD_LETTER_CONSUMER = process.env
    .INTEGRATION_BROKER_INGRESS_IS_DEAD_LETTER as string | undefined;

  const isDeadLetterConsumer =
    IS_DEAD_LETTER_CONSUMER && IS_DEAD_LETTER_CONSUMER === 'true';

  if (
    !isDeadLetterConsumer &&
    (!DEAD_LETTER_TOPIC || !DEAD_LETTER_SUBSCRIPTION)
  ) {
    throw new Error(
      'Dead letter topic and subscription are required. (Unless this is the dead letter consumer)',
    );
  }

  <http://logger.info|logger.info>('Creating consumer with topic: ' + TOPIC);
  const pulsarClient = PulsarService.getInstance().getClient();
  return await pulsarClient.subscribe({
    topics: [TOPIC],
    subscription: SUBSCRIPTION_NAME,
    subscriptionType: 'Shared',
    ackTimeoutMs: ACK_TIMEOUT_MILLIS,
    nAckRedeliverTimeoutMs: NACK_REDELIVER_TIMEOUT_MILLIS,
    batchIndexAckEnabled: true,
    listener: (msg, consumer) =>
      handleMessage(msg, consumer, onMsgReceived, onMsgProcessed, onMsgError),
    deadLetterPolicy:
      !isDeadLetterConsumer && DEAD_LETTER_TOPIC && DEAD_LETTER_TOPIC !== TOPIC
        ? {
            initialSubscriptionName: DEAD_LETTER_SUBSCRIPTION,
            deadLetterTopic: DEAD_LETTER_TOPIC,
            maxRedeliverCount: DEAD_LETTER_MAX_REDELIVER_COUNT,
          }
        : undefined,
  });
}

/**
 * Handle a message from the Pulsar consumer.
 * Acks if successful, nacks if error is thrown.
 * @param msg - Message to handle.
 * @param consumer - Consumer that received the message.
 * @param onMsgReceived - callback for when a message is received.
 * @param onMsgProcessed - callback for when a message is processed.
 * @param onMsgError - callback for when a message process errors.
 */
export async function handleMessage(
  msg: PulsarMessage,
  consumer: PulsarConsumer,
  onMsgReceived: (msg: PulsarMessage) => void,
  onMsgProcessed: (msg: PulsarMessage) => void,
  onMsgError: (error: Error) => void,
): Promise<void> {
  onMsgReceived(msg);
  const body = msg.getData().toString();
  const messageId = msg.getMessageId().toString();

  try {
    const timestamp = new Date(msg.getPublishTimestamp());
    await processMessage({ messageId, body }, timestamp);
    await consumer.acknowledge(msg);
    onMsgProcessed(msg);
  } catch (error) {
    consumer.negativeAcknowledge(msg);
    onMsgError(error as Error);
    return;
  }
}
I have about 1000 messages on the backlog that seem to be stuck in an infinite loop
d
All of these messages most likely fell into the
catch
clause that keeps
negativelyAcknowlede
the message. These nacks are interpreted by Pulsar as “send me the message again”, so they keep getting sent/error/nack/resent….. over and over again.
f
Hmm okay, maybe I had misunderstood something. I though the
maxRedeliverCount
would set a cap of 3 redeliveres before sending to DLQ
d
Which client is this?
f
node
d
NodeJS doesn’t support DLQ, according to the client feature matrix. So that is out-of-date. Maybe it is a bug in the client code?
deadLetterPolicy: !isDeadLetterConsumer && DEAD_LETTER_TOPIC && DEAD_LETTER_TOPIC !== TOPIC ? { initialSubscriptionName: DEAD_LETTER_SUBSCRIPTION, deadLetterTopic: DEAD_LETTER_TOPIC, maxRedeliverCount: DEAD_LETTER_MAX_REDELIVER_COUNT, } : undefined,
It seems like there are situations in which a DLQ is not created. Is it possible that this is the issue?
f
I don't think so. I checked the env variables, though I can double check. We have deployments that we use specifically for consuming from dead letter and those will have
isDeadLetterConsumer=true
🤔 1
d
Can you share the message properties from one of these 1000 messages? Thanks!
f
yup, one sec!
Copy code
"{\n  \"x-pulsar-batch-size\": \"7212\",\n  \"x-pulsar-num-batch-message\": \"2\"\n}"
d
There should be a property in there that tracks the number of times the message has been delivered. That explains why the max redelivery count isn’t being honored.
That property is used to make that determination. So this is most likely a bug in the nodeJS client. Which version are you using? Can you please submit a bug?
f
Oh interesting, you're saying that that property should always be there? What would be a good way of getting more than 100 messages from the backlog? (I've been using the Streamnative consolve which caps at 100). I'd like to do some more spot checking I'm using
1.10.0
d
The property should only be there for messages that get nacked.
the Pulsar consumer CLI tool let’s use consume older messages
f
Copy code
pulsarctl subscriptions peek  production/data-broker/ingress-p1-partion-0 ingress-p1-production                                                                   1 ✘
[✖]  code: 404 reason: Topic <persistent://production/data-broker/ingress-p1-partion-0> not found
hm
oh wait lol, okay I think there's a bug in the streamnative admin tool. I've seen this before, it didn't respect me changing subscription for the peeking. I may have sent you message properties that are wrong
👍 1
same properties
Copy code
"{\n  \"x-pulsar-batch-size\": \"1749\",\n  \"x-pulsar-num-batch-message\": \"2\"\n}"
and this one I can confirm has been retried tons of times. I have it in my service logs
d
I suspect it is a bug then.
f
hmm
d
For now, you could implement the max retry logic in your own code using message properties.
f
you mean manually incrementing a count and manually sending to DLQ?
d
Are you using an exclusive or failover subscription type?
f
shared
I have other messages successfully going to the DLQ though which is weird
d
manually add/increment a property in the catch block.
then add set and test logic in the catch block as well.
if (message.getProperty(“redelivery_count”) < LIMIT) { increment property & nack} else { move to DLQ and ack };
f
ok! and to clarify, moving to DLQ would mean creating a producer and putting the message on that queue?
d
correct
But also, follow up on the issue you shared, letting the community know that this issue is in shared subscription mode as well
f
Will do!
I'll follow up on that existing issue to keep them together
what would the redelivery property be named? I dumped all the stuck messages and want to make sure its not in there
Finally, out of curiosity. How come negativeAcknowledge does not return a promise?
d
I forget the exact name of the property, but it is something like delivery, or deliveryCount.
f
ok yeah it's not there, I dumped all the messages and none have it
Thanks a lot David!
👍 1
As an update. Tried removing the explicit nacking and rely on the ack timeout. Didn't work 😕
🤔 1
In the streamnative console, is there a way to verify the
maxRedeliverCount
and
ackTimeoutMs
for a subscription?
d
Those settings aren't stored with the subscription. They are kept on the client library side
f
Update. It only happens in batching, and if in the batch at least one message is acked. Or in other words, messages only end up on dlq if all messages fail
d
That makes sense since the producer sent the messages in batches. ACK/NACK is on the batch itself. IIRC, the Java client has a setting for individual batch message tracking. I am not sure about Node
f
We may have an issue where we're enabled
batchIndexAckEnabled
on the consumer but not on the broker
👍 1