Skip to content

Commit 159eb72

Browse files
authored
Return cached segments stats if include_unloaded_segments is true (elastic#39698)
Today we don't return segments stats for closed indices which makes it hard to tell how much memory such an index would require. With this change we return the statistics if requested by setting `include_unloaded_segments` to true on the rest request. Relates to elastic#39512
1 parent c46120f commit 159eb72

File tree

7 files changed

+133
-10
lines changed

7 files changed

+133
-10
lines changed

rest-api-spec/src/main/resources/rest-api-spec/api/indices.stats.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@
5757
"type": "boolean",
5858
"description": "If set to true segment stats will include stats for segments that are not currently loaded into memory",
5959
"default": false
60+
},
61+
"expand_wildcards": {
62+
"type" : "enum",
63+
"options" : ["open","closed","none","all"],
64+
"default" : "open",
65+
"description" : "Whether to expand wildcard expression to concrete indices that are open, closed or both."
66+
},
67+
"forbid_closed_indices": {
68+
"type": "boolean",
69+
"description": "If set to false stats will also collected from closed indices if explicitly specified or if expand_wildcards expands to closed indices",
70+
"default": true
6071
}
6172
}
6273
},
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
setup:
3+
- do:
4+
indices.create:
5+
index: test
6+
body:
7+
settings:
8+
number_of_shards: 1
9+
number_of_replicas: 0
10+
11+
- do:
12+
cluster.health:
13+
wait_for_no_initializing_shards: true
14+
15+
---
16+
"Segment Stats":
17+
18+
- skip:
19+
version: " - 7.99.99"
20+
reason: forbid_closed_indices is not supported in ealier version
21+
22+
- do:
23+
indices.stats:
24+
metric: [ segments ]
25+
- set: { indices.test.primaries.segments.count: num_segments }
26+
27+
- do:
28+
index:
29+
index: test
30+
id: 1
31+
body: { "foo": "bar" }
32+
33+
- do:
34+
indices.flush:
35+
index: test
36+
37+
- do:
38+
indices.stats:
39+
metric: [ segments ]
40+
- gt: { indices.test.primaries.segments.count: $num_segments }
41+
- set: { indices.test.primaries.segments.count: num_segments_after_flush }
42+
43+
- do:
44+
indices.close:
45+
index: test
46+
47+
- do:
48+
indices.stats:
49+
metric: segments
50+
expand_wildcards: closed
51+
forbid_closed_indices: false
52+
53+
- match: { indices.test.primaries.segments.count: 0 }
54+
55+
- do:
56+
indices.stats:
57+
metric: segments
58+
include_unloaded_segments: true
59+
expand_wildcards: closed
60+
forbid_closed_indices: false
61+
62+
- match: { indices.test.primaries.segments.count: $num_segments_after_flush }

server/src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@ public IndicesStatsRequest includeSegmentFileSizes(boolean includeSegmentFileSiz
271271
return this;
272272
}
273273

274+
public IndicesStatsRequest includeUnloadedSegments(boolean includeUnloadedSegments) {
275+
flags.includeUnloadedSegments(includeUnloadedSegments);
276+
return this;
277+
}
278+
274279
@Override
275280
public void writeTo(StreamOutput out) throws IOException {
276281
super.writeTo(out);

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

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323
import org.apache.lucene.index.IndexCommit;
2424
import org.apache.lucene.index.IndexWriter;
2525
import org.apache.lucene.index.LeafReader;
26+
import org.apache.lucene.index.LeafReaderContext;
27+
import org.apache.lucene.index.SegmentReader;
2628
import org.apache.lucene.store.Directory;
29+
import org.elasticsearch.common.lucene.Lucene;
2730

2831
import java.io.IOException;
32+
import java.io.UncheckedIOException;
2933
import java.util.List;
3034
import java.util.function.Function;
3135

@@ -36,8 +40,20 @@
3640
*/
3741
public final class NoOpEngine extends ReadOnlyEngine {
3842

43+
private final SegmentsStats stats;
44+
3945
public NoOpEngine(EngineConfig config) {
4046
super(config, null, null, true, Function.identity());
47+
this.stats = new SegmentsStats();
48+
Directory directory = store.directory();
49+
try (DirectoryReader reader = DirectoryReader.open(directory)) {
50+
for (LeafReaderContext ctx : reader.getContext().leaves()) {
51+
SegmentReader segmentReader = Lucene.segmentReader(ctx.reader());
52+
fillSegmentStats(segmentReader, true, stats);
53+
}
54+
} catch (IOException e) {
55+
throw new UncheckedIOException(e);
56+
}
4157
}
4258

4359
@Override
@@ -47,17 +63,17 @@ protected DirectoryReader open(final IndexCommit commit) throws IOException {
4763
final IndexCommit indexCommit = indexCommits.get(indexCommits.size() - 1);
4864
return new DirectoryReader(directory, new LeafReader[0]) {
4965
@Override
50-
protected DirectoryReader doOpenIfChanged() throws IOException {
66+
protected DirectoryReader doOpenIfChanged() {
5167
return null;
5268
}
5369

5470
@Override
55-
protected DirectoryReader doOpenIfChanged(IndexCommit commit) throws IOException {
71+
protected DirectoryReader doOpenIfChanged(IndexCommit commit) {
5672
return null;
5773
}
5874

5975
@Override
60-
protected DirectoryReader doOpenIfChanged(IndexWriter writer, boolean applyAllDeletes) throws IOException {
76+
protected DirectoryReader doOpenIfChanged(IndexWriter writer, boolean applyAllDeletes) {
6177
return null;
6278
}
6379

@@ -67,17 +83,17 @@ public long getVersion() {
6783
}
6884

6985
@Override
70-
public boolean isCurrent() throws IOException {
86+
public boolean isCurrent() {
7187
return true;
7288
}
7389

7490
@Override
75-
public IndexCommit getIndexCommit() throws IOException {
91+
public IndexCommit getIndexCommit() {
7692
return indexCommit;
7793
}
7894

7995
@Override
80-
protected void doClose() throws IOException {
96+
protected void doClose() {
8197
}
8298

8399
@Override
@@ -86,4 +102,18 @@ public CacheHelper getReaderCacheHelper() {
86102
}
87103
};
88104
}
105+
106+
@Override
107+
public SegmentsStats segmentsStats(boolean includeSegmentFileSizes, boolean includeUnloadedSegments) {
108+
if (includeUnloadedSegments) {
109+
final SegmentsStats stats = new SegmentsStats();
110+
stats.add(this.stats);
111+
if (includeSegmentFileSizes == false) {
112+
stats.clearFileSizes();
113+
}
114+
return stats;
115+
} else {
116+
return super.segmentsStats(includeSegmentFileSizes, includeUnloadedSegments);
117+
}
118+
}
89119
}

server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestIndicesStatsAction.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ public String getName() {
6969
@Override
7070
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
7171
IndicesStatsRequest indicesStatsRequest = new IndicesStatsRequest();
72-
indicesStatsRequest.indicesOptions(IndicesOptions.fromRequest(request, indicesStatsRequest.indicesOptions()));
72+
boolean forbidClosedIndices = request.paramAsBoolean("forbid_closed_indices", true);
73+
IndicesOptions defaultIndicesOption = forbidClosedIndices ? indicesStatsRequest.indicesOptions()
74+
: IndicesOptions.strictExpandOpen();
75+
assert indicesStatsRequest.indicesOptions() == IndicesOptions.strictExpandOpenAndForbidClosed() : "IndicesStats default indices " +
76+
"options changed";
77+
indicesStatsRequest.indicesOptions(IndicesOptions.fromRequest(request, defaultIndicesOption));
7378
indicesStatsRequest.indices(Strings.splitStringByCommaToArray(request.param("index")));
7479
indicesStatsRequest.types(Strings.splitStringByCommaToArray(request.param("types")));
7580

@@ -121,7 +126,7 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC
121126

122127
if (indicesStatsRequest.segments()) {
123128
indicesStatsRequest.includeSegmentFileSizes(request.paramAsBoolean("include_segment_file_sizes", false));
124-
indicesStatsRequest.includeSegmentFileSizes(request.paramAsBoolean("include_unloaded_segments", false));
129+
indicesStatsRequest.includeUnloadedSegments(request.paramAsBoolean("include_unloaded_segments", false));
125130
}
126131

127132
return channel -> client.admin().indices().stats(indicesStatsRequest, new RestToXContentListener<>(channel));

server/src/main/java/org/elasticsearch/rest/action/cat/RestIndicesAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public void processResponse(final ClusterHealthResponse clusterHealthResponse) {
116116
indicesStatsRequest.indices(indices);
117117
indicesStatsRequest.indicesOptions(strictExpandIndicesOptions);
118118
indicesStatsRequest.all();
119-
indicesStatsRequest.includeSegmentFileSizes(request.paramAsBoolean("include_unloaded_segments", false));
119+
indicesStatsRequest.includeUnloadedSegments(request.paramAsBoolean("include_unloaded_segments", false));
120120

121121
client.admin().indices().stats(indicesStatsRequest, new RestResponseListener<IndicesStatsResponse>(channel) {
122122
@Override

server/src/test/java/org/elasticsearch/index/engine/NoOpEngineTests.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public void testNoopAfterRegularEngine() throws IOException {
100100
noOpEngine.close();
101101
}
102102

103-
public void testNoOpEngineDocStats() throws Exception {
103+
public void testNoOpEngineStats() throws Exception {
104104
IOUtils.close(engine, store);
105105
final AtomicLong globalCheckpoint = new AtomicLong(SequenceNumbers.NO_OPS_PERFORMED);
106106
try (Store store = createStore()) {
@@ -131,15 +131,25 @@ public void testNoOpEngineDocStats() throws Exception {
131131
}
132132

133133
final DocsStats expectedDocStats;
134+
boolean includeFileSize = randomBoolean();
135+
final SegmentsStats expectedSegmentStats;
134136
try (InternalEngine engine = createEngine(config)) {
135137
expectedDocStats = engine.docStats();
138+
expectedSegmentStats = engine.segmentsStats(includeFileSize, true);
136139
}
137140

138141
try (NoOpEngine noOpEngine = new NoOpEngine(config)) {
139142
assertEquals(expectedDocStats.getCount(), noOpEngine.docStats().getCount());
140143
assertEquals(expectedDocStats.getDeleted(), noOpEngine.docStats().getDeleted());
141144
assertEquals(expectedDocStats.getTotalSizeInBytes(), noOpEngine.docStats().getTotalSizeInBytes());
142145
assertEquals(expectedDocStats.getAverageSizeInBytes(), noOpEngine.docStats().getAverageSizeInBytes());
146+
assertEquals(expectedSegmentStats.getCount(), noOpEngine.segmentsStats(includeFileSize, true).getCount());
147+
assertEquals(expectedSegmentStats.getMemoryInBytes(), noOpEngine.segmentsStats(includeFileSize, true).getMemoryInBytes());
148+
assertEquals(expectedSegmentStats.getFileSizes().size(),
149+
noOpEngine.segmentsStats(includeFileSize, true).getFileSizes().size());
150+
151+
assertEquals(0, noOpEngine.segmentsStats(includeFileSize, false).getFileSizes().size());
152+
assertEquals(0, noOpEngine.segmentsStats(includeFileSize, false).getMemoryInBytes());
143153
} catch (AssertionError e) {
144154
logger.error(config.getMergePolicy());
145155
throw e;

0 commit comments

Comments
 (0)