[Improve] Correct the anti-intuitive behavior of `seek` when `startMessageInclusive()` is configured
- Dominant language
- Java
- Stars
- 15.3k
- Forks
- 3.8k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 160
Description
### Search before asking
- [x] I searched in the [issues](https://github.com/apache/pulsar/issues) and found nothing similar.
### Motivation
Given a reader and the following code:
```java
final var msgIds = new ArrayList();
final var timestamps = new ArrayList();
for (int i = 0; i < 3; i++) {
final var msg = reader.readNext();
msgIds.add(msg.getMessageId());
timestamps.add(msg.getPublishTime());
}
// 1. seek to the 2nd message's message id
reader.seek(msgIds.get(1));
log.info("Case 1: {}", reader.readNext().getValue());
// 2. seek to the 2nd message's timestamp
reader.seek(timestamps.get(1));
log.info("Case 2: {}", reader.readNext().getValue());
```
It's intuitive to see values of case 1 and case 2 are the same.
However, if the reader has configured `startMessageIdInclusive`, the results are different:
```
2025-03-04T12:07:05,365 - INFO - [main:SimpleProducerConsumerTest] - Case 1: msg-2
2025-03-04T12:07:05,483 - INFO - [main:SimpleProducerConsumerTest] - Case 2: msg-1
```
See the full test code:
```java
@DataProvider
public static Object[][] startMessageIdInclusive() {
return new Object[][] { { true }, { false } };
}
@Test(dataProvider = "startMessageIdInclusive")
public void test(boolean startMessageIdInclusive) throws Exception {
final var topic = "test";
@Cleanup final var producer = pulsarClient.newProducer(Schema.STRING).topic(topic).create();
for (int i = 0; i < 3; i++) {
producer.send("msg-" + i);
}
final var readerBuilder = pulsarClient.newReader(Schema.STRING).topic(topic).startMessageId(MessageId.earliest);
if (startMessageIdInclusive) {
readerBuilder.startMessageIdInclusive();
}
@Cleanup final var reader = readerBuilder.create();
final var msgIds = new ArrayList();
final var timestamps = new ArrayList();
for (int i = 0; i < 3; i++) {
final var msg = reader.readNext();
msgIds.add(msg.getMessageId());
timestamps.add(msg.getPublishTime());
}
// 1. seek to the 2nd message's message id
reader.seek(msgIds.get(1));
log.info("Case 1: {}", reader.readNext().getValue());
// 2. seek to the 2nd message's timestamp
reader.seek(timestamps.get(1));
log.info("Case 2: {}", reader.readNext().getValue());
}
```
### Solution
The root cause is when https://github.com/apache/pulsar/pull/4331 introduced this config, it didn't consider the case when seeking by timestamp.
It's hard to change the existing behavior because many applications might rely on it. We'd better add new client side configs and mark existing `startMessageInclusive` as deprecated.
### Alternatives
_No response_
### Anything else?
_No response_
### Are you willing to submit a PR?
- [x] I'm willing to submit a PR!
Contributor guide
Research direction
Start by reproducing the shown test using reader.seek(...), startMessageIdInclusive(), and the SimpleProducerConsumerTest entry point. Then inspect the client-side reader configuration and existing seek behavior. Done means the requested client configurations and deprecation are covered by tests that define consistent timestamp and message-ID seek semantics without breaking the existing behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- distributed-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100