Skip to content
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

Add two phased commit to Cluster State publishing #13062

Merged
merged 19 commits into from
Sep 14, 2015

Conversation

bleskes
Copy link
Contributor

@bleskes bleskes commented Aug 23, 2015

When publishing a new cluster state, the master will send it to all the node of the cluster, noting down how many master nodes responded successfully. The nodes do not yet process the new cluster state, but rather park it in memory. As soon as at least minimum master nodes have ack-ed the cluster state change, it is committed and a commit request is sent to all the node that responded so far (and will respond in the future). Once receiving the commit requests the nodes continue to process the cluster state change as they did before this change.

A few notable comments:

  1. For this change to have effect, min master nodes must be configured.
  2. All basic cluster state validation is done in the first phase of publish and is thus now part of ShardOperationResult
  3. A new COMMIT_TIMEOUT settings is introduced, dictating how long a master should wait for nodes to ack the first phase. Unlike PUBLISH_TIMEOUT, if waiting for a commit times out, the cluster state change will be rejected.
  4. Failing to achieve a min master node of acks, will cause the master to step down as it clearly doesn't have enough active followers.
  5. Previously there was a short window between the moment a master lost it's followers and it stepping down because of node fault detection failures. In this short window, the master could process any change (but fail to publish it). This PR closes this gap to 0.

I still have one no commit and some docs to add but I think we can start the review cycles.

@brwe @imotov and @jasontedor - can you have a careful look when you have time?

@bleskes bleskes added review resiliency :Distributed/Discovery-Plugins Anything related to our integration plugins with EC2, GCP and Azure labels Aug 23, 2015
@bleskes bleskes changed the title Add two phased to Cluster State publishing Add two phased commit to Cluster State publishing Aug 23, 2015
@@ -57,6 +69,7 @@ public DiscoverySettings(Settings settings, NodeSettingsService nodeSettingsServ
nodeSettingsService.addListener(new ApplySettings());
this.noMasterBlock = parseNoMasterBlock(settings.get(NO_MASTER_BLOCK, DEFAULT_NO_MASTER_BLOCK));
this.publishTimeout = settings.getAsTime(PUBLISH_TIMEOUT, publishTimeout);
this.commitTimeout = settings.getAsTime(COMMIT_TIMEOUT, publishTimeout);
Copy link
Contributor

Choose a reason for hiding this comment

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

should this be settings.getAsTime(COMMIT_TIMEOUT, commitTimeout); ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ykes. Yes… :(

On 24 Aug 2015, at 11:57, Britta Weber notifications@github.com wrote:

In core/src/main/java/org/elasticsearch/discovery/DiscoverySettings.java:

@@ -57,6 +69,7 @@ public DiscoverySettings(Settings settings, NodeSettingsService nodeSettingsServ
nodeSettingsService.addListener(new ApplySettings());
this.noMasterBlock = parseNoMasterBlock(settings.get(NO_MASTER_BLOCK, DEFAULT_NO_MASTER_BLOCK));
this.publishTimeout = settings.getAsTime(PUBLISH_TIMEOUT, publishTimeout);

  •    this.commitTimeout = settings.getAsTime(COMMIT_TIMEOUT, publishTimeout);
    

should this be settings.getAsTime(COMMIT_TIMEOUT, commitTimeout); ?


Reply to this email directly or view it on GitHub.

@bleskes
Copy link
Contributor Author

bleskes commented Aug 24, 2015

@brwe @imotov @jasontedor FYI I removed the no commit.

String stateUUID;

public CommitClusterStateRequest() {
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This constructor doesn't seem to be necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's needed for the request to be created on the receiving node. It's used by reflection.

@bleskes
Copy link
Contributor Author

bleskes commented Aug 25, 2015

I update the docs. @clintongormley @jasontedor - I would love a native English speaker to review it - can you take a look?

@jasontedor
Copy link
Member

@bleskes I left a review of the updated docs but I'm still in progress on a review of the code.

to 30 seconds and can be changed dynamically through the
<<cluster-update-settings,cluster update settings api>>
the other nodes in the cluster. Each node receives the publish message, acknowledges
it but do *not* yet apply it. If the master does not receive acknowledgement from
Copy link
Contributor

Choose a reason for hiding this comment

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

do -> does

@clintongormley
Copy link
Contributor

left some minor docs suggestions

logger.debug("publishing cluster state version [{}]", newClusterState.version());
try {
discoveryService.publish(clusterChangedEvent, ackListener);
} catch (Throwable t) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we need that to catch the FailedToCommitException? If so, why not only catch that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good one. I've tightened things up to make sure can rely on FaileToCommitException to indicate something wrong happened before committing and we can safely reject the CS (it will not be committed on any node)

@bleskes
Copy link
Contributor Author

bleskes commented Aug 26, 2015

@imotov @brwe pushed another commit addressing comments so far

// ignore & restore interrupt
Thread.currentThread().interrupt();
} catch (IOException e) {
throw new ElasticsearchException("failed to serialize cluster_state for publishing to node {}", e, node);
}
}
}

private void sendFullClusterState(ClusterState clusterState, @Nullable Map<Version, BytesReference> serializedStates,
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like with the latest changes serializedStates can no longer be null, so we should probably remove Nullable here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

correct. Removed.

@brwe
Copy link
Contributor

brwe commented Aug 28, 2015

Just #13062 (comment) left and I am not too passionate about it. LGTM too otherwise.

@bleskes
Copy link
Contributor Author

bleskes commented Aug 28, 2015

@brwe thx. I update the PR based on your last comment (and rebased to latest master)

bleskes added a commit to bleskes/elasticsearch that referenced this pull request Sep 11, 2015
The initial implementation of two phase commit based cluster state publishing (elastic#13062) relied on a single in memory "pending" cluster state that is only processed by ZenDiscovery once committed by the master. While this is fine on it's own, it resulted in an issue with acknowledged APIs, such as the open index API, in the extreme case where a node falls behind and receives a commit message after a new cluster state has been published. Specifically:

1) Master receives and acked-API call and publishes cluster state CS1
2) Master waits for a min-master nodes to receives CS1 and commits it.
3) All nodes that have responded to CS1 are sent a commit message, however, node N didn't respond yet
4) Master waits for publish timeout (defaults to 30s) for all nodes to process the commit. Node N fails to do so.
5) Master publishes a cluster state CS2. Node N responds to cluster state CS1's publishing but receives cluster state CS2 before the commit for CS1 arrives.
6) The commit message for cluster CS1 is processed on node N, but fails because CS2 is pending. This caused the acked API in step 1 to return (but CS2 , is not yet processed).

