Skip to content

Commit d713b9e

Browse files
authored
Add Clear Cache API for Searchable Snapshots (#53009)
This commit adds an API to clear the cache used for searchable snapshots: POST /_searchable_snapshots/cache/clear POST <index>/_searchable_snapshots/cache/clear This API is useful in tests or to simply free disk space if needed. Note that this API does not clear cache stats. Relates #50999
1 parent 7f8cd95 commit d713b9e

14 files changed

+686
-82
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
setup:
3+
4+
- do:
5+
indices.create:
6+
index: docs
7+
body:
8+
settings:
9+
number_of_shards: 1
10+
number_of_replicas: 0
11+
12+
- do:
13+
bulk:
14+
body:
15+
- index:
16+
_index: docs
17+
_id: 1
18+
- field: doc
19+
- index:
20+
_index: docs
21+
_id: 2
22+
- field: doc
23+
- index:
24+
_index: docs
25+
_id: 3
26+
- field: other
27+
28+
- do:
29+
snapshot.create_repository:
30+
repository: repository-fs
31+
body:
32+
type: fs
33+
settings:
34+
location: "repository-fs"
35+
36+
# Remove the snapshot if a previous test failed to delete it.
37+
# Useful for third party tests that runs the test against a real external service.
38+
- do:
39+
snapshot.delete:
40+
repository: repository-fs
41+
snapshot: snapshot
42+
ignore: 404
43+
44+
- do:
45+
snapshot.create:
46+
repository: repository-fs
47+
snapshot: snapshot
48+
wait_for_completion: true
49+
50+
- do:
51+
indices.delete:
52+
index: docs
53+
54+
- do:
55+
snapshot.create_repository:
56+
repository: repository-searchable-snapshots
57+
body:
58+
type: searchable
59+
settings:
60+
delegate_type: fs
61+
location: "repository-fs"
62+
63+
---
64+
teardown:
65+
66+
- do:
67+
snapshot.delete:
68+
repository: repository-fs
69+
snapshot: snapshot
70+
ignore: 404
71+
72+
- do:
73+
snapshot.delete_repository:
74+
repository: repository-fs
75+
76+
- do:
77+
snapshot.delete_repository:
78+
repository: repository-searchable-snapshots
79+
80+
---
81+
"Clear searchable snapshots cache":
82+
- skip:
83+
version: " - 7.99.99"
84+
reason: searchable snapshots introduced in 8.0
85+
86+
- do:
87+
catch: missing
88+
searchable_snapshots.clear_cache: {}
89+
90+
- match: { error.root_cause.0.type: "resource_not_found_exception" }
91+
- match: { error.root_cause.0.reason: "No searchable snapshots indices found" }
92+
93+
- do:
94+
catch: missing
95+
searchable_snapshots.clear_cache:
96+
index: _all
97+
98+
- match: { error.root_cause.0.type: "resource_not_found_exception" }
99+
- match: { error.root_cause.0.reason: "No searchable snapshots indices found" }
100+
101+
- do:
102+
catch: missing
103+
searchable_snapshots.clear_cache:
104+
index: "unknown"
105+
106+
- do:
107+
indices.create:
108+
index: non_searchable_snapshot_index
109+
110+
- do:
111+
catch: missing
112+
searchable_snapshots.clear_cache:
113+
index: non_*
114+
115+
- match: { error.root_cause.0.type: "resource_not_found_exception" }
116+
- match: { error.root_cause.0.reason: "No searchable snapshots indices found" }
117+
118+
- do:
119+
snapshot.restore:
120+
repository: repository-searchable-snapshots
121+
snapshot: snapshot
122+
wait_for_completion: true
123+
124+
- match: { snapshot.snapshot: snapshot }
125+
- match: { snapshot.shards.failed: 0 }
126+
- match: { snapshot.shards.successful: 1 }
127+
128+
- do:
129+
search:
130+
rest_total_hits_as_int: true
131+
index: docs
132+
body:
133+
query:
134+
match:
135+
field: "doc"
136+
137+
- match: { hits.total: 2 }
138+
139+
- do:
140+
searchable_snapshots.clear_cache:
141+
index: "docs"
142+
143+
- match: { _shards.total: 1 }
144+
- match: { _shards.failed: 0 }
145+

x-pack/plugin/searchable-snapshots/qa/rest/src/test/resources/rest-api-spec/test/stats.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ setup:
3333
settings:
3434
location: "repository-fs"
3535

36+
# Remove the snapshot if a previous test failed to delete it.
37+
# Useful for third party tests that runs the test against a real external service.
38+
- do:
39+
snapshot.delete:
40+
repository: repository-fs
41+
snapshot: snapshot
42+
ignore: 404
43+
3644
- do:
3745
snapshot.create:
3846
repository: repository-fs
@@ -133,6 +141,9 @@ teardown:
133141
searchable_snapshots.stats:
134142
index: "d*"
135143

144+
- match: { _shards.total: 1 }
145+
- match: { _shards.failed: 0 }
146+
136147
- length: { indices: 1 }
137148
- length: { indices.docs.shards: 1 }
138149
- length: { indices.docs.shards.0: 1 }

x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshots.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,12 @@
3838
import org.elasticsearch.script.ScriptService;
3939
import org.elasticsearch.threadpool.ThreadPool;
4040
import org.elasticsearch.watcher.ResourceWatcherService;
41+
import org.elasticsearch.xpack.searchablesnapshots.action.ClearSearchableSnapshotsCacheAction;
4142
import org.elasticsearch.xpack.searchablesnapshots.action.SearchableSnapshotsStatsAction;
43+
import org.elasticsearch.xpack.searchablesnapshots.action.TransportClearSearchableSnapshotsCacheAction;
4244
import org.elasticsearch.xpack.searchablesnapshots.action.TransportSearchableSnapshotsStatsAction;
4345
import org.elasticsearch.xpack.searchablesnapshots.cache.CacheService;
46+
import org.elasticsearch.xpack.searchablesnapshots.rest.RestClearSearchableSnapshotsCacheAction;
4447
import org.elasticsearch.xpack.searchablesnapshots.rest.RestSearchableSnapshotsStatsAction;
4548

4649
import java.util.Collection;
@@ -126,14 +129,20 @@ public Map<String, Repository.Factory> getRepositories(Environment env, NamedXCo
126129

127130
@Override
128131
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
129-
return List.of(new ActionHandler<>(SearchableSnapshotsStatsAction.INSTANCE, TransportSearchableSnapshotsStatsAction.class));
132+
return List.of(
133+
new ActionHandler<>(SearchableSnapshotsStatsAction.INSTANCE, TransportSearchableSnapshotsStatsAction.class),
134+
new ActionHandler<>(ClearSearchableSnapshotsCacheAction.INSTANCE, TransportClearSearchableSnapshotsCacheAction.class)
135+
);
130136
}
131137

132138
public List<RestHandler> getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings,
133139
IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
134140
IndexNameExpressionResolver indexNameExpressionResolver,
135141
Supplier<DiscoveryNodes> nodesInCluster) {
136-
return List.of(new RestSearchableSnapshotsStatsAction());
142+
return List.of(
143+
new RestSearchableSnapshotsStatsAction(),
144+
new RestClearSearchableSnapshotsCacheAction()
145+
);
137146
}
138147
}
139148

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.searchablesnapshots.action;
7+
8+
import org.apache.lucene.store.Directory;
9+
import org.apache.lucene.store.FilterDirectory;
10+
import org.elasticsearch.ResourceNotFoundException;
11+
import org.elasticsearch.action.support.ActionFilters;
12+
import org.elasticsearch.action.support.broadcast.BroadcastRequest;
13+
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
14+
import org.elasticsearch.action.support.broadcast.node.TransportBroadcastByNodeAction;
15+
import org.elasticsearch.cluster.ClusterState;
16+
import org.elasticsearch.cluster.block.ClusterBlockException;
17+
import org.elasticsearch.cluster.block.ClusterBlockLevel;
18+
import org.elasticsearch.cluster.metadata.IndexMetaData;
19+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
20+
import org.elasticsearch.cluster.routing.ShardRouting;
21+
import org.elasticsearch.cluster.routing.ShardsIterator;
22+
import org.elasticsearch.cluster.service.ClusterService;
23+
import org.elasticsearch.common.Nullable;
24+
import org.elasticsearch.common.io.stream.Writeable;
25+
import org.elasticsearch.common.settings.Settings;
26+
import org.elasticsearch.index.shard.IndexShard;
27+
import org.elasticsearch.indices.IndicesService;
28+
import org.elasticsearch.transport.TransportService;
29+
import org.elasticsearch.xpack.searchablesnapshots.InMemoryNoOpCommitDirectory;
30+
import org.elasticsearch.xpack.searchablesnapshots.cache.CacheDirectory;
31+
32+
import java.io.IOException;
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
36+
import static org.elasticsearch.index.IndexModule.INDEX_STORE_TYPE_SETTING;
37+
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotRepository.SNAPSHOT_CACHE_ENABLED_SETTING;
38+
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotRepository.SNAPSHOT_DIRECTORY_FACTORY_KEY;
39+
40+
public abstract class AbstractTransportSearchableSnapshotsAction
41+
<Request extends BroadcastRequest<Request>, Response extends BroadcastResponse, ShardOperationResult extends Writeable>
42+
extends TransportBroadcastByNodeAction<Request, Response, ShardOperationResult> {
43+
44+
private final IndicesService indicesService;
45+
46+
AbstractTransportSearchableSnapshotsAction(String actionName, ClusterService clusterService, TransportService transportService,
47+
ActionFilters actionFilters, IndexNameExpressionResolver resolver,
48+
Writeable.Reader<Request> request, String executor, IndicesService indicesService) {
49+
super(actionName, clusterService, transportService, actionFilters, resolver, request, executor);
50+
this.indicesService = indicesService;
51+
}
52+
53+
AbstractTransportSearchableSnapshotsAction(String actionName, ClusterService clusterService, TransportService transportService,
54+
ActionFilters actionFilters, IndexNameExpressionResolver resolver,
55+
Writeable.Reader<Request> request, String executor, IndicesService indicesService,
56+
boolean canTripCircuitBreaker) {
57+
super(actionName, clusterService, transportService, actionFilters, resolver, request, executor, canTripCircuitBreaker);
58+
this.indicesService = indicesService;
59+
}
60+
61+
@Override
62+
protected ClusterBlockException checkGlobalBlock(ClusterState state, Request request) {
63+
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
64+
}
65+
66+
@Override
67+
protected ClusterBlockException checkRequestBlock(ClusterState state, Request request, String[] indices) {
68+
return state.blocks().indicesBlockedException(ClusterBlockLevel.METADATA_READ, indices);
69+
}
70+
71+
@Override
72+
protected ShardsIterator shards(ClusterState state, Request request, String[] concreteIndices) {
73+
final List<String> searchableSnapshotIndices = new ArrayList<>();
74+
for (String concreteIndex : concreteIndices) {
75+
IndexMetaData indexMetaData = state.metaData().index(concreteIndex);
76+
if (indexMetaData != null) {
77+
Settings indexSettings = indexMetaData.getSettings();
78+
if (INDEX_STORE_TYPE_SETTING.get(indexSettings).equals(SNAPSHOT_DIRECTORY_FACTORY_KEY)) {
79+
if (SNAPSHOT_CACHE_ENABLED_SETTING.get(indexSettings)) {
80+
searchableSnapshotIndices.add(concreteIndex);
81+
}
82+
}
83+
}
84+
}
85+
if (searchableSnapshotIndices.isEmpty()) {
86+
throw new ResourceNotFoundException("No searchable snapshots indices found");
87+
}
88+
return state.routingTable().allShards(searchableSnapshotIndices.toArray(new String[0]));
89+
}
90+
91+
@Override
92+
protected ShardOperationResult shardOperation(Request request, ShardRouting shardRouting) throws IOException {
93+
final IndexShard indexShard = indicesService.indexServiceSafe(shardRouting.index()).getShard(shardRouting.id());
94+
final CacheDirectory cacheDirectory = unwrapCacheDirectory(indexShard.store().directory());
95+
assert cacheDirectory != null;
96+
assert cacheDirectory.getShardId().equals(shardRouting.shardId());
97+
return executeShardOperation(request, shardRouting, cacheDirectory);
98+
}
99+
100+
protected abstract ShardOperationResult executeShardOperation(Request request, ShardRouting shardRouting,
101+
CacheDirectory cacheDirectory) throws IOException;
102+
103+
@Nullable
104+
private static CacheDirectory unwrapCacheDirectory(Directory dir) {
105+
while (dir != null) {
106+
if (dir instanceof CacheDirectory) {
107+
return (CacheDirectory) dir;
108+
} else if (dir instanceof InMemoryNoOpCommitDirectory) {
109+
dir = ((InMemoryNoOpCommitDirectory) dir).getRealDirectory();
110+
} else if (dir instanceof FilterDirectory) {
111+
dir = ((FilterDirectory) dir).getDelegate();
112+
} else {
113+
dir = null;
114+
}
115+
}
116+
return null;
117+
}
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.searchablesnapshots.action;
7+
8+
import org.elasticsearch.action.ActionType;
9+
10+
public class ClearSearchableSnapshotsCacheAction extends ActionType<ClearSearchableSnapshotsCacheResponse> {
11+
12+
public static final ClearSearchableSnapshotsCacheAction INSTANCE = new ClearSearchableSnapshotsCacheAction();
13+
static final String NAME = "cluster:admin/xpack/searchable_snapshots/cache/clear";
14+
15+
private ClearSearchableSnapshotsCacheAction() {
16+
super(NAME, ClearSearchableSnapshotsCacheResponse::new);
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.searchablesnapshots.action;
7+
8+
import org.elasticsearch.action.support.IndicesOptions;
9+
import org.elasticsearch.action.support.broadcast.BroadcastRequest;
10+
import org.elasticsearch.common.io.stream.StreamInput;
11+
12+
import java.io.IOException;
13+
14+
public class ClearSearchableSnapshotsCacheRequest extends BroadcastRequest<ClearSearchableSnapshotsCacheRequest> {
15+
16+
public ClearSearchableSnapshotsCacheRequest(StreamInput in) throws IOException {
17+
super(in);
18+
}
19+
20+
public ClearSearchableSnapshotsCacheRequest(String... indices) {
21+
super(indices);
22+
}
23+
24+
protected ClearSearchableSnapshotsCacheRequest(String[] indices, IndicesOptions indicesOptions) {
25+
super(indices, indicesOptions);
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.searchablesnapshots.action;
7+
8+
import org.elasticsearch.action.support.DefaultShardOperationFailedException;
9+
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
10+
import org.elasticsearch.common.io.stream.StreamInput;
11+
12+
import java.io.IOException;
13+
import java.util.List;
14+
15+
public class ClearSearchableSnapshotsCacheResponse extends BroadcastResponse {
16+
17+
ClearSearchableSnapshotsCacheResponse(StreamInput in) throws IOException {
18+
super(in);
19+
}
20+
21+
ClearSearchableSnapshotsCacheResponse(int totalShards, int successfulShards, int failedShards,
22+
List<DefaultShardOperationFailedException> shardFailures) {
23+
super(totalShards, successfulShards, failedShards, shardFailures);
24+
}
25+
}

0 commit comments

Comments
 (0)