Skip to content

Commit fcf293f

Browse files
authored
Report overall mapping size in cluster stats (#87556)
Adds measures of the total size of all mappings and the total number of fields in the cluster (both before and after deduplication). Relates #86639 Relates #77466
1 parent fe327c6 commit fcf293f

File tree

6 files changed

+185
-27
lines changed

6 files changed

+185
-27
lines changed

docs/changelog/87556.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 87556
2+
summary: Report overall mapping size in cluster stats
3+
area: Cluster Coordination
4+
type: enhancement
5+
issues: []

docs/reference/cluster/stats.asciidoc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,22 @@ Contains statistics about <<mapping,field mappings>> in selected nodes.
485485
.Properties of `mappings`
486486
[%collapsible%open]
487487
=====
488+
`total_field_count`::
489+
(integer)
490+
Total number of fields in all non-system indices.
491+
492+
`total_deduplicated_field_count`::
493+
(integer)
494+
Total number of fields in all non-system indices, accounting for mapping deduplication.
495+
496+
`total_deduplicated_mapping_size`::
497+
(<<byte-units, byte units>>)
498+
Total size of all mappings after deduplication and compression.
499+
500+
`total_deduplicated_mapping_size_in_bytes`::
501+
(integer)
502+
Total size of all mappings, in bytes, after deduplication and compression.
503+
488504
`field_types`::
489505
(array of objects)
490506
Contains statistics about <<mapping-types,field data types>> used in selected
@@ -1363,6 +1379,10 @@ The API returns the following response:
13631379
"file_sizes": {}
13641380
},
13651381
"mappings": {
1382+
"total_field_count": 0,
1383+
"total_deduplicated_field_count": 0,
1384+
"total_deduplicated_mapping_size": "0b",
1385+
"total_deduplicated_mapping_size_in_bytes": 0,
13661386
"field_types": [],
13671387
"runtime_field_types": []
13681388
},

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,21 @@
166166
- match: { indices.mappings.runtime_field_types.1.doc_max: 0 }
167167
- match: { indices.mappings.runtime_field_types.1.doc_total: 0 }
168168

169+
---
170+
"mappings sizes reported in get cluster stats":
171+
- skip:
172+
version: " - 8.3.99"
173+
reason: "mapping sizes reported from 8.4 onwards"
174+
- do:
175+
indices.create:
176+
index: sensor
177+
body:
178+
mappings:
179+
"properties":
180+
"field":
181+
"type": "keyword"
182+
183+
- do: {cluster.stats: {}}
184+
- gt: { indices.mappings.total_field_count: 0 }
185+
- gt: { indices.mappings.total_deduplicated_field_count: 0 }
186+
- gt: { indices.mappings.total_deduplicated_mapping_size_in_bytes: 0 }

server/src/main/java/org/elasticsearch/action/admin/cluster/stats/MappingStats.java

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88

99
package org.elasticsearch.action.admin.cluster.stats;
1010

11+
import org.elasticsearch.Version;
1112
import org.elasticsearch.cluster.metadata.IndexMetadata;
1213
import org.elasticsearch.cluster.metadata.MappingMetadata;
1314
import org.elasticsearch.cluster.metadata.Metadata;
1415
import org.elasticsearch.common.Strings;
1516
import org.elasticsearch.common.io.stream.StreamInput;
1617
import org.elasticsearch.common.io.stream.StreamOutput;
1718
import org.elasticsearch.common.io.stream.Writeable;
19+
import org.elasticsearch.common.unit.ByteSizeValue;
20+
import org.elasticsearch.core.Nullable;
1821
import org.elasticsearch.xcontent.ToXContentFragment;
1922
import org.elasticsearch.xcontent.XContentBuilder;
2023

@@ -29,7 +32,9 @@
2932
import java.util.List;
3033
import java.util.Map;
3134
import java.util.Objects;
35+
import java.util.OptionalLong;
3236
import java.util.Set;
37+
import java.util.concurrent.atomic.AtomicLong;
3338
import java.util.regex.Matcher;
3439
import java.util.regex.Pattern;
3540