In this case, the action indicated by CS1 is not yet executed on node N and therefore the acked API calls return pre-maturely. Note that once CS2 is processed but the change in CS1 takes effect (cluster state operations are safe to batch and we do so all the time).

An example failure can be found on: http://build-us-00.elastic.co/job/es_feature_two_phase_pub/314/

This commit extracts the already existing pending cluster state queue (processNewClusterStates) from ZenDiscovery into it's own class, which serves as a temporary container for in-flight cluster states. Once committed the cluster states are transferred to ZenDiscovery as they used to before. This allows "lagging" cluster states to still be successfully committed and processed (and likely to be ignored as a newer cluster state has already been processed).

As a side effect, all batching logic is now extracted from ZenDiscovery and is unit tested.
@bleskes bleskes merged commit 218979d into elastic:master Sep 14, 2015
bleskes added a commit that referenced this pull request Sep 14, 2015
When publishing a new cluster state, the master will send it to all the node of the cluster, noting down how many *master* nodes responded successfully. The nodes do not yet process the new cluster state, but rather park it in memory. As soon as at least minimum master nodes have ack-ed the cluster state change, it is committed and a commit request is sent to all the node that responded so far (and will respond in the future). Once receiving the commit requests the nodes continue to process the cluster state change as they did before this change.

A few notable comments:
1. For this change to have effect, min master nodes must be configured.
2. All basic cluster state validation is done in the first phase of publish and is thus now part of `ShardOperationResult`
3. A new `COMMIT_TIMEOUT` settings is introduced, dictating how long a master should wait for nodes to ack the first phase. Unlike `PUBLISH_TIMEOUT`, if waiting for a commit times out, the cluster state change will be rejected.
4. Failing to achieve a min master node of acks, will cause the master to step down as it clearly doesn't have enough active followers.
5. Previously there was a short window between the moment a master lost it's followers and it stepping down because of node fault detection failures. In this short window, the master could process any change (but fail to publish it). This PR closes this gap to 0.
6. A dedicated pending cluster states queue was added to keep pending non-comitted cluster states and manage the logic around processing committed cluster states. See #13303 for details.

Closes #13062 , Closes #13303
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Distributed/Discovery-Plugins Anything related to our integration plugins with EC2, GCP and Azure >feature resiliency v5.0.0-alpha1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants