Skip to content

NATS JetStream source can duplicate ingestion with a single durable consumer cursor #25681

@almostintuitive

Description

@almostintuitive

Describe the bug

A NATS JetStream source can ingest duplicate rows when one logical source is expanded into multiple actor-owned synthetic splits even though ACK progress is tracked by a single durable consumer cursor.
This happens when parallelism is >=2 and we have more than 1 views attached to a single source.

Error message/log

JetStream consumer state showed ack_floor stuck at 0 while num_redelivered increased, and RisingWave source tables accumulated duplicate rows for the same message key/time.

To Reproduce

First create a NATS-backed source with a minimal JSON payload containing a single property, for example:

CREATE TABLE nats_values (
  value INT
) APPEND ONLY WITH (
  connector = 'nats',
  server_url = 'nats://127.0.0.1:4222',
  subject = 'values.demo',
  stream = 'values-demo',
  consumer.durable_name = 'values-demo-durable'
) FORMAT PLAIN ENCODE JSON;

Then publish messages like:

{"value":1}

When the source runs with multiple actors for the same logical source, JetStream may repeatedly redeliver the same earliest unacked messages and RisingWave may ingest duplicates of the same { "value": 1 } payload. When parallelism is >1 and there are more than 1 views chained after a single source.

Expected behavior

I expected one stored JetStream message like { "value": 1 } to be ingested once per delivery attempt after checkpoint-safe ACK progression.

Instead, the same message can be redelivered and appended multiple times because multiple actor-owned synthetic splits contend on one durable consumer cursor.

How did you deploy RisingWave?

Generic local/dev deployment against a JetStream-enabled NATS instance.

The version of RisingWave

Observed on current main before the fix in PR #25680.

Additional context

The simplified root cause is a split-model mismatch:

  • meta treated NATS as an adaptive-split source and expanded one discovered split per actor
  • the NATS connector delegated progress to a single JetStream durable consumer instead of maintaining independent per-split offsets in RisingWave state
  • this let multiple actors interfere with ACK progress on one durable consumer and triggered repeated redelivery

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions