[fix] [broker] Ensure policy cache is updated before updateTopicPoliciesAsync returns.#21030
[fix] [broker] Ensure policy cache is updated before updateTopicPoliciesAsync returns.#21030thetumbled wants to merge 3 commits into
Conversation
|
@thetumbled Please add the following content to your PR description and select a checkbox: |
| .thenAccept(msg -> { | ||
| refreshTopicPoliciesCache(msg); | ||
| notifyListener(msg); | ||
| completeResults(TopicName.get(TopicName.get(msg.getKey()).getPartitionedTopicName())); |
There was a problem hiding this comment.
I dont think this is a right way to solve the problem. For examle:
If we have three different actions, e.g., setA and setB and setC:
- setA is waiting for futureA completed. The eventMsg is eventA.
- setB is waiting for futureB completed. The eventMsg is eventB.
- setC is waiting for futureC completed. The eventMsg is eventC.
- The method
completeResultswill complete futureA and futureB and futureC when read eventA. Howerver setB has not been refresh to the policies cache. - So setC maybe read the old B value
There was a problem hiding this comment.
Yes, this is the corner case for this solution, which can not handle with concurrent request for modifying topic policy. This solution fix the synchronize case.
And i wonder whether we have the need to implement strong concurrent controll? There are two directions:
- add a warning to users, that the topic policy setting api can not handle with concurrent cases
- or, i can implement a stronger concurrent controll.
What do you think? @AnonHxy @Demogorgon314 @codelipenghui
There was a problem hiding this comment.
OK. I see. Synchronize case is enough I think
| @VisibleForTesting | ||
| final Map<TopicName, List<TopicPolicyListener<TopicPolicies>>> listeners = new ConcurrentHashMap<>(); | ||
|
|
||
| final Map<TopicName, BlockingDeque<CompletableFuture>> results = new ConcurrentHashMap<>(); |
There was a problem hiding this comment.
I don't think it will resolve the issue completely because the next request might happened on different brokers.
There was a problem hiding this comment.
All request for setting topic policy will be redirected to the owner broker of the partition-0, so we do not need to consider distributed situations.
There was a problem hiding this comment.
All request for setting topic policy will be redirected to the owner broker of the partition-0, so we do not need to consider distributed situations.
Could you share the code which routed the HTTP request to the "owner broker of the partition-0"? I want to double confirm it. Thanks
There was a problem hiding this comment.
we have such redirection logic in: org.apache.pulsar.broker.admin.impl.PersistentTopicsBase#preValidation
return getPartitionedTopicMetadataAsync(topicName, false, false)
.thenCompose(metadata -> {
if (metadata.partitions > 0) {
return validateTopicOwnershipAsync(TopicName.get(topicName.toString()
+ PARTITIONED_TOPIC_SUFFIX + 0), authoritative);
} else {
return validateTopicOwnershipAsync(topicName, authoritative);
}
});
There was a problem hiding this comment.
There are two other concerns about the fix:
- The problem still exists if the Topic Owner is transferred to another broker
- Now the reading is correct. But the topic may have yet to apply the latest policy even if users received the response.
I'm also thinking about how we can implement this function simpler.
There was a problem hiding this comment.
Indeed, even if all the read and update operations occur on the same broker, it remains crucial to serialize all such operations. Without serialization, the broker would still be incapable of ensuring that every modification is carried out on the most up-to-date data.
Update from v1 -> v2
Update from v1 -> v3
The changes from v2 will be missed.
Additionally, during the transfer of ownership, it is possible for ongoing updates to continue taking place on the old broker. Meanwhile, new requests directed to the new broker may still have an opportunity to update the data based on a potentially "dirty read."
|
From my understanding now. It should be a problem like a dirty reading. Right? Since the design of the topic policy mechanism is the final consensus, it's a little bit hard for us to fix this kinda situation. IMO, we can try to define a redirect logic here.
Afterwards, we move a distributed problem to a single machine which will be easier for us to handle it.
|
Yes, it is dirty reading problem. So we do not need to consider distributed situations. @codelipenghui
Currently, we have two places need to read the messages, the first one is in the backgroup thread triggered by To achieve strong concurrency control, maybe we could make the whole setting logic running synchronously in same thread for each topic, which will make break change. But as you say, the design of the topic policy mechanism is the final consensus, we do not need such kind of strong guarantee, right? IMO, i don't see any drawback of this solution in synchronous requests situations, which is the basic situation and enough for users. In synchronous requests situations, there will be only one result in the queue at most at a time, which will be removed before the corresponding request returns. |
|
The pr had no activity for 30 days, mark with Stale label. |
|
The problem has been addressed with "PIP-428: Change TopicPoliciesService interface to fix consistency issues" and the implementation PR #24427. One of the main differences compared to the approach in this PR is that existing TopicPolicies instances are never mutated. Instead, the existing instance is cloned and the mutation happens on the cloned instance. This ensures that threads that are referencing the instance won't get an inconsistent view on the instance. The mutation will be atomic from this perspective and become available via the cache. A similar pattern is used for namespace policy updates. |
|
Closing since this has been addressed by PIP-428 implementation PR #24427 |
Motivation
Calling these two method synchronously to set the retention and ttl of one topic will result into unexpected results.
For example, if we call
We expect that the retention is set to 2880 minutes, ttl is set to
2880*16=172800seconds. But the result is that the retention policy is null !We read the messages from
__change_eventsderectly and find out the reason:We can see that the retention policy in the second message is null, which result into the null retention policy.
Diving into the implementation of
SystemTopicBasedTopicPoliciesService, we can see thatorg.apache.pulsar.broker.service.SystemTopicBasedTopicPoliciesService#updateTopicPoliciesAsyncreturns as soon as the message is written into__change_events, but do not guranteen that the topic policy in cache is updated. However, topic policy setting api will retrieve the topic policy from cache.Before the cache is updated, the second rest api for setting ttl arrived, and change the topic policy based on the old one, which result into the problem.
Modifications
The solution is: Ensure policy cache is updated before updateTopicPoliciesAsync returns.
Verifying this change
(Please pick either of the following options)
This change added tests and can be verified as follows:
Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes
Documentation
docdoc-requireddoc-not-neededdoc-completeMatching PR in forked repository
PR in forked repository: thetumbled#25