Skip to content

Commit fe4c3e7

Browse files
authored
Enhanced segment files sizes information in Nodes Stats/Indices Stats APIs (#71643)
Since #16661 it is possible to know the total sizes for some Lucene segment files by using the Node Stats or Indices Stats API with the include_segment_file_sizes parameter, and the list of file extensions has been extended in #71416. This commit adds a bit more information about file sizes like the number of files (count), the min, max and average file sizes in bytes that share the same extension. Here is a sample: "cfs" : { "description" : "Compound Files", "size_in_bytes" : 2260, "min_size_in_bytes" : 2260, "max_size_in_bytes" : 2260, "average_size_in_bytes" : 2260, "count" : 1 } This commit also simplifies how compound file sizes were computed: before compound segment files were extracted and sizes aggregated with regular non-compound files sizes (which can be confusing and out of the scope of the original issue #6728), now CFS/CFE files appears as distinct files. These new information are provided to give a better view of the segment files and are useful in many cases, specially with frozen searchable snapshots whose segment stats can now be introspected thanks to the include_unloaded_segments parameter.
1 parent 84a1553 commit fe4c3e7

File tree

9 files changed

+397
-130
lines changed

9 files changed

+397
-130
lines changed

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/30_segments.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,42 @@ setup:
6161
forbid_closed_indices: false
6262

6363
- match: { indices.test.primaries.segments.count: $num_segments_after_flush }
64+
65+
66+
---
67+
"Indices Stats API with extended files stats":
68+
69+
- skip:
70+
version: " - 7.99.99"
71+
reason: segment files stats extended in 8.0.0
72+
73+
- do:
74+
index:
75+
index: test
76+
id: 1
77+
body: { "foo": "bar" }
78+
79+
- do:
80+
indices.flush:
81+
index: test
82+
83+
- do:
84+
indices.stats:
85+
metric: [ segments ]
86+
include_segment_file_sizes: true
87+
88+
- is_true: _all.total.segments.file_sizes
89+
- is_true: _all.total.segments.file_sizes.si
90+
- gt: { _all.total.segments.file_sizes.si.count: 0 }
91+
- gt: { _all.total.segments.file_sizes.si.size_in_bytes: 0 }
92+
- gt: { _all.total.segments.file_sizes.si.min_size_in_bytes: 0 }
93+
- gt: { _all.total.segments.file_sizes.si.max_size_in_bytes: 0 }
94+
- gt: { _all.total.segments.file_sizes.si.average_size_in_bytes: 0 }
95+
96+
- is_true: indices.test.primaries.segments.file_sizes
97+
- is_true: indices.test.primaries.segments.file_sizes.si
98+
- gt: { indices.test.primaries.segments.file_sizes.si.count: 0 }
99+
- gt: { indices.test.primaries.segments.file_sizes.si.size_in_bytes: 0 }
100+
- gt: { indices.test.primaries.segments.file_sizes.si.min_size_in_bytes: 0 }
101+
- gt: { indices.test.primaries.segments.file_sizes.si.max_size_in_bytes: 0 }
102+
- gt: { indices.test.primaries.segments.file_sizes.si.average_size_in_bytes: 0 }

server/src/internalClusterTest/java/org/elasticsearch/indices/stats/IndexStatsIT.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package org.elasticsearch.indices.stats;
1010

11+
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
1112
import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
1213
import org.elasticsearch.action.DocWriteResponse;
1314
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
@@ -44,6 +45,7 @@
4445
import org.elasticsearch.index.MergeSchedulerConfig;
4546
import org.elasticsearch.index.VersionType;
4647
import org.elasticsearch.index.cache.query.QueryCacheStats;
48+
import org.elasticsearch.index.engine.SegmentsStats;
4749
import org.elasticsearch.index.engine.VersionConflictEngineException;
4850
import org.elasticsearch.index.query.QueryBuilders;
4951
import org.elasticsearch.index.shard.IndexShard;
@@ -607,11 +609,23 @@ public void testSegmentsStats() {
607609
client().admin().indices().prepareFlush().get();
608610
client().admin().indices().prepareForceMerge().setMaxNumSegments(1).execute().actionGet();
609611
client().admin().indices().prepareRefresh().get();
610-
stats = client().admin().indices().prepareStats().setSegments(true).get();
612+
613+
final boolean includeSegmentFileSizes = randomBoolean();
614+
stats = client().admin().indices().prepareStats().setSegments(true).setIncludeSegmentFileSizes(includeSegmentFileSizes).get();
611615

612616
assertThat(stats.getTotal().getSegments(), notNullValue());
613617
assertThat(stats.getTotal().getSegments().getCount(), equalTo((long) test1.totalNumShards));
614618
assertThat(stats.getTotal().getSegments().getMemoryInBytes(), greaterThan(0L));
619+
if (includeSegmentFileSizes) {
620+
assertThat(stats.getTotal().getSegments().getFiles().size(), greaterThan(0));
621+
for (ObjectObjectCursor<String, SegmentsStats.FileStats> cursor : stats.getTotal().getSegments().getFiles()) {
622+
assertThat(cursor.value.getExt(), notNullValue());
623+
assertThat(cursor.value.getTotal(), greaterThan(0L));
624+
assertThat(cursor.value.getCount(), greaterThan(0L));
625+
assertThat(cursor.value.getMin(), greaterThan(0L));
626+
assertThat(cursor.value.getMax(), greaterThan(0L));
627+
}
628+
}
615629
}
616630

617631
public void testAllFlags() throws Exception {

server/src/main/java/org/elasticsearch/index/engine/Engine.java

Lines changed: 30 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
import org.apache.lucene.search.ReferenceManager;
2626
import org.apache.lucene.search.similarities.Similarity;
2727
import org.apache.lucene.store.AlreadyClosedException;
28-
import org.apache.lucene.store.Directory;
29-
import org.apache.lucene.store.IOContext;
3028
import org.apache.lucene.util.Accountable;
3129
import org.apache.lucene.util.Accountables;
3230
import org.apache.lucene.util.SetOnce;
@@ -66,10 +64,8 @@
6664
import org.elasticsearch.search.suggest.completion.CompletionStats;
6765

6866
import java.io.Closeable;
69-
import java.io.FileNotFoundException;
7067
import java.io.IOException;
7168
import java.io.UncheckedIOException;
72-
import java.nio.file.NoSuchFileException;
7369
import java.util.Arrays;
7470
import java.util.Comparator;
7571
import java.util.HashMap;
@@ -135,7 +131,7 @@ protected Engine(EngineConfig engineConfig) {
135131
this.store = engineConfig.getStore();
136132
// we use the engine class directly here to make sure all subclasses have the same logger name
137133
this.logger = Loggers.getLogger(Engine.class,
138-
engineConfig.getShardId());
134+
engineConfig.getShardId());
139135
this.eventListener = engineConfig.getEventListener();
140136
}
141137

@@ -178,7 +174,7 @@ public DocsStats docStats() {
178174
// when indexing but not refreshing in general. Yet, if a refresh happens the internal searcher is refresh as well so we are
179175
// safe here.
180176
try (Searcher searcher = acquireSearcher("docStats", SearcherScope.INTERNAL)) {
181-
return docsStats(searcher.getIndexReader());
177+
return docsStats(searcher.getIndexReader());
182178
}
183179
}
184180

@@ -285,12 +281,14 @@ boolean throttleLockIsHeldByCurrentThread() { // to be used in assertions and te
285281

286282
/**
287283
* Returns the <code>true</code> iff this engine is currently under index throttling.
284+
*
288285
* @see #getIndexThrottleTimeInMillis()
289286
*/
290287
public abstract boolean isThrottled();
291288

292289
/**
293290
* Trims translog for terms below <code>belowTerm</code> and seq# above <code>aboveSeqNo</code>
291+
*
294292
* @see Translog#trimOperations(long, long)
295293
*/
296294
public abstract void trimOperationsFromTranslog(long belowTerm, long aboveSeqNo) throws EngineException;
@@ -785,86 +783,38 @@ protected void fillSegmentStats(SegmentReader segmentReader, boolean includeSegm
785783
stats.addNormsMemoryInBytes(guardedRamBytesUsed(segmentReader.getNormsReader()));
786784
stats.addPointsMemoryInBytes(guardedRamBytesUsed(segmentReader.getPointsReader()));
787785
stats.addDocValuesMemoryInBytes(guardedRamBytesUsed(segmentReader.getDocValuesReader()));
788-
789786
if (includeSegmentFileSizes) {
790-
// TODO: consider moving this to StoreStats
791-
stats.addFileSizes(getSegmentFileSizes(segmentReader));
787+
stats.addFiles(getSegmentFileSizes(segmentReader));
792788
}
793789
}
794790

795-
private ImmutableOpenMap<String, Long> getSegmentFileSizes(SegmentReader segmentReader) {
796-
Directory directory = null;
797-
SegmentCommitInfo segmentCommitInfo = segmentReader.getSegmentInfo();
798-
boolean useCompoundFile = segmentCommitInfo.info.getUseCompoundFile();
799-
if (useCompoundFile) {
800-
try {
801-
directory = engineConfig.getCodec().compoundFormat().getCompoundReader(segmentReader.directory(),
802-
segmentCommitInfo.info, IOContext.READ);
803-
} catch (IOException e) {
804-
logger.warn(() -> new ParameterizedMessage("Error when opening compound reader for Directory [{}] and " +
805-
"SegmentCommitInfo [{}]", segmentReader.directory(), segmentCommitInfo), e);
806-
807-
return ImmutableOpenMap.of();
808-
}
809-
} else {
810-
directory = segmentReader.directory();
811-
}
812-
813-
assert directory != null;
814-
815-
String[] files;
816-
if (useCompoundFile) {
817-
try {
818-
files = directory.listAll();
819-
} catch (IOException e) {
820-
final Directory finalDirectory = directory;
821-
logger.warn(() ->
822-
new ParameterizedMessage("Couldn't list Compound Reader Directory [{}]", finalDirectory), e);
823-
return ImmutableOpenMap.of();
824-
}
825-
} else {
826-
try {
827-
files = segmentReader.getSegmentInfo().files().toArray(new String[]{});
828-
} catch (IOException e) {
829-
logger.warn(() ->
830-
new ParameterizedMessage("Couldn't list Directory from SegmentReader [{}] and SegmentInfo [{}]",
831-
segmentReader, segmentReader.getSegmentInfo()), e);
832-
return ImmutableOpenMap.of();
833-
}
834-
}
835-
836-
ImmutableOpenMap.Builder<String, Long> map = ImmutableOpenMap.builder();
837-
for (String file : files) {
838-
String extension = IndexFileNames.getExtension(file);
839-
long length = 0L;
840-
try {
841-
length = directory.fileLength(file);
842-
} catch (NoSuchFileException | FileNotFoundException e) {
843-
final Directory finalDirectory = directory;
844-
logger.warn(() -> new ParameterizedMessage("Tried to query fileLength but file is gone [{}] [{}]",
845-
finalDirectory, file), e);
846-
} catch (IOException e) {
847-
final Directory finalDirectory = directory;
848-
logger.warn(() -> new ParameterizedMessage("Error when trying to query fileLength [{}] [{}]",
849-
finalDirectory, file), e);
850-
}
851-
if (length == 0L) {
852-
continue;
853-
}
854-
map.put(extension, length);
855-
}
856-
857-
if (useCompoundFile) {
858-
try {
859-
directory.close();
860-
} catch (IOException e) {
861-
final Directory finalDirectory = directory;
862-
logger.warn(() -> new ParameterizedMessage("Error when closing compound reader on Directory [{}]",
863-
finalDirectory), e);
791+
private ImmutableOpenMap<String, SegmentsStats.FileStats> getSegmentFileSizes(SegmentReader segmentReader) {
792+
try {
793+
final ImmutableOpenMap.Builder<String, SegmentsStats.FileStats> files = ImmutableOpenMap.builder();
794+
final SegmentCommitInfo segmentCommitInfo = segmentReader.getSegmentInfo();
795+
for (String fileName : segmentCommitInfo.files()) {
796+
String fileExtension = IndexFileNames.getExtension(fileName);
797+
if (fileExtension != null) {
798+
try {
799+
long fileLength = segmentReader.directory().fileLength(fileName);
800+
files.put(fileExtension, new SegmentsStats.FileStats(fileExtension, fileLength, 1L, fileLength, fileLength));
801+
} catch (IOException ioe) {
802+
logger.warn(() ->
803+
new ParameterizedMessage("Error when retrieving file length for [{}]", fileName), ioe);
804+
} catch (AlreadyClosedException ace) {
805+
logger.warn(() ->
806+
new ParameterizedMessage("Error when retrieving file length for [{}], directory is closed", fileName), ace);
807+
return ImmutableOpenMap.of();
808+
}
809+
}
864810
}
811+
return files.build();
812+
} catch (IOException e) {
813+
logger.warn(() ->
814+
new ParameterizedMessage("Error when listing files for segment reader [{}] and segment info [{}]",
815+
segmentReader, segmentReader.getSegmentInfo()), e);
816+
return ImmutableOpenMap.of();
865817
}
866-
867-
return map.build();
868818
}
869819

870820
protected void writerSegmentStats(SegmentsStats stats) {

server/src/main/java/org/elasticsearch/index/engine/NoOpEngine.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public SegmentsStats segmentsStats(boolean includeSegmentFileSizes, boolean incl
110110
final SegmentsStats stats = new SegmentsStats();
111111
stats.add(this.segmentsStats);
112112
if (includeSegmentFileSizes == false) {
113-
stats.clearFileSizes();
113+
stats.clearFiles();
114114
}
115115
return stats;
116116
} else {

0 commit comments

Comments
 (0)