@@ -57,6 +62,8 @@ public static MappingStats of(Metadata metadata, Runnable ensureNotCancelled) {
5762
}
5863
AnalysisStats.countMapping(mappingCounts, indexMetadata);
5964
}
65+
final AtomicLong totalFieldCount = new AtomicLong();
66+
final AtomicLong totalDeduplicatedFieldCount = new AtomicLong();
6067
for (Map.Entry<MappingMetadata, Integer> mappingAndCount : mappingCounts.entrySet()) {
6168
ensureNotCancelled.run();
6269
Set<String> indexFieldTypes = new HashSet<>();
@@ -73,6 +80,8 @@ public static MappingStats of(Metadata metadata, Runnable ensureNotCancelled) {
7380
type = "object";
7481
}
7582
if (type != null) {
83+
totalDeduplicatedFieldCount.incrementAndGet();
84+
totalFieldCount.addAndGet(count);
7685
FieldStats stats;
7786
if (type.equals("dense_vector")) {
7887
stats = fieldTypes.computeIfAbsent(type, DenseVectorFieldStats::new);
@@ -134,7 +143,17 @@ public static MappingStats of(Metadata metadata, Runnable ensureNotCancelled) {
134143
}
135144
});
136145
}
137-
return new MappingStats(fieldTypes.values(), runtimeFieldTypes.values());
146+
long totalMappingSizeBytes = 0L;
147+
for (MappingMetadata mappingMetadata : metadata.getMappingsByHash().values()) {
148+
totalMappingSizeBytes += mappingMetadata.source().compressed().length;
149+
}
150+
return new MappingStats(
151+
totalFieldCount.get(),
152+
totalDeduplicatedFieldCount.get(),
153+
totalMappingSizeBytes,
154+
fieldTypes.values(),
155+
runtimeFieldTypes.values()
156+
);
138157
}
139158

140159
private static void updateScriptParams(Object scriptSourceObject, FieldScriptStats scriptStats, int multiplier) {
@@ -157,10 +176,28 @@ private static int countOccurrences(String script, Pattern pattern) {
157176
return occurrences;
158177
}
159178

179+
@Nullable // for BwC
180+
private final Long totalFieldCount;
181+
182+
@Nullable // for BwC
183+
private final Long totalDeduplicatedFieldCount;
184+
185+
@Nullable // for BwC
186+
private final Long totalMappingSizeBytes;
187+
160188
private final List<FieldStats> fieldTypeStats;
161189
private final List<RuntimeFieldStats> runtimeFieldStats;
162190

163-
MappingStats(Collection<FieldStats> fieldTypeStats, Collection<RuntimeFieldStats> runtimeFieldStats) {
191+
MappingStats(
192+
long totalFieldCount,
193+
long totalDeduplicatedFieldCount,
194+
long totalMappingSizeBytes,
195+
Collection<FieldStats> fieldTypeStats,
196+
Collection<RuntimeFieldStats> runtimeFieldStats
197+
) {
198+
this.totalFieldCount = totalFieldCount;
199+
this.totalDeduplicatedFieldCount = totalDeduplicatedFieldCount;
200+
this.totalMappingSizeBytes = totalMappingSizeBytes;
164201
List<FieldStats> stats = new ArrayList<>(fieldTypeStats);
165202
stats.sort(Comparator.comparing(IndexFeatureStats::getName));
166203
this.fieldTypeStats = Collections.unmodifiableList(stats);
@@ -170,16 +207,57 @@ private static int countOccurrences(String script, Pattern pattern) {
170207
}
171208

172209
MappingStats(StreamInput in) throws IOException {
210+
if (in.getVersion().onOrAfter(Version.V_8_4_0)) {
211+
totalFieldCount = in.readOptionalVLong();
212+
totalDeduplicatedFieldCount = in.readOptionalVLong();
213+
totalMappingSizeBytes = in.readOptionalVLong();
214+
} else {
215+
totalFieldCount = null;
216+
totalDeduplicatedFieldCount = null;
217+
totalMappingSizeBytes = null;
218+
}
173219
fieldTypeStats = Collections.unmodifiableList(in.readList(FieldStats::new));
174220
runtimeFieldStats = Collections.unmodifiableList(in.readList(RuntimeFieldStats::new));
175221
}
176222

177223
@Override
178224
public void writeTo(StreamOutput out) throws IOException {
225+
if (out.getVersion().onOrAfter(Version.V_8_4_0)) {
226+
out.writeOptionalVLong(totalFieldCount);
227+
out.writeOptionalVLong(totalDeduplicatedFieldCount);
228+
out.writeOptionalVLong(totalMappingSizeBytes);
229+
} // else just omit these stats, they're not computed on older nodes anyway
179230
out.writeCollection(fieldTypeStats);
180231
out.writeCollection(runtimeFieldStats);
181232
}
182233

234+
private static OptionalLong ofNullable(Long l) {
235+
return l == null ? OptionalLong.empty() : OptionalLong.of(l);
236+
}
237+
238+
/**
239+
* @return the total number of fields (in non-system indices), or {@link OptionalLong#empty()} if omitted (due to BwC)
240+
*/
241+
public OptionalLong getTotalFieldCount() {
242+
return ofNullable(totalFieldCount);
243+
}
244+
245+
/**
246+
* @return the total number of fields (in non-system indices) accounting for deduplication, or {@link OptionalLong#empty()} if omitted
247+
* (due to BwC)
248+
*/
249+
public OptionalLong getTotalDeduplicatedFieldCount() {
250+
return ofNullable(totalDeduplicatedFieldCount);
251+
}
252+
253+
/**
254+
* @return the total size of all mappings (including those for system indices) accounting for deduplication and compression, or {@link
255+
* OptionalLong#empty()} if omitted (due to BwC).
256+
*/
257+
public OptionalLong getTotalMappingSizeBytes() {
258+
return ofNullable(totalMappingSizeBytes);
259+
}
260+
183261
/**
184262
* Return stats about field types.
185263
*/
@@ -197,6 +275,19 @@ public List<RuntimeFieldStats> getRuntimeFieldStats() {
197275
@Override
198276
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
199277
builder.startObject("mappings");
278+
if (totalFieldCount != null) {
279+
builder.field("total_field_count", totalFieldCount);
280+
}
281+
if (totalDeduplicatedFieldCount != null) {
282+
builder.field("total_deduplicated_field_count", totalDeduplicatedFieldCount);
283+
}
284+
if (totalMappingSizeBytes != null) {
285+
builder.humanReadableField(
286+
"total_deduplicated_mapping_size_in_bytes",
287+
"total_deduplicated_mapping_size",
288+
ByteSizeValue.ofBytes(totalMappingSizeBytes)
289+
);
290+
}
200291
builder.startArray("field_types");
201292
for (IndexFeatureStats st : fieldTypeStats) {
202293
st.toXContent(builder, params);
@@ -218,15 +309,18 @@ public String toString() {
218309

219310
@Override
220311
public boolean equals(Object o) {
221-
if (o instanceof MappingStats == false) {
222-
return false;
223-
}
312+
if (this == o) return true;
313+
if (o == null || getClass() != o.getClass()) return false;
224314
MappingStats that = (MappingStats) o;
225-
return fieldTypeStats.equals(that.fieldTypeStats) && runtimeFieldStats.equals(that.runtimeFieldStats);
315+
return Objects.equals(totalFieldCount, that.totalFieldCount)
316+
&& Objects.equals(totalDeduplicatedFieldCount, that.totalDeduplicatedFieldCount)
317+
&& Objects.equals(totalMappingSizeBytes, that.totalMappingSizeBytes)
318+
&& fieldTypeStats.equals(that.fieldTypeStats)
319+
&& runtimeFieldStats.equals(that.runtimeFieldStats);
226320
}
227321

228322
@Override
229323
public int hashCode() {
230-
return Objects.hash(fieldTypeStats, runtimeFieldStats);
324+
return Objects.hash(totalFieldCount, totalDeduplicatedFieldCount, totalMappingSizeBytes, fieldTypeStats, runtimeFieldStats);
231325
}
232326
}

server/src/test/java/org/elasticsearch/action/admin/cluster/stats/MappingStatsTests.java

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.elasticsearch.script.Script;
2020
import org.elasticsearch.tasks.TaskCancelledException;
2121
import org.elasticsearch.test.AbstractWireSerializingTestCase;
22+
import org.elasticsearch.test.ESTestCase;
2223
import org.elasticsearch.test.VersionUtils;
2324
import org.hamcrest.Matchers;
2425

@@ -104,6 +105,10 @@ public void testToXContent() {
104105
assertEquals("""
105106
{
106107
"mappings" : {
108+
"total_field_count" : 12,
109+
"total_deduplicated_field_count" : 6,
110+
"total_deduplicated_mapping_size" : "260b",
111+
"total_deduplicated_mapping_size_in_bytes" : 260,
107112
"field_types" : [
108113
{
109114
"name" : "dense_vector",
@@ -211,6 +216,10 @@ public void testToXContentWithSomeSharedMappings() {
211216
assertEquals("""
212217
{
213218
"mappings" : {
219+
"total_field_count" : 18,
220+
"total_deduplicated_field_count" : 12,
221+
"total_deduplicated_mapping_size" : "519b",
222+
"total_deduplicated_mapping_size_in_bytes" : 519,
214223
"field_types" : [
215224
{
216225
"name" : "dense_vector",
@@ -328,7 +337,7 @@ protected MappingStats createTestInstance() {
328337
if (randomBoolean()) {
329338
runtimeFieldStats.add(randomRuntimeFieldStats("long"));
330339
}
331-
return new MappingStats(stats, runtimeFieldStats);
340+
return new MappingStats(randomNonNegativeLong(), randomNonNegativeLong(), randomNonNegativeLong(), stats, runtimeFieldStats);
332341
}
333342

334343
private static FieldStats randomFieldStats(String type) {
@@ -368,32 +377,41 @@ private static RuntimeFieldStats randomRuntimeFieldStats(String type) {
368377
return stats;
369378
}
370379

380+
@SuppressWarnings("OptionalGetWithoutIsPresent")
371381
@Override
372382
protected MappingStats mutateInstance(MappingStats instance) throws IOException {
373383
List<FieldStats> fieldTypes = new ArrayList<>(instance.getFieldTypeStats());
374384
List<RuntimeFieldStats> runtimeFieldTypes = new ArrayList<>(instance.getRuntimeFieldStats());
375-
if (randomBoolean()) {
376-
boolean remove = fieldTypes.size() > 0 && randomBoolean();
377-
if (remove) {
378-
fieldTypes.remove(randomInt(fieldTypes.size() - 1));
379-
}
380-
if (remove == false || randomBoolean()) {
381-
FieldStats s = new FieldStats("float");
382-
s.count = 13;
383-
s.indexCount = 2;
384-
fieldTypes.add(s);
385-
}
386-
} else {
387-
boolean remove = runtimeFieldTypes.size() > 0 && randomBoolean();
388-
if (remove) {
389-
runtimeFieldTypes.remove(randomInt(runtimeFieldTypes.size() - 1));
385+
long totalFieldCount = instance.getTotalFieldCount().getAsLong();
386+
long totalDeduplicatedFieldCount = instance.getTotalDeduplicatedFieldCount().getAsLong();
387+
long totalMappingSizeBytes = instance.getTotalMappingSizeBytes().getAsLong();
388+
switch (between(1, 5)) {
389+
case 1 -> {
390+
boolean remove = fieldTypes.size() > 0 && randomBoolean();
391+
if (remove) {
392+
fieldTypes.remove(randomInt(fieldTypes.size() - 1));
393+
}
394+
if (remove == false || randomBoolean()) {
395+
FieldStats s = new FieldStats("float");
396+
s.count = 13;
397+
s.indexCount = 2;
398+
fieldTypes.add(s);
399+
}
390400
}
391-
if (remove == false || randomBoolean()) {
392-
runtimeFieldTypes.add(randomRuntimeFieldStats("double"));
401+
case 2 -> {
402+
boolean remove = runtimeFieldTypes.size() > 0 && randomBoolean();
403+
if (remove) {
404+
runtimeFieldTypes.remove(randomInt(runtimeFieldTypes.size() - 1));
405+
}
406+
if (remove == false || randomBoolean()) {
407+
runtimeFieldTypes.add(randomRuntimeFieldStats("double"));
408+
}
393409
}
410+
case 3 -> totalFieldCount = randomValueOtherThan(totalFieldCount, ESTestCase::randomNonNegativeLong);
411+
case 4 -> totalDeduplicatedFieldCount = randomValueOtherThan(totalDeduplicatedFieldCount, ESTestCase::randomNonNegativeLong);
412+
case 5 -> totalMappingSizeBytes = randomValueOtherThan(totalMappingSizeBytes, ESTestCase::randomNonNegativeLong);
394413
}
395-
396-
return new MappingStats(fieldTypes, runtimeFieldTypes);
414+
return new MappingStats(totalFieldCount, totalDeduplicatedFieldCount, totalMappingSizeBytes, fieldTypes, runtimeFieldTypes);
397415
}
398416

399417
public void testDenseVectorType() {

x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,9 @@ public void testToXContent() throws IOException {
541541
"file_sizes": {}
542542
},
543543
"mappings": {
544+
"total_field_count" : 0,
545+
"total_deduplicated_field_count" : 0,
546+
"total_deduplicated_mapping_size_in_bytes" : 0,
544547
"field_types": [],
545548
"runtime_field_types": []
546549
},

0 commit comments

Comments
 (0)