Skip to content

Commit 0faf638

Browse files
author
Ali Beyad
committed
Blocked allocations on primary causes RED health
If the allocation decision for a primary shard was NO, this should cause the cluster health for the shard to go RED, even if the shard belongs to a newly created index or is part of cluster recovery. Relates elastic#9126
1 parent 417bd0c commit 0faf638

23 files changed

+540
-249
lines changed

core/src/main/java/org/elasticsearch/action/admin/indices/shards/TransportIndicesShardStoresAction.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,7 @@ protected void masterOperation(IndicesShardStoresRequest request, ClusterState s
101101
}
102102
for (IndexShardRoutingTable routing : indexShardRoutingTables) {
103103
final int shardId = routing.shardId().id();
104-
ClusterShardHealth shardHealth = new ClusterShardHealth(shardId,
105-
routing,
106-
indexMetaData.activeAllocationIds(shardId).isEmpty());
104+
ClusterShardHealth shardHealth = new ClusterShardHealth(shardId, routing, indexMetaData);
107105
if (request.shardStatuses().contains(shardHealth.getStatus())) {
108106
shardIdsToFetch.add(routing.shardId());
109107
}

core/src/main/java/org/elasticsearch/cluster/health/ClusterIndexHealth.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public ClusterIndexHealth(final IndexMetaData indexMetaData, final IndexRoutingT
5454

5555
for (IndexShardRoutingTable shardRoutingTable : indexRoutingTable) {
5656
int shardId = shardRoutingTable.shardId().id();
57-
shards.put(shardId, new ClusterShardHealth(shardId, shardRoutingTable, indexMetaData.activeAllocationIds(shardId).isEmpty()));
57+
shards.put(shardId, new ClusterShardHealth(shardId, shardRoutingTable, indexMetaData));
5858
}
5959

6060
// update the index status

core/src/main/java/org/elasticsearch/cluster/health/ClusterShardHealth.java

+36-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919

2020
package org.elasticsearch.cluster.health;
2121

22+
import org.elasticsearch.cluster.metadata.IndexMetaData;
2223
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
2324
import org.elasticsearch.cluster.routing.ShardRouting;
2425
import org.elasticsearch.cluster.routing.UnassignedInfo;
26+
import org.elasticsearch.cluster.routing.UnassignedInfo.AllocationStatus;
27+
import org.elasticsearch.cluster.routing.UnassignedInfo.Reason;
2528
import org.elasticsearch.common.io.stream.StreamInput;
2629
import org.elasticsearch.common.io.stream.StreamOutput;
2730
import org.elasticsearch.common.io.stream.Writeable;
@@ -38,7 +41,7 @@ public final class ClusterShardHealth implements Writeable {
3841
private final int unassignedShards;
3942
private final boolean primaryActive;
4043

41-
public ClusterShardHealth(final int shardId, final IndexShardRoutingTable shardRoutingTable, final boolean noActiveAllocationIds) {
44+
public ClusterShardHealth(final int shardId, final IndexShardRoutingTable shardRoutingTable, final IndexMetaData indexMetaData) {
4245
this.shardId = shardId;
4346
int computeActiveShards = 0;
4447
int computeRelocatingShards = 0;
@@ -66,7 +69,7 @@ public ClusterShardHealth(final int shardId, final IndexShardRoutingTable shardR
6669
computeStatus = ClusterHealthStatus.YELLOW;
6770
}
6871
} else {
69-
computeStatus = UnassignedInfo.unassignedPrimaryShardHealth(primaryRouting.unassignedInfo(), noActiveAllocationIds);
72+
computeStatus = getInactivePrimaryHealth(primaryRouting, indexMetaData);
7073
}
7174
this.status = computeStatus;
7275
this.activeShards = computeActiveShards;
@@ -125,4 +128,35 @@ public void writeTo(final StreamOutput out) throws IOException {
125128
out.writeBoolean(primaryActive);
126129
}
127130

131+
/**
132+
* Checks if an inactive primary shard should cause the cluster health to go RED.
133+
*
134+
* Normally, an inactive primary shard in an index should cause the cluster health to be RED. However,
135+
* there are exceptions where a health status of RED is inappropriate, namely in these scenarios:
136+
* 1. Index Creation. When an index is first created, the primary shards are in the initializing state, so
137+
* there is a small window where the cluster health is RED due to the primaries not being activated yet.
138+
* However, this leads to a false sense that the cluster is in an unhealthy state, when in reality, its
139+
* simply a case of needing to wait for the primaries to initialize.
140+
* 2. When a cluster is in the recovery state, and the shard never had any allocation ids assigned to it,
141+
* which indicates the index was created and before allocation of the primary occurred for this shard,
142+
* a cluster restart happened.
143+
*
144+
* Here, we check for these scenarios and set the cluster health to YELLOW if any are applicable.
145+
*
146+
* NB: this method should *not* be called on active shards nor on non-primary shards.
147+
*/
148+
public static ClusterHealthStatus getInactivePrimaryHealth(final ShardRouting shardRouting, final IndexMetaData indexMetaData) {
149+
assert shardRouting.primary() : "cannot invoke on a replica shard: " + shardRouting;
150+
assert shardRouting.active() == false : "cannot invoke on an active shard: " + shardRouting;
151+
assert shardRouting.unassignedInfo() != null : "cannot invoke on a shard with no UnassignedInfo: " + shardRouting;
152+
final UnassignedInfo unassignedInfo = shardRouting.unassignedInfo();
153+
if (unassignedInfo.getLastAllocationStatus() != AllocationStatus.DECIDERS_NO
154+
&& shardRouting.allocatedPostIndexCreate(indexMetaData) == false
155+
&& (unassignedInfo.getReason() == Reason.INDEX_CREATED || unassignedInfo.getReason() == Reason.CLUSTER_RECOVERED)) {
156+
return ClusterHealthStatus.YELLOW;
157+
} else {
158+
return ClusterHealthStatus.RED;
159+
}
160+
}
161+
128162
}

core/src/main/java/org/elasticsearch/cluster/routing/RoutingNodes.java

+25-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.cluster.ClusterState;
2626
import org.elasticsearch.cluster.metadata.MetaData;
2727
import org.elasticsearch.cluster.node.DiscoveryNode;
28+
import org.elasticsearch.cluster.routing.UnassignedInfo.AllocationStatus;
2829
import org.elasticsearch.common.Nullable;
2930
import org.elasticsearch.common.Randomness;
3031
import org.elasticsearch.common.collect.Tuple;
@@ -641,14 +642,27 @@ public List<ShardRouting> ignored() {
641642
* Should be used with caution, typically,
642643
* the correct usage is to removeAndIgnore from the iterator.
643644
* @see #ignored()
644-
* @see UnassignedIterator#removeAndIgnore()
645+
* @see UnassignedIterator#removeAndIgnore(AllocationStatus)
645646
* @see #isIgnoredEmpty()
647+
* @return true iff the decision caused a change to the unassigned info
646648
*/
647-
public void ignoreShard(ShardRouting shard) {
649+
public boolean ignoreShard(ShardRouting shard, AllocationStatus allocationStatus) {
650+
boolean changed = false;
648651
if (shard.primary()) {
649652
ignoredPrimaries++;
653+
UnassignedInfo currInfo = shard.unassignedInfo();
654+
assert currInfo != null;
655+
if (allocationStatus.equals(currInfo.getLastAllocationStatus()) == false) {
656+
UnassignedInfo newInfo = new UnassignedInfo(currInfo.getReason(), currInfo.getMessage(), currInfo.getFailure(),
657+
currInfo.getNumFailedAllocations(), currInfo.getUnassignedTimeInNanos(),
658+
currInfo.getUnassignedTimeInMillis(), currInfo.isDelayed(),
659+
allocationStatus);
660+
shard = shard.updateUnassignedInfo(newInfo);
661+
changed = true;
662+
}
650663
}
651664
ignored.add(shard);
665+
return changed;
652666
}
653667

654668
public class UnassignedIterator implements Iterator<ShardRouting> {
@@ -685,10 +699,13 @@ public ShardRouting initialize(String nodeId, @Nullable String existingAllocatio
685699
* will be added back to unassigned once the metadata is constructed again).
686700
* Typically this is used when an allocation decision prevents a shard from being allocated such
687701
* that subsequent consumers of this API won't try to allocate this shard again.
702+
*
703+
* @param attempt the result of the allocation attempt
704+
* @return true iff the decision caused an update to the unassigned info
688705
*/
689-
public void removeAndIgnore() {
706+
public boolean removeAndIgnore(AllocationStatus attempt) {
690707
innerRemove();
691-
ignoreShard(current);
708+
return ignoreShard(current, attempt);
692709
}
693710

694711
private void updateShardRouting(ShardRouting shardRouting) {
@@ -721,7 +738,7 @@ public ShardRouting demotePrimaryToReplicaShard() {
721738
}
722739

723740
/**
724-
* Unsupported operation, just there for the interface. Use {@link #removeAndIgnore()} or
741+
* Unsupported operation, just there for the interface. Use {@link #removeAndIgnore(AllocationStatus)} or
725742
* {@link #initialize(String, String, long)}.
726743
*/
727744
@Override
@@ -747,8 +764,8 @@ public boolean isEmpty() {
747764

748765
/**
749766
* Returns <code>true</code> iff any unassigned shards are marked as temporarily ignored.
750-
* @see UnassignedShards#ignoreShard(ShardRouting)
751-
* @see UnassignedIterator#removeAndIgnore()
767+
* @see UnassignedShards#ignoreShard(ShardRouting, AllocationStatus)
768+
* @see UnassignedIterator#removeAndIgnore(AllocationStatus)
752769
*/
753770
public boolean isIgnoredEmpty() {
754771
return ignored.isEmpty();
@@ -878,6 +895,7 @@ public static boolean assertShardStats(RoutingNodes routingNodes) {
878895
assert inactiveShardCount == routingNodes.inactiveShardCount :
879896
"Inactive Shard count [" + inactiveShardCount + "] but RoutingNodes returned inactive shards [" + routingNodes.inactiveShardCount + "]";
880897
assert routingNodes.getRelocatingShardCount() == relocating : "Relocating shards mismatch [" + routingNodes.getRelocatingShardCount() + "] but expected [" + relocating + "]";
898+
881899
return true;
882900
}
883901

0 commit comments

Comments
 (0)