Skip to content

KAFKA-18844: Stale features information in QuorumController#registerBroker #18997

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

Merged
merged 2 commits into from
Feb 27, 2025

Conversation

FrankYang0529
Copy link
Member

@FrankYang0529 FrankYang0529 commented Feb 21, 2025

In #16848, we added kraft.version to finalized features and got finalized features outside controller event handling thread. This may make finalized features stale when processing registerBroker event. Also, some cases like QuorumControllerTest.testBalancePartitionLeaders become flaky cause of outdated MV. This PR moves finalized features back to controller event handling thread to avoid the error.

Reviewers: Ismael Juma ijuma@apache.org, Jun Rao junrao@gmail.com, Colin P. McCabe cmccabe@apache.org, Chia-Ping Tsai chia7712@gmail.com


Previous PR title: Fix flaky QuorumControllerTest.testBalancePartitionLeaders()
Previous PR description:
When we initialize FeatureControlManager in QuorumController [0], we don't set bootstrapMetadata for it, so it use Metadata.latestProduction() [1]. In the test case, we use IBP_3_7_IV0 as bootstrapMetadata [2]. When initializing QuorumController, it takes time to replay record to overwrite FeatureControlManager#metadataVersion. If we add Thread.sleep(1000L) before metadataVersion.set(mv) [3], we can reproduce the flaky test steadily.

I'm not sure whether setting bootstrapMetadata to FeatureControlManager is correct, so I fix the case by waiting metadataVersion is overwrote.

[0]

this.featureControl = new FeatureControlManager.Builder().
setLogContext(logContext).
setQuorumFeatures(quorumFeatures).
setSnapshotRegistry(snapshotRegistry).
setClusterFeatureSupportDescriber(clusterSupportDescriber).
build();

[1]

private MetadataVersion metadataVersion = MetadataVersion.latestProduction();

[2]

static final BootstrapMetadata SIMPLE_BOOTSTRAP = BootstrapMetadata.
fromVersion(MetadataVersion.IBP_3_7_IV0, "test-provided bootstrap");

[3]

public void replay(FeatureLevelRecord record) {
VersionRange range = quorumFeatures.localSupportedFeature(record.name());
if (!range.contains(record.featureLevel())) {
throw new RuntimeException("Tried to apply FeatureLevelRecord " + record + ", but this controller only " +
"supports versions " + range);
}
if (record.name().equals(MetadataVersion.FEATURE_NAME)) {
MetadataVersion mv = MetadataVersion.fromFeatureLevel(record.featureLevel());
metadataVersion.set(mv);

@github-actions github-actions bot added tests Test fixes (including flaky tests) kraft small Small PRs labels Feb 21, 2025
@ijuma
Copy link
Member

ijuma commented Feb 21, 2025

This is related to #18845, thanks for the quick fix!

@ijuma
Copy link
Member

ijuma commented Feb 21, 2025

in QuorumController [0], we don't set bootstrapMetadata for it, so it use Metadata.latestProduction()

We tried to remove these defaults as part of #18845 - looks like I missed this one. Can you try removing the default? Is that an easy change or does it cause a lot of issues?

@FrankYang0529
Copy link
Member Author

FrankYang0529 commented Feb 21, 2025

Can you try removing the default?

I think we can't remove the default one. Each variable get default value if it's null. Or do you mean that trying to set bootstrapMetadata when initialize FeatureControlManager?

if (logContext == null) logContext = new LogContext();
if (snapshotRegistry == null) snapshotRegistry = new SnapshotRegistry(logContext);
if (quorumFeatures == null) {
Map<String, VersionRange> localSupportedFeatures = new HashMap<>();
localSupportedFeatures.put(MetadataVersion.FEATURE_NAME, VersionRange.of(
MetadataVersion.MINIMUM_VERSION.featureLevel(),
MetadataVersion.latestProduction().featureLevel()));
quorumFeatures = new QuorumFeatures(0, localSupportedFeatures, Collections.singletonList(0));
}
return new FeatureControlManager(
logContext,
quorumFeatures,
snapshotRegistry,
metadataVersion,
clusterSupportDescriber
);

@ijuma
Copy link
Member

ijuma commented Feb 21, 2025

Yes, if it's mandatory, then we should set it in the constructor. Any issues doing that?

Copy link
Contributor

@junrao junrao left a comment

Choose a reason for hiding this comment

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

@FrankYang0529 : Thanks for the PR.

Regarding FeatureControlManager.metadataVersion, it's bootstrapped from the bootstrap file or the log. Any test that depends on it should wait for the bootstrap to complete. So, it does seem reasonable to initialize it to null. @cmccabe : What do you think?

@@ -753,6 +753,10 @@ public void testBalancePartitionLeaders() throws Throwable {
QuorumController active = controlEnv.activeController();
Map<Integer, Long> brokerEpochs = new HashMap<>();

TestUtils.waitForCondition(() ->
active.featureControl().finalizedFeatures(Long.MAX_VALUE).featureMap().get(MetadataVersion.FEATURE_NAME) == SIMPLE_BOOTSTRAP.metadataVersion().featureLevel(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we need to wait for QuorumController to fully bootstrap before doing the remaining test, it seems that we should just call controlEnv.activeController(true) here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. I think we can remove QuorumControllerTestEnv#activeController(boolean) function, because it's better for all cases get a real active controller. Other test cases like testUncleanShutdownBrokerElrEnabled also has similar error.

Screenshot 2025-02-22 at 10 37 39 AM

@ijuma
Copy link
Member

ijuma commented Feb 22, 2025

So, it does seem reasonable to initialize it to null. @cmccabe : What do you think?

It looks like this would basically always be null outside of tests and it would fail in the following line of the FeatureControlManager constructor:

this.metadataVersion = new TimelineObject<>(snapshotRegistry, metadataVersion);

It seems to me that we should actually remove the metadata version from FeatureControlManager.Builder and rely on FeatureControlManager.replay to set the metadata version.

@FrankYang0529
Copy link
Member Author

@ijuma, it looks like TimelineObject initial value cannot be null, so we can't remove metadata version from FeatureControlManager.Builder.

public TimelineObject(SnapshotRegistry snapshotRegistry, T initialValue) {
Objects.requireNonNull(initialValue);

@ijuma
Copy link
Member

ijuma commented Feb 23, 2025

@FrankYang0529 Yes, I know it cannot be null. My point is that you'd have to use TimelineObject<Optional<MetadataVersion>> or something long those lines.

@chia7712
Copy link
Member

It seems to me that we should actually remove the metadata version from FeatureControlManager.Builder and rely on FeatureControlManager.replay to set the metadata version.

How about initializing it to the bootstrapped metadata? If the role is quorum leader, it will eventually be initialized to the bootstrapped metadata. It is still valid as the "current" metadata version if the role is a standby controller.

@ijuma
Copy link
Member

ijuma commented Feb 24, 2025

@chia7712 Is the bootstrapped metadata available when FeatureControlManager is first initialized?

Copy link
Contributor

@junrao junrao left a comment

Choose a reason for hiding this comment

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

@FrankYang0529 : Thanks for the updated PR. A couple of more comments.

How about initializing it to the bootstrapped metadata? If the role is quorum leader, it will eventually be initialized to the bootstrapped metadata. It is still valid as the "current" metadata version if the role is a standby controller.

Chatted with Colin offline a bit. The bootstrap MV (from the bootstrap file) in QC can be higher or lower than the MV in the log. So, it should only be used if the log is empty. We could use MINIMUM_VERSION for initialization and the correct MV will be set after replaying the log. If the log is empty, in production, we prepend the bootstrap MV in QC. In testing, any test depending on the correct MV needs to explicitly ensure that the correct MV is populated.

return activeController(false);
}

QuorumController activeController(boolean waitForActivation) throws InterruptedException {
Copy link
Contributor

Choose a reason for hiding this comment

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

controlEnv.activeController() is called multiple times in some of the tests. It's a bit wasteful to have to append/wait for a new event in every call. Perhaps we could cache the returned QuorumController and reuse it in those tests.

@ijuma
Copy link
Member

ijuma commented Feb 24, 2025

We could use MINIMUM_VERSION for initialization and the correct MV will be set after replaying the log.

This is error-prone though. While working on #18845, we found an existing race condition that was hard to detect because we silently used the wrong metadata version in a number of situations. I fear this approach may lead to similar problems.

I think it's cleanest to leave the metadata version unset until we actually read it from the log.

@junrao
Copy link
Contributor

junrao commented Feb 24, 2025

I think it's cleanest to leave the metadata version unset until we actually read it from the log.

That's safer. The only question is what to do in FeatureControlManager.metadataVersion() when MV is not set yet. Returning an option requires every caller to check it. Perhaps we could throw an exception since the caller is not expected to use this method when MV is not set yet.

@ijuma
Copy link
Member

ijuma commented Feb 24, 2025

Yes, that's the approach we took with FeaturesImage. Most callers use metadataVersionOrThrow. The rare use cases that may call it before it's set call the version that returns Optional and keep retrying until it's set (which is the desired behavior - it's not safe for them to proceed until then).

@chia7712
Copy link
Member

that's the approach we took with FeaturesImage. Most callers use metadataVersionOrThrow. The rare use cases that may call it before it's set call the version that returns Optional and keep retrying until it's set (which is the desired behavior - it's not safe for them to proceed until then).

I think it includes following changes.

  1. remove metadataVersion from FeatureControlManager constructor, since the TimelineObject<MetadataVersion> will be changed to TimelineObject<Optional<MetadataVersion>>. And it is initialized with Optional.empty
  2. FeatureControlManager#metadataVersion should be changed to metadataVersionOrThrow, similar to FeaturesImage
  3. CompleteActivationEvent should NOT use FeatureControlManager#metadataVersionOrThrowif the log is empty, since the FeatureControlManager has not yet set the MV.

If above list is correct, maybe we can address it in follow-up?

@chia7712
Copy link
Member

If above list is correct, maybe we can address it in follow-up?

open https://issues.apache.org/jira/browse/KAFKA-18858

@junrao
Copy link
Contributor

junrao commented Feb 24, 2025

  1. CompleteActivationEvent should NOT use FeatureControlManager#metadataVersionOrThrow if the log is empty, since the FeatureControlManager has not yet set the MV.

We could change FeatureControlManager.metadataVersion to return an option and use it in CompleteActivationEvent. With this, we could remove the isEmpty param in ActivationRecordsGenerator.generate() since it's the same as metadataVersion being empty.

We could do 1-3 in a followup jira.

@@ -373,7 +373,7 @@ public void testElrEnabledByDefault() throws Throwable {
)).
build()
) {
controlEnv.activeController(true);
controlEnv.activeController();
Copy link
Contributor

Choose a reason for hiding this comment

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

Thinking a bit more. It seems that this test exposes a real bug introduced in https://github.com/apache/kafka/pull/16848/files#diff-77dc2adb187fd078084644613cff2b53021c8a5fbcdcfa116515734609d1332a.

In the following code, we pick up the finalized feature version (including MV) first and pass them to the registerBroker event. It's possible that the passed in MV is outdated, but when the registerBroker event is processed, the MV becomes update to date. This will fail a registerBroker request that shouldn't be failed. It seems that we should pick up the finalized feature version when the registerBroker event is processed.

public CompletableFuture<BrokerRegistrationReply> registerBroker(
    ControllerRequestContext context,
    BrokerRegistrationRequestData request
) {
    // populate finalized features map with latest known kraft version for validation
    Map<String, Short> controllerFeatures = new HashMap<>(featureControl.finalizedFeatures(Long.MAX_VALUE).featureMap());
    controllerFeatures.put(KRaftVersion.FEATURE_NAME, raftClient.kraftVersion().featureLevel());
    return appendWriteEvent("registerBroker", context.deadlineNs(),
        () -> clusterControl.
            registerBroker(request, offsetControl.nextWriteOffset(),
                new FinalizedControllerFeatures(controllerFeatures, Long.MAX_VALUE),
                context.requestHeader().requestApiVersion() >= 3),
        EnumSet.noneOf(ControllerOperationFlag.class));
}

Colin confirmed that this is an issue. In general, we should not be accessing featureControl.finalizedFeatures outside of the event queue thread since it introduces potential concurrency issues.

Copy link
Member Author

Choose a reason for hiding this comment

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

I try to change the function as following and the test can pass without active controller check.

    @Override
    public CompletableFuture<BrokerRegistrationReply> registerBroker(
        ControllerRequestContext context,
        BrokerRegistrationRequestData request
    ) {
        return appendWriteEvent("registerBroker", context.deadlineNs(),
            () -> {
                // populate finalized features map with latest known kraft version for validation
                Map<String, Short> controllerFeatures = new HashMap<>(featureControl.finalizedFeatures(Long.MAX_VALUE).featureMap());
                controllerFeatures.put(KRaftVersion.FEATURE_NAME, raftClient.kraftVersion().featureLevel());
                return clusterControl.
                    registerBroker(request, offsetControl.nextWriteOffset(),
                        new FinalizedControllerFeatures(controllerFeatures, Long.MAX_VALUE),
                        context.requestHeader().requestApiVersion() >= 3);
            },
            EnumSet.noneOf(ControllerOperationFlag.class));
    }

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, exactly. If we fix the registerBroker code, just waiting for the QC to be the leader should be enough.

Copy link
Member

Choose a reason for hiding this comment

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

Nice catch @junrao!

…rs()

Signed-off-by: PoAn Yang <payang@apache.org>
@ijuma
Copy link
Member

ijuma commented Feb 25, 2025

Once we merge this, we should also backport it to 4.0 so the build is stable there (and the bug is fixed).

Copy link
Contributor

@junrao junrao left a comment

Choose a reason for hiding this comment

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

@FrankYang0529 : Thanks for the updated PR. Just a minor comment. Also, could we change the description of the jira and the PR since this is not just a flaky test fix?

@ahuang98 and @jsancio : Should we cherry pick this to 3.9 since the issue was introduced in https://github.com/apache/kafka/pull/16848/files#diff-77dc2adb187fd078084644613cff2b53021c8a5fbcdcfa116515734609d1332a?

context.requestHeader().requestApiVersion() >= 3),
() -> {
// Populate finalized features map with latest known kraft version for validation.
// Get the finalized features map in controller operation to avoid outdated features.
Copy link
Contributor

Choose a reason for hiding this comment

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

in controller operation => in the controller event handling thread

Copy link
Member

@ijuma ijuma Feb 25, 2025

Choose a reason for hiding this comment

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

Isn't the important point that both reads and writes have to happen within the controller event handling thread (versus the more specific finalized features map part)? Overly specific comments tend to go stale more easily.

Signed-off-by: PoAn Yang <payang@apache.org>
Copy link
Contributor

@junrao junrao left a comment

Choose a reason for hiding this comment

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

@FrankYang0529 : Thanks for the updated PR. LGTM too assuming the tests pass. Also, could you change the title/description of the jira and the PR?

@FrankYang0529 FrankYang0529 changed the title KAFKA-18844: Fix flaky QuorumControllerTest.testBalancePartitionLeaders() KAFKA-18844: Stale features information in QuorumController#registerBroker Feb 26, 2025
@FrankYang0529
Copy link
Member Author

Hi @junrao, thanks for the review. Updated title and description on Jira and the PR.

@mumrah
Copy link
Member

mumrah commented Feb 26, 2025

Regarding:
image

This was added in #18985 as part of the merge queue effort. It is not yet required for this check to pass, but if you want to fix the failed check you can add the "Reviewers:" line to the end of the PR body.

@junrao
Copy link
Contributor

junrao commented Feb 26, 2025

@FrankYang0529 : Thanks for the updated description. Just a minor comment.

This makes finalized features may be stale when processing registerBroker event. => This may make finalized features stale when processing registerBroker event.

Copy link
Contributor

@cmccabe cmccabe left a comment

Choose a reason for hiding this comment

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

LGTM for the current change. Thanks, all. I wish we had tools to find cases where we accessed variables from the wrong thread.

@jsancio
Copy link
Member

jsancio commented Feb 26, 2025

@junrao @cmccabe @ijuma @chia7712 I added 3.9.1 and 4.0.0 as the fix version so we should cherry pick it to those branches if you agree.

@chia7712
Copy link
Member

I added 3.9.1 and 4.0.0 as the fix version so we should cherry pick it to those branches if you agree.

+1

I wish we had tools to find cases where we accessed variables from the wrong thread.

open https://issues.apache.org/jira/browse/KAFKA-18877 to log/discuss it

@FrankYang0529
Copy link
Member Author

@junrao updated PR description. Also, added reviewers to it. Thanks.

@junrao
Copy link
Contributor

junrao commented Feb 27, 2025

@FrankYang0529 : Thanks for the updated description. LGTM

@jsancio : +1 on cherry-pick this to 3.9 too.

@junrao junrao merged commit 88a23da into apache:trunk Feb 27, 2025
27 checks passed
@junrao
Copy link
Contributor

junrao commented Feb 27, 2025

@jsancio : I merged the PR to trunk. Could you help cherry-pick it to 4.0 and 3.9?

@FrankYang0529 FrankYang0529 deleted the KAFKA-18844 branch February 28, 2025 01:01
FrankYang0529 added a commit to FrankYang0529/kafka that referenced this pull request Feb 28, 2025
…roker (apache#18997)

In apache#16848, we added `kraft.version`
to finalized features and got finalized features outside controller
event handling thread. This may make finalized features stale when
processing `registerBroker` event. Also, some cases like
`QuorumControllerTest.testBalancePartitionLeaders` become flaky cause of
outdated MV. This PR moves finalized features back to controller event
handling thread to avoid the error.

Reviewers: Ismael Juma <ijuma@apache.org>, Jun Rao <junrao@gmail.com>,
Colin P. McCabe <cmccabe@apache.org>, Chia-Ping Tsai
<chia7712@gmail.com>

(cherry picked from commit 88a23da)
FrankYang0529 added a commit to FrankYang0529/kafka that referenced this pull request Feb 28, 2025
…roker (apache#18997)

In apache#16848, we added `kraft.version`
to finalized features and got finalized features outside controller
event handling thread. This may make finalized features stale when
processing `registerBroker` event. Also, some cases like
`QuorumControllerTest.testBalancePartitionLeaders` become flaky cause of
outdated MV. This PR moves finalized features back to controller event
handling thread to avoid the error.

Reviewers: Ismael Juma <ijuma@apache.org>, Jun Rao <junrao@gmail.com>,
Colin P. McCabe <cmccabe@apache.org>, Chia-Ping Tsai
<chia7712@gmail.com>

(cherry picked from commit 88a23da)
@FrankYang0529
Copy link
Member Author

3.9 PR: #19058

FrankYang0529 added a commit to FrankYang0529/kafka that referenced this pull request Mar 2, 2025
…roker (apache#18997)

In apache#16848, we added `kraft.version`
to finalized features and got finalized features outside controller
event handling thread. This may make finalized features stale when
processing `registerBroker` event. Also, some cases like
`QuorumControllerTest.testBalancePartitionLeaders` become flaky cause of
outdated MV. This PR moves finalized features back to controller event
handling thread to avoid the error.

Reviewers: Ismael Juma <ijuma@apache.org>, Jun Rao <junrao@gmail.com>,
Colin P. McCabe <cmccabe@apache.org>, Chia-Ping Tsai
<chia7712@gmail.com>

(cherry picked from commit 88a23da)
dajac pushed a commit that referenced this pull request Mar 3, 2025
…roker (#18997)

In #16848, we added `kraft.version`
to finalized features and got finalized features outside controller
event handling thread. This may make finalized features stale when
processing `registerBroker` event. Also, some cases like
`QuorumControllerTest.testBalancePartitionLeaders` become flaky cause of
outdated MV. This PR moves finalized features back to controller event
handling thread to avoid the error.

Reviewers: Ismael Juma <ijuma@apache.org>, Jun Rao <junrao@gmail.com>,
Colin P. McCabe <cmccabe@apache.org>, Chia-Ping Tsai
<chia7712@gmail.com>
@dajac
Copy link
Member

dajac commented Mar 3, 2025

I cherry-picked the commit to 4.0. cc @junrao

azhar2407 pushed a commit to azhar2407/kafka that referenced this pull request Mar 4, 2025
…roker (apache#18997)

In apache#16848, we added `kraft.version`
to finalized features and got finalized features outside controller
event handling thread. This may make finalized features stale when
processing `registerBroker` event. Also, some cases like
`QuorumControllerTest.testBalancePartitionLeaders` become flaky cause of
outdated MV. This PR moves finalized features back to controller event
handling thread to avoid the error.

Reviewers: Ismael Juma <ijuma@apache.org>, Jun Rao <junrao@gmail.com>,
Colin P. McCabe <cmccabe@apache.org>, Chia-Ping Tsai
<chia7712@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kraft small Small PRs tests Test fixes (including flaky tests)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants