Skip to content

Commit 22a1ba7

Browse files
authored
Expose tier balancing stats via internal endpoint (#92199)
1 parent 5182748 commit 22a1ba7

File tree

15 files changed

+694
-40
lines changed

15 files changed

+694
-40
lines changed

docs/changelog/92199.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 92199
2+
summary: Expose tier balancing stats via internal endpoint
3+
area: "Allocation"
4+
type: enhancement
5+
issues: []

docs/reference/cluster/get-desired-balance.asciidoc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,56 @@ The API returns the following result:
3232
"computation_time_in_millis": 0,
3333
"reconciliation_time_in_millis": 0
3434
},
35+
"cluster_balance_stats" : {
36+
{
37+
"data_hot" : {
38+
"total_shard_size" : {
39+
"total" : 36.0,
40+
"min" : 10.0,
41+
"max" : 16.0,
42+
"average" : 12.0,
43+
"std_dev" : 2.8284271247461903
44+
},
45+
"total_write_load" : {
46+
"total" : 21.0,
47+
"min" : 6.0,
48+
"max" : 8.5,
49+
"average" : 7.0,
50+
"std_dev" : 1.0801234497346435
51+
},
52+
"shard_count" : {
53+
"total" : 7.0,
54+
"min" : 2.0,
55+
"max" : 3.0,
56+
"average" : 2.3333333333333335,
57+
"std_dev" : 0.4714045207910317
58+
}
59+
},
60+
"data_warm" : {
61+
"total_shard_size" : {
62+
"total" : 42.0,
63+
"min" : 12.0,
64+
"max" : 18.0,
65+
"average" : 14.0,
66+
"std_dev" : 2.8284271247461903
67+
},
68+
"total_write_load" : {
69+
"total" : 0.0,
70+
"min" : 0.0,
71+
"max" : 0.0,
72+
"average" : 0.0,
73+
"std_dev" : 0.0
74+
},
75+
"shard_count" : {
76+
"total" : 3.0,
77+
"min" : 1.0,
78+
"max" : 1.0,
79+
"average" : 1.0,
80+
"std_dev" : 0.0
81+
}
82+
}
83+
}
84+
},
3585
"routing_table": {
3686
"test": {
3787
"0": {

qa/smoke-test-multinode/src/yamlRestTest/resources/rest-api-spec/test/smoke_test_multinode/30_desired_balance.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,32 @@ setup:
6464
- gte: { routing_table.test.1.desired.ignored: 0 }
6565
- is_true: 'routing_table.test.1.desired.node_ids'
6666

67+
---
68+
"Test cluster_balance_stats":
69+
70+
- skip:
71+
version: " - 8.6.99"
72+
reason: "Field added in in 8.7.0"
73+
74+
- do:
75+
_internal.get_desired_balance: { }
76+
77+
- is_true: 'cluster_balance_stats'
78+
- is_true: 'cluster_balance_stats.data_content.total_shard_size'
79+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.total'
80+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.min'
81+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.max'
82+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.average'
83+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.std_dev'
84+
- is_true: 'cluster_balance_stats.data_content.total_write_load'
85+
- is_true: 'cluster_balance_stats.data_content.total_write_load.total'
86+
- is_true: 'cluster_balance_stats.data_content.total_write_load.min'
87+
- is_true: 'cluster_balance_stats.data_content.total_write_load.max'
88+
- is_true: 'cluster_balance_stats.data_content.total_write_load.average'
89+
- is_true: 'cluster_balance_stats.data_content.total_write_load.std_dev'
90+
- is_true: 'cluster_balance_stats.data_content.shard_count'
91+
- is_true: 'cluster_balance_stats.data_content.shard_count.total'
92+
- is_true: 'cluster_balance_stats.data_content.shard_count.min'
93+
- is_true: 'cluster_balance_stats.data_content.shard_count.max'
94+
- is_true: 'cluster_balance_stats.data_content.shard_count.average'
95+
- is_true: 'cluster_balance_stats.data_content.shard_count.std_dev'

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_balance/10_basic.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,36 @@ setup:
1919
- gte: { stats.reconciliation_time_in_millis: 0 }
2020
- match: { routing_table: {} }
2121

22+
---
23+
"Test cluster_balance_stats":
24+
25+
- skip:
26+
version: " - 8.6.99"
27+
reason: "Field added in in 8.7.0"
28+
29+
- do:
30+
_internal.get_desired_balance: { }
31+
32+
- is_true: 'cluster_balance_stats'
33+
- is_true: 'cluster_balance_stats.data_content.total_shard_size'
34+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.total'
35+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.min'
36+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.max'
37+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.average'
38+
- is_true: 'cluster_balance_stats.data_content.total_shard_size.std_dev'
39+
- is_true: 'cluster_balance_stats.data_content.total_write_load'
40+
- is_true: 'cluster_balance_stats.data_content.total_write_load.total'
41+
- is_true: 'cluster_balance_stats.data_content.total_write_load.min'
42+
- is_true: 'cluster_balance_stats.data_content.total_write_load.max'
43+
- is_true: 'cluster_balance_stats.data_content.total_write_load.average'
44+
- is_true: 'cluster_balance_stats.data_content.total_write_load.std_dev'
45+
- is_true: 'cluster_balance_stats.data_content.shard_count'
46+
- is_true: 'cluster_balance_stats.data_content.shard_count.total'
47+
- is_true: 'cluster_balance_stats.data_content.shard_count.min'
48+
- is_true: 'cluster_balance_stats.data_content.shard_count.max'
49+
- is_true: 'cluster_balance_stats.data_content.shard_count.average'
50+
- is_true: 'cluster_balance_stats.data_content.shard_count.std_dev'
51+
2252
---
2353
"Test get desired balance for single shard":
2454
- do:

server/src/main/java/org/elasticsearch/action/admin/cluster/allocation/DesiredBalanceResponse.java

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.action.ActionResponse;
1212
import org.elasticsearch.cluster.routing.AllocationId;
1313
import org.elasticsearch.cluster.routing.ShardRoutingState;
14+
import org.elasticsearch.cluster.routing.allocation.allocator.ClusterBalanceStats;
1415
import org.elasticsearch.cluster.routing.allocation.allocator.DesiredBalanceStats;
1516
import org.elasticsearch.common.collect.Iterators;
1617
import org.elasticsearch.common.io.stream.StreamInput;
@@ -29,26 +30,40 @@
2930
import java.util.Objects;
3031
import java.util.Set;
3132

33+
import static org.elasticsearch.common.xcontent.ChunkedToXContentHelper.singleChunk;
34+
3235
public class DesiredBalanceResponse extends ActionResponse implements ChunkedToXContentObject {
3336

37+
private static final Version CLUSTER_BALANCE_STATS_VERSION = Version.V_8_7_0;
38+
3439
private final DesiredBalanceStats stats;
40+
private final ClusterBalanceStats clusterBalanceStats;
3541
private final Map<String, Map<Integer, DesiredShards>> routingTable;
3642

37-
public DesiredBalanceResponse(DesiredBalanceStats stats, Map<String, Map<Integer, DesiredShards>> routingTable) {
43+
public DesiredBalanceResponse(
44+
DesiredBalanceStats stats,
45+
ClusterBalanceStats clusterBalanceStats,
46+
Map<String, Map<Integer, DesiredShards>> routingTable
47+
) {
3848
this.stats = stats;
49+
this.clusterBalanceStats = clusterBalanceStats;
3950
this.routingTable = routingTable;
4051
}
4152

4253
public static DesiredBalanceResponse from(StreamInput in) throws IOException {
4354
return new DesiredBalanceResponse(
4455
DesiredBalanceStats.readFrom(in),
56+
in.getVersion().onOrAfter(CLUSTER_BALANCE_STATS_VERSION) ? ClusterBalanceStats.readFrom(in) : ClusterBalanceStats.EMPTY,
4557
in.readImmutableMap(StreamInput::readString, v -> v.readImmutableMap(StreamInput::readVInt, DesiredShards::from))
4658
);
4759
}
4860

4961
@Override
5062
public void writeTo(StreamOutput out) throws IOException {
5163
stats.writeTo(out);
64+
if (out.getVersion().onOrAfter(CLUSTER_BALANCE_STATS_VERSION)) {
65+
clusterBalanceStats.writeTo(out);
66+
}
5267
out.writeMap(
5368
routingTable,
5469
StreamOutput::writeString,
@@ -62,26 +77,37 @@ public void writeTo(StreamOutput out) throws IOException {
6277

6378
@Override
6479
public Iterator<? extends ToXContent> toXContentChunked(ToXContent.Params params) {
65-
return Iterators.concat(Iterators.single((builder, p) -> {
66-
builder.startObject();
67-
builder.startObject("stats");
68-
stats.toXContent(builder, p);
69-
builder.endObject();
70-
return builder.startObject("routing_table");
71-
}), routingTable.entrySet().stream().map(indexEntry -> (ToXContent) (builder, p) -> {
80+
return Iterators.concat(
81+
singleChunk(
82+
(builder, p) -> builder.startObject(),
83+
(builder, p) -> builder.field("stats", stats),
84+
(builder, p) -> builder.field("cluster_balance_stats", clusterBalanceStats),
85+
(builder, p) -> builder.startObject("routing_table")
86+
),
87+
routingTableToXContentChunked(),
88+
singleChunk((builder, p) -> builder.endObject(), (builder, p) -> builder.endObject())
89+
);
90+
}
91+
92+
private Iterator<ToXContent> routingTableToXContentChunked() {
93+
return routingTable.entrySet().stream().map(indexEntry -> (ToXContent) (builder, p) -> {
7294
builder.startObject(indexEntry.getKey());
7395
for (Map.Entry<Integer, DesiredShards> shardEntry : indexEntry.getValue().entrySet()) {
7496
builder.field(String.valueOf(shardEntry.getKey()));
7597
shardEntry.getValue().toXContent(builder, p);
7698
}
7799
return builder.endObject();
78-
}).iterator(), Iterators.single((builder, p) -> builder.endObject().endObject()));
100+
}).iterator();
79101
}
80102

81103
public DesiredBalanceStats getStats() {
82104
return stats;
83105
}
84106

107+
public ClusterBalanceStats getClusterBalanceStats() {
108+
return clusterBalanceStats;
109+
}
110+
85111
public Map<String, Map<Integer, DesiredShards>> getRoutingTable() {
86112
return routingTable;
87113
}
@@ -91,17 +117,24 @@ public boolean equals(Object o) {
91117
if (this == o) return true;
92118
return o instanceof DesiredBalanceResponse that
93119
&& Objects.equals(stats, that.stats)
120+
&& Objects.equals(clusterBalanceStats, that.clusterBalanceStats)
94121
&& Objects.equals(routingTable, that.routingTable);
95122
}
96123

97124
@Override
98125
public int hashCode() {
99-
return Objects.hash(stats, routingTable);
126+
return Objects.hash(stats, clusterBalanceStats, routingTable);
100127
}
101128

102129
@Override
103130
public String toString() {
104-
return "DesiredBalanceResponse{stats=" + stats + ", routingTable=" + routingTable + "}";
131+
return "DesiredBalanceResponse{stats="
132+
+ stats
133+
+ ", clusterBalanceStats="
134+
+ clusterBalanceStats
135+
+ ", routingTable="
136+
+ routingTable
137+
+ "}";
105138
}
106139

107140
public record DesiredShards(List<ShardView> current, ShardAssignmentView desired) implements Writeable, ToXContentObject {

server/src/main/java/org/elasticsearch/action/admin/cluster/allocation/TransportGetDesiredBalanceAction.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
2121
import org.elasticsearch.cluster.routing.ShardRouting;
2222
import org.elasticsearch.cluster.routing.allocation.WriteLoadForecaster;
23+
import org.elasticsearch.cluster.routing.allocation.allocator.ClusterBalanceStats;
2324
import org.elasticsearch.cluster.routing.allocation.allocator.DesiredBalance;
2425
import org.elasticsearch.cluster.routing.allocation.allocator.DesiredBalanceShardsAllocator;
2526
import org.elasticsearch.cluster.routing.allocation.allocator.ShardAssignment;
@@ -86,6 +87,19 @@ protected void masterOperation(
8687
listener.onFailure(new ResourceNotFoundException("Desired balance is not computed yet"));
8788
return;
8889
}
90+
listener.onResponse(
91+
new DesiredBalanceResponse(
92+
desiredBalanceShardsAllocator.getStats(),
93+
ClusterBalanceStats.createFrom(state, writeLoadForecaster),
94+
createRoutingTable(state, latestDesiredBalance)
95+
)
96+
);
97+
}
98+
99+
private Map<String, Map<Integer, DesiredBalanceResponse.DesiredShards>> createRoutingTable(
100+
ClusterState state,
101+
DesiredBalance latestDesiredBalance
102+
) {
89103
Map<String, Map<Integer, DesiredBalanceResponse.DesiredShards>> routingTable = new HashMap<>();
90104
for (IndexRoutingTable indexRoutingTable : state.routingTable()) {
91105
Map<Integer, DesiredBalanceResponse.DesiredShards> indexDesiredShards = new HashMap<>();
@@ -134,7 +148,7 @@ protected void masterOperation(
134148
}
135149
routingTable.put(indexRoutingTable.getIndex().getName(), indexDesiredShards);
136150
}
137-
listener.onResponse(new DesiredBalanceResponse(desiredBalanceShardsAllocator.getStats(), routingTable));
151+
return routingTable;
138152
}
139153

140154
@Override

0 commit comments

Comments
 (0)