-
Notifications
You must be signed in to change notification settings - Fork 24.8k
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
Conversation
@@ -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); |
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.
should this be settings.getAsTime(COMMIT_TIMEOUT, commitTimeout);
?
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.
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.
@brwe @imotov @jasontedor FYI I removed the no commit. |
String stateUUID; | ||
|
||
public CommitClusterStateRequest() { | ||
} |
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.
This constructor doesn't seem to be necessary.
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.
it's needed for the request to be created on the receiving node. It's used by reflection.
I update the docs. @clintongormley @jasontedor - I would love a native English speaker to review it - can you take a look? |
@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 |
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.
do -> does
left some minor docs suggestions |
logger.debug("publishing cluster state version [{}]", newClusterState.version()); | ||
try { | ||
discoveryService.publish(clusterChangedEvent, ackListener); | ||
} catch (Throwable t) { |
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 guess we need that to catch the FailedToCommitException
? If so, why not only catch that?
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.
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)
// 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, |
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.
It looks like with the latest changes serializedStates can no longer be null, so we should probably remove Nullable
here.
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.
correct. Removed.
Just #13062 (comment) left and I am not too passionate about it. LGTM too otherwise. |
…a CS is never committed after publishing is marked out as timed out
251b27b
to
218979d
Compare
@brwe thx. I update the PR based on your last comment (and rebased to latest master) |
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.
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
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:
ShardOperationResult
COMMIT_TIMEOUT
settings is introduced, dictating how long a master should wait for nodes to ack the first phase. UnlikePUBLISH_TIMEOUT
, if waiting for a commit times out, the cluster state change will be rejected.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?