Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: impl ack and migrate to durable consumer for Nats #18873

Merged
merged 6 commits into from
Oct 16, 2024

Conversation

tabVersion
Copy link
Contributor

@tabVersion tabVersion commented Oct 11, 2024

I hereby agree to the terms of the RisingWave Labs, Inc. Contributor License Agreement.

What's changed and what's your intention?

As title, do ack on messages when a barrier completes.

Checklist

  • I have written necessary rustdoc comments
  • I have added necessary unit tests and integration tests
  • I have added test labels as necessary. See details.
  • I have added fuzzing tests or opened an issue to track them. (Optional, recommended for new SQL features Sqlsmith: Sql feature generation #7934).
  • My PR contains breaking changes. (If it deprecates some features, please create a tracking issue to remove them in the future).
  • All checks passed in ./risedev check (or alias, ./risedev c)
  • My PR changes performance-critical code. (Please run macro/micro-benchmarks and show the results.)
  • My PR contains critical fixes that are necessary to be merged into the latest release. (Please check out the details)

Documentation

  • My PR needs documentation updates. (Please use the Release note section below to summarize the impact on users)

Release note

add a required param for nats connector consumer.durable_name, and we will either build a new one or continue reading from an existing durable consumer.

Note here: we don't encourage reusing the consumer.durable_name across streaming jobs, it will cause data loss.

After this pr, RW no longer keeps offset of Nats, the broker is responsible for offset management. And we have to accept a regression on sematic, from exactly-once to at-least-once.
If users care about data loss, they should set consumer.ack_policy to all or explicit.


Why do I abandon managing offset for the durable consumer to achieve exactly-once?

  • the API get_or_create_consumer requires the config provided should remain the same as it was created. It does not encourage reading from a spec offset. If we really want to do it, we should build a new one.
    • In view of complaints about too many consumer groups on kafka, I'd give up on prev impl.
  • Under this case, managing offset ourselves is not scalable. Imagine we have multiple exec, each one get a batch from the subject. After recovery, which offset should they start with?

Signed-off-by: tabVersion <[email protected]>
@github-actions github-actions bot added the type/fix Bug fix label Oct 11, 2024
@tabVersion tabVersion marked this pull request as ready for review October 11, 2024 10:36
@graphite-app graphite-app bot requested a review from a team October 11, 2024 10:37
@tabVersion tabVersion requested review from fuyufjh and removed request for a team October 11, 2024 10:37
Copy link
Member

@xxchan xxchan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After browsing the doc about the model, I don't get why we need to ack for NATS Jetstream.

https://docs.nats.io/using-nats/developer/develop_jetstream/consumers#delivery-reliability

The "AckPolicy" is defined by the Consumer, not the Stream. And it means "application level acknowledgements", like failure in bussiness logic.

We can just always use none and no need to ack.

src/connector/src/source/nats/mod.rs Outdated Show resolved Hide resolved
src/connector/src/source/nats/mod.rs Show resolved Hide resolved
src/connector/src/source/reader/reader.rs Outdated Show resolved Hide resolved
@tabVersion
Copy link
Contributor Author

After browsing the doc about the model, I don't get why we need to ack for NATS Jetstream.

docs.nats.io/using-nats/developer/develop_jetstream/consumers#delivery-reliability

The "AckPolicy" is defined by the Consumer, not the Stream. And it means "application level acknowledgements", like failure in bussiness logic.

Thanks for your fast review, the pr is part of Nats JetStream refactor. Let me address your concern in a tracking issue.

@tabVersion
Copy link
Contributor Author

@xxchan refer to #18876 for details

@@ -30,9 +36,12 @@ impl From<NatsMessage> for SourceMessage {
key: None,
payload: Some(message.payload),
// For nats jetstream, use sequence id as offset
// DEPRECATED: no longer use sequence id as offset, let nats broker handle failover
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +160 to +171
match ack_policy {
JetStreamAckPolicy::None => (),
JetStreamAckPolicy::Explicit => {
for reply_subject in reply_subjects {
ack(context, reply_subject).await;
}
}
JetStreamAckPolicy::All => {
if let Some(reply_subject) = reply_subjects.last() {
ack(context, reply_subject.clone()).await;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, the subjects like "$JS.ACK.test_stream_1.l2vxD20k.1.3.4.1728547619594368340.0" already contain the offset information, right?

Want to make the logic correct in this situation:
Nats send message: m1,m2,m3,m4
RW get message m1, m2, (checkpoint 1), m3, m4
When we do checkpoint 1 and send batch ack, which means ack m2 subjects. In nats part, it will only ack the m1, m2, and m3,m4 not ack, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, the subjects like "$JS.ACK.test_stream_1.l2vxD20k.1.3.4.1728547619594368340.0" already contain the offset information, right?

yes

Want to make the logic correct in this situation:
Nats send message: m1,m2,m3,m4
RW get message m1, m2, (checkpoint 1), m3, m4
When we do checkpoint 1 and send batch ack, which means ack m2 subjects. In nats part, it will only ack the m1, m2, and m3,m4 not ack, right?

If ack_policy is ack_all (batch ack), at ckpt_1, we just ack msg2, which ack both msg1 and msg2. Msg3 and msg4 are not ack-ed.

Copy link
Member

@yufansong yufansong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Copy link

gitguardian bot commented Oct 14, 2024

⚠️ GitGuardian has uncovered 3 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
9425213 Triggered Generic Password a6ffcd6 e2e_test/source/tvf/postgres_query.slt View secret
9425213 Triggered Generic Password a6ffcd6 e2e_test/source/tvf/postgres_query.slt View secret
9425213 Triggered Generic Password 02f28b8 ci/scripts/e2e-source-test.sh View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

//
// DEPRECATED: no longer use sequence id as offset, let nats broker handle failover
// use reply_subject as offset for ack use, we just check the persisted state for whether this is the first run
offset: message.reply_subject.unwrap_or_default(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yufansong Unfortunately, offset column should be kept as additional column and I think it is not worth making an exception for Nats. We use offset here to store the reply subject.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get it. SGTM

Comment on lines +76 to +78
// We have record on this Nats Split, contains the last seen offset (seq id) or reply subject
// We do not use the seq id as start position anymore,
// but just let the reader load from durable consumer on broker.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand these comments, but you didn't make any change to the implementation code (L58 ~ L72). Why & how it works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use api get_or_create_consumer when building stream consumer. api ref
If we utilize an existing durable consumer, the provided config should be the same as the consumer created. So the config here should never change, always align to the one conducted from with clause.

following changes in #18895

@@ -61,6 +62,8 @@ def_anyhow_newtype! {
async_nats::jetstream::consumer::pull::MessagesError => "Nats error",
async_nats::jetstream::context::CreateStreamError => "Nats error",
async_nats::jetstream::stream::ConsumerError => "Nats error",
NatsJetStreamError => "Nats error",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these 5 errors are "Nats error". Consider including more context?

Copy link
Member

@fuyufjh fuyufjh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@xxchan xxchan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the main motivation is performance, should we first do a quick demo & benchmark to confirm this direction is correct?

Another thing I still don't understand why we need to make Ack policy customizable. It should be decided by the application (i.e., us).

@tabVersion
Copy link
Contributor Author

Since the main motivation is performance, should we first do a quick demo & benchmark to confirm this direction is correct?

Don't get me wrong, the motivation for the refactor is making Nats JetStream consumer parallelizable. Ack is not avoidable when syncing consumption progress among workers.

@xxchan
Copy link
Member

xxchan commented Oct 14, 2024

Don't get me wrong, the motivation for the refactor is making Nats JetStream consumer parallelizable.

I mean exactly that we can test "consumer group"-like usage can have perf improvements. Otherwise, perhaps sth like manually shard subjects into sub-subjects is inevitable.

e.g., Chrisss93/flink-connector-nats says:

Specifying multiple consumers with non-overlapping subject-filters allows different portions of the stream to be read and processed in parallel (somewhat like a Kafka/Pulsar topic with multiple partitions).

(I do not mean what they are doing is correct neither.)

Google Pub/Sub mentioned explicitly that they are "per-message parallelism". But I feel lost when browsing NATS's docs about what's the best practice. Therefore, IMO testing is better.

@xxchan
Copy link
Member

xxchan commented Oct 14, 2024

On the other hand, cumulative ack (AckAll)'s overhead should be much smaller than individual acks.

@tabVersion
Copy link
Contributor Author

On the other hand, cumulative ack (AckAll)'s overhead should be much smaller than individual acks.

#[serde(rename = "consumer.ack_policy")]
#[serde_as(as = "Option<DisplayFromStr>")]
pub ack_policy: Option<String>,

we are offering the flexibility to users.

@xxchan
Copy link
Member

xxchan commented Oct 14, 2024

we are offering the flexibility to users.

I asked this exact question above:

Another thing I still don't understand why we need to make Ack policy customizable. It should be decided by the application (i.e., us).

I don't get what benefits this flexibility brings to users.

@stdrc
Copy link
Member

stdrc commented Oct 14, 2024

Since the main motivation is performance, should we first do a quick demo & benchmark to confirm this direction is correct?

Agree. IIUC, changing "ack all" + "sequence number as offset" to per-message ack is a regression in terms of the "exactly-once" semantics. It's reasonable to prove there'll be performance benefit. And if we make it customizable, when user specifies ack_all, it will be indeed a regression of "exactly-once" semantics.

@xxchan
Copy link
Member

xxchan commented Oct 14, 2024

changing "ack all" + "sequence number as offset" to per-message ack is a regression in terms of the "exactly-once" semantics.

Previously we are not "ack all", but no ack (regardless of ack policy).

And regarding exactly-once and performance, #18876 has more discussion.

@yufansong
Copy link
Member

Since the main motivation is performance, should we first do a quick demo & benchmark to confirm this direction is correct?

Agree. IIUC, changing "ack all" + "sequence number as offset" to per-message ack is a regression in terms of the "exactly-once" semantics. It's reasonable to prove there'll be performance benefit. And if we make it customizable, when user specifies ack_all, it will be indeed a regression of "exactly-once" semantics.

Previous, we use no ack to keep exactly-once sematic, but will have performance problem

@tabVersion tabVersion changed the title fix: ack messages on Nats JetStream refactor: impl ack and migrate to durable consumer for Nats Oct 15, 2024
@tabVersion tabVersion enabled auto-merge October 15, 2024 08:00
@tabVersion tabVersion added this pull request to the merge queue Oct 15, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Oct 15, 2024
@tabVersion tabVersion added this pull request to the merge queue Oct 16, 2024
Merged via the queue into main with commit d997ebe Oct 16, 2024
28 of 30 checks passed
@tabVersion tabVersion deleted the tab/nats-offset branch October 16, 2024 03:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants