Skip to content

KAFKA-9724 Newer clients not always sending fetch request to older brokers#8376

Merged
mumrah merged 13 commits into
apache:trunkfrom
mumrah:KAFKA-9724
May 27, 2020
Merged

KAFKA-9724 Newer clients not always sending fetch request to older brokers#8376
mumrah merged 13 commits into
apache:trunkfrom
mumrah:KAFKA-9724

Conversation

@mumrah

@mumrah mumrah commented Mar 27, 2020

Copy link
Copy Markdown
Member

We had a similar case previously with KAFKA-8422 (#6806) where we would skip the validation step if the broker was on a version older than 2.3.

This PR makes a similar change on the prepareFetchRequest side. If the broker is older than 2.3, we will skip the transition to AWAITING_VALIDATION but also we will clear that state if it had been set by a call to seekUnvalidated.

This PR adds a check for an appropriate version of OFFSETS_FOR_LEADER_EPOCH before entering the validation state in the consumer. If the broker does not support version 3+ of this API, we skip validation.

Also cleaned up the logic behind updating last-seen leader epochs. Now, if the leader epoch is null, we will not update it unless we are processing a metadata response. This prevents the client from learning about an epoch before it knows anything about the leader.

@mumrah
mumrah requested a review from hachikuji March 27, 2020 20:25
@mumrah

mumrah commented Mar 27, 2020

Copy link
Copy Markdown
Member Author

@hachikuji this change might be too superficial. It should fix the problem when talking to older brokers, but ideally we shouldn't enter the AWAITING_VALIDATION state at all if the broker is older than 2.3. Doing that would probably require pushing down a flag into the TopicPartitionState which would be a bigger change. WTDY?

@ijuma

ijuma commented Mar 27, 2020

Copy link
Copy Markdown
Member

ok to test

Comment thread clients/src/main/java/org/apache/kafka/clients/Metadata.java Outdated
Comment thread clients/src/main/java/org/apache/kafka/clients/consumer/internals/Fetcher.java Outdated
@hachikuji

Copy link
Copy Markdown
Contributor

Note that FetcherTest.testFetcherConcurrency is hanging.

* @param epochTest a predicate to determine if the old epoch should be replaced
* @return true if the epoch was updated, false otherwise
*/
private synchronized boolean updateLastSeenEpoch(TopicPartition topicPartition,

@mumrah mumrah Apr 7, 2020

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I removed this since at this point the two callers have different needs for updating the epoch and adding a flag felt pretty kludgy

Comment thread clients/src/test/java/org/apache/kafka/clients/MetadataTest.java
Comment thread clients/src/main/java/org/apache/kafka/clients/Metadata.java Outdated
Comment thread clients/src/main/java/org/apache/kafka/clients/Metadata.java Outdated
Comment thread clients/src/main/java/org/apache/kafka/clients/Metadata.java Outdated
Comment thread clients/src/main/java/org/apache/kafka/clients/consumer/internals/Fetcher.java Outdated
Comment thread clients/src/test/java/org/apache/kafka/clients/MetadataTest.java
return false;
}
} else {
return assignedState(tp).maybeValidatePosition(leaderAndEpoch);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I wonder, do we really need this call here? If the leader is not present the epoch shouldn't be present either -- right? If that's the case, then the call to maybeValidatePosition will short circuit

        private boolean maybeValidatePosition(Metadata.LeaderAndEpoch currentLeaderAndEpoch) {
            if (this.fetchState.equals(FetchStates.AWAIT_RESET)) {
                return false;
            }

            if (!currentLeaderAndEpoch.leader.isPresent() && !currentLeaderAndEpoch.epoch.isPresent()) {
                return false;
            }

            if (position != null && !position.currentLeader.equals(currentLeaderAndEpoch)) {
                FetchPosition newPosition = new FetchPosition(position.offset, position.offsetEpoch, currentLeaderAndEpoch);
                validatePosition(newPosition);
                preferredReadReplica = null;
            }
            return this.fetchState.equals(FetchStates.AWAIT_VALIDATION);
        }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, actually looking at the javadoc for LeaderAndEpoch, I see

It is also possible that we know of the leader epoch, but not the leader when it is derived from an external source (e.g. a committed offset).

Also in Metadata, we do return a LeaderAndEpoch with the last-seen epoch, but no leader if the metadata is stale. So, I guess it makes sense to keep this call in maybeValidatePositionForCurrentLeader

@hachikuji hachikuji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM (assuming tests pass after checkstyle is fixed)

@ijuma

ijuma commented May 22, 2020

Copy link
Copy Markdown
Member

Do we think this is safe to backport or is it best to stick with 2.6.0?

@hachikuji

Copy link
Copy Markdown
Contributor

@ijuma I think it is safe. I was planning to backport to 2.5.

@ijuma

ijuma commented May 25, 2020

Copy link
Copy Markdown
Member

Sounds good.

@hachikuji

Copy link
Copy Markdown
Contributor

retest this please

1 similar comment
@mumrah

mumrah commented May 27, 2020

Copy link
Copy Markdown
Member Author

retest this please

@mumrah

mumrah commented May 27, 2020

Copy link
Copy Markdown
Member Author

Test failures are known flaky tests

  • EosBetaUpgradeIntegrationTest
  • PreferredReplicaLeaderElectionCommandTest#testBasicPreferredReplicaElection

@mumrah
mumrah merged commit 1672a75 into apache:trunk May 27, 2020
mumrah added a commit to mumrah/kafka that referenced this pull request May 27, 2020
…okers (apache#8376)

Newer clients were getting stuck entering the validation phase even when a broker didn't support it. This commit will bypass the AWAITING_VALIDATION state when the broker is on an older version of the OffsetsForLeaderEpoch RPC.
mumrah added a commit that referenced this pull request May 27, 2020
…okers (#8376)

Newer clients were getting stuck entering the validation phase even when a broker didn't support it. This commit will bypass the AWAITING_VALIDATION state when the broker is on an older version of the OffsetsForLeaderEpoch RPC.
hachikuji pushed a commit that referenced this pull request Jun 18, 2020
## Background

When a partition subscription is initialized it has a `null` position and is in the INITIALIZING state. Depending on the consumer, it will then transition to one of the other states. Typically a consumer will either reset the offset to earliest/latest, or it will provide an offset (with or without offset metadata). For the reset case, we still have no position to act on so fetches should not occur.

Recently we made changes for KAFKA-9724 (#8376) to prevent clients from entering the AWAIT_VALIDATION state when targeting older brokers. New logic to bypass offset validation as part of this change exposed this new issue.

## Bug and Fix

In the partition subscriptions, the AWAIT_RESET state was incorrectly reporting that it had a position. In some cases a position might actually exist (e.g., if we were resetting offsets during a fetch after a truncation), but in the initialization case no position had been set. We saw this issue in system tests where there is a race between the offset reset completing and the first fetch request being issued.

Since AWAIT_RESET#hasPosition was incorrectly returning `true`, the new logic to bypass offset validation was transitioning the subscription to FETCHING (even though no position existed).

The fix was simply to have AWAIT_RESET#hasPosition to return `false` which should have been the case from the start. Additionally, this fix includes some guards against NPE when reading the position from the subscription.

Reviewers: Chia-Ping Tsai <chia7712@gmail.com>, Jason Gustafson <jason@confluent.io>
hachikuji pushed a commit that referenced this pull request Jun 18, 2020
## Background

When a partition subscription is initialized it has a `null` position and is in the INITIALIZING state. Depending on the consumer, it will then transition to one of the other states. Typically a consumer will either reset the offset to earliest/latest, or it will provide an offset (with or without offset metadata). For the reset case, we still have no position to act on so fetches should not occur.

Recently we made changes for KAFKA-9724 (#8376) to prevent clients from entering the AWAIT_VALIDATION state when targeting older brokers. New logic to bypass offset validation as part of this change exposed this new issue.

## Bug and Fix

In the partition subscriptions, the AWAIT_RESET state was incorrectly reporting that it had a position. In some cases a position might actually exist (e.g., if we were resetting offsets during a fetch after a truncation), but in the initialization case no position had been set. We saw this issue in system tests where there is a race between the offset reset completing and the first fetch request being issued.

Since AWAIT_RESET#hasPosition was incorrectly returning `true`, the new logic to bypass offset validation was transitioning the subscription to FETCHING (even though no position existed).

The fix was simply to have AWAIT_RESET#hasPosition to return `false` which should have been the case from the start. Additionally, this fix includes some guards against NPE when reading the position from the subscription.

Reviewers: Chia-Ping Tsai <chia7712@gmail.com>, Jason Gustafson <jason@confluent.io>
hachikuji pushed a commit that referenced this pull request Jun 18, 2020
When a partition subscription is initialized it has a `null` position and is in the INITIALIZING state. Depending on the consumer, it will then transition to one of the other states. Typically a consumer will either reset the offset to earliest/latest, or it will provide an offset (with or without offset metadata). For the reset case, we still have no position to act on so fetches should not occur.

Recently we made changes for KAFKA-9724 (#8376) to prevent clients from entering the AWAIT_VALIDATION state when targeting older brokers. New logic to bypass offset validation as part of this change exposed this new issue.

In the partition subscriptions, the AWAIT_RESET state was incorrectly reporting that it had a position. In some cases a position might actually exist (e.g., if we were resetting offsets during a fetch after a truncation), but in the initialization case no position had been set. We saw this issue in system tests where there is a race between the offset reset completing and the first fetch request being issued.

Since AWAIT_RESET#hasPosition was incorrectly returning `true`, the new logic to bypass offset validation was transitioning the subscription to FETCHING (even though no position existed).

The fix was simply to have AWAIT_RESET#hasPosition to return `false` which should have been the case from the start. Additionally, this fix includes some guards against NPE when reading the position from the subscription.

Reviewers: Chia-Ping Tsai <chia7712@gmail.com>, Jason Gustafson <jason@confluent.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants