-
Notifications
You must be signed in to change notification settings - Fork 376
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
🐛 Avoid duplicated Partitions being created during PartitionSet reconciliation #2889
Conversation
Signed-off-by: Frederic Giloux <fgiloux@redhat.com>
@fgiloux do you know why duplicates are being created? |
@@ -146,7 +146,25 @@ func (c *controller) reconcile(ctx context.Context, partitionSet *topologyv1alph | |||
partitionKey = partitionKey + "+" + key + "=" + oldMatchLabels[key] | |||
} | |||
if _, ok := matchLabelsMap[partitionKey]; ok { | |||
existingMatches[partitionKey] = struct{}{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's please use sets.String
Seems like the previous logic handled the delete-finally/tombstone case and the create-new case, but not the update case. I would suggest doing a real update or patch - if one reconciliation loop does both the delete & update you can get into lots of goofy race conditions. |
@ncdc, no I don't know. They are not created in the same reconciliation cycle. The count in the PartitionSet status is correctly set to 2, which is the length of the map from which Partitions get created. |
If I correctly understand what you are writing: Partitions don't get updated. If they match the current state of the PartitionSet they are kept otherwise they are removed |
I'm not familiar with this code, but this sounds potentially similar to some things I've seen in the past: if you are operating on data from the shared informer cache, it's possible your counts can be wrong, leading to duplicate creations. Here's an example from Cluster API:
We ran into a problem where we saw multiple "first" control plane machines being created because the reconcile function was called multiple times when the informer for machines was still showing 0 machines. We fixed this by doing a live client list to get the machine count, instead of relying on the cache. Not sure if this is relevant for your code, but maybe? |
/hold for now - would like to continue discussing why we think multiples are being created and if we can avoid that entirely |
Yes that could be it. |
@fgiloux if the code that you wrote in this PR runs, would the result be a DELETE followed by a CREATE in one reconcile loop? I was considering that to be the missing UPDATE case. |
702e3d1
to
e5a6863
Compare
@ncdc I ran the test 100 times and it did not fail. |
Sounds like a good plan. Is there ever a situation where it's ok to accidentally create duplicates, or do we really want to avoid them at all costs? I'm guessing we want to avoid? |
It is surely not desirable to have duplicates. Whether it is ok to accidentally create them in some situation I don't know. As a human interface it may not be an issue if it is corrected quickly enough. I think that it will depend on the upper mechanisms that will be built on top of this API and their tolerance of duplicates. The future may tell. |
result := []*topologyv1alpha1.Partition{} | ||
for i := range partitions.Items { | ||
for j := range partitions.Items[i].OwnerReferences { | ||
if partitions.Items[i].OwnerReferences[j].UID == partitionSet.UID { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also check API group and kind?
pkg/reconciler/topology/partitionset/partitionset_controller.go
Outdated
Show resolved
Hide resolved
listShards func(selector labels.Selector) ([]*corev1alpha1.Shard, error) | ||
listPartitionSets func() ([]*topologyv1alpha1.PartitionSet, error) | ||
getPartitionSet func(clusterName logicalcluster.Name, name string) (*topologyv1alpha1.PartitionSet, error) | ||
// getPartitionsByPartitionSet func(partitionSet *topologyv1alpha1.PartitionSet) ([]*topologyv1alpha1.Partition, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have a commented-out line here
@@ -71,12 +70,22 @@ func NewController( | |||
getPartitionSet: func(clusterName logicalcluster.Name, name string) (*topologyv1alpha1.PartitionSet, error) { | |||
return partitionSetClusterInformer.Lister().Cluster(clusterName).Get(name) | |||
}, | |||
getPartitionsByPartitionSet: func(partitionSet *topologyv1alpha1.PartitionSet) ([]*topologyv1alpha1.Partition, error) { | |||
key, err := kcpcache.MetaClusterNamespaceKeyFunc(partitionSet) | |||
// getPartitionsByPartitionSet is calling the API directly as otherwise duplicates are created when the cache is not updated quickly enough. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// getPartitionsByPartitionSet is calling the API directly as otherwise duplicates are created when the cache is not updated quickly enough. | |
// getPartitionsByPartitionSet calls the API directly instead of using an indexer as otherwise duplicates are created when the cache is not updated quickly enough. |
@@ -193,7 +193,7 @@ func TestPartitionSet(t *testing.T) { | |||
if len(partitions.Items) == 1 { | |||
return true, "" | |||
} | |||
return false, fmt.Sprintf("expected 1 partition, but got %d", len(partitions.Items)) | |||
return false, fmt.Sprintf("expected 1 partition, but got %d, details: %s", len(partitions.Items), spew.Sdump(partitions.Items)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine this prints hundreds of lines into the log - any way to make it more minimal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. I tried first to only print out the last state outside of the loop but did not get what I expected. On the other hand it also provides the history.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good - we can let it mature over time
Signed-off-by: Frederic Giloux <fgiloux@redhat.com>
/lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: stevekuznetsov The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
@ncdc anything left before this PR can get merged? |
Was on PTO; sorry I didn't clear the hold sooner! /unhold |
CI DNS issue |
Summary
In some rare cases (~1%) there are duplicated Partitions being created during the reconciliation of a PartitionSet. This PR adds a check for a duplicate and removes it if necessary.
Example from e2e tests:
Related issue(s)
Fixes #2865