Skip to content

Commit 418ce50

Browse files
committed
Query Cache: Support shard level query response caching
The query cache allow to cache the (binary serialized) response of the shard level query phase execution based on the actual request as the key. The cache is fully coherent with the semantics of NRT, with a refresh (that actually ended up refreshing) causing previous cached entries on the relevant shard to be invalidated and eventually evicted. This change enables query caching as an opt in index level setting, called `index.cache.query.enable` and defaults to `false`. The setting can be changed dynamically on an index. The cache is only enabled for search requests with search_type count. The indices query cache is a node level query cache. The `indices.cache.query.size` controls what is the size (bytes wise) the cache will take, and defaults to `1%` of the heap. Note, this cache is very effective with small values in it already. There is also the advanced option to set `indices.cache.query.expire` that allow to control after a certain time of inaccessibility the cache will be evicted. Note, the request takes the search "body" as is (bytes), and uses it as the key. This means same JSON but with different key order will constitute different cache entries. This change includes basic stats (shard level, index/indices level, and node level) for the query cache, showing how much is used and eviction rates. While this is a good first step, and the goal is to get it in, there are a few things that would be great additions to this work, but they can be done as additional pull requests: - More stats, specifically cache hit and cache miss, per shard. - Request level flag, defaults to "not set" (inheriting what the setting is). - Allowing to change the cache size using the cluster update settings API - Consider enabling the cache to query phase also when asking hits are involved, note, this will only include the "top docs", not the actual hits. - See if there is a performant manner to solve the "out of order" of keys in the JSON case. - Maybe introduce a filter element, that is outside of the request, that is checked, and if it matches all docs in a shard, will not be used as part of the key. This will help with time based indices and moving windows for shards that fall "inside" the window to be more effective caching wise. - Add a more infra level support in search context that allows for any element to mark the search as non deterministic (on top of the support for "now"), and use it to not cache search responses. closes #7161
1 parent 35e67c8 commit 418ce50

39 files changed

+1122
-217
lines changed

src/main/java/org/elasticsearch/action/admin/indices/cache/clear/ClearIndicesCacheRequest.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.action.admin.indices.cache.clear;
2121

22+
import org.elasticsearch.Version;
2223
import org.elasticsearch.action.support.broadcast.BroadcastOperationRequest;
2324
import org.elasticsearch.common.io.stream.StreamInput;
2425
import org.elasticsearch.common.io.stream.StreamOutput;
@@ -34,6 +35,7 @@ public class ClearIndicesCacheRequest extends BroadcastOperationRequest<ClearInd
3435
private boolean fieldDataCache = false;
3536
private boolean idCache = false;
3637
private boolean recycler = false;
38+
private boolean queryCache = false;
3739
private String[] fields = null;
3840
private String[] filterKeys = null;
3941

@@ -54,6 +56,15 @@ public ClearIndicesCacheRequest filterCache(boolean filterCache) {
5456
return this;
5557
}
5658

59+
public boolean queryCache() {
60+
return this.queryCache;
61+
}
62+
63+
public ClearIndicesCacheRequest queryCache(boolean queryCache) {
64+
this.queryCache = queryCache;
65+
return this;
66+
}
67+
5768
public boolean fieldDataCache() {
5869
return this.fieldDataCache;
5970
}
@@ -107,6 +118,9 @@ public void readFrom(StreamInput in) throws IOException {
107118
recycler = in.readBoolean();
108119
fields = in.readStringArray();
109120
filterKeys = in.readStringArray();
121+
if (in.getVersion().onOrAfter(Version.V_1_4_0)) {
122+
queryCache = in.readBoolean();
123+
}
110124
}
111125

112126
public void writeTo(StreamOutput out) throws IOException {
@@ -117,7 +131,8 @@ public void writeTo(StreamOutput out) throws IOException {
117131
out.writeBoolean(recycler);
118132
out.writeStringArrayNullable(fields);
119133
out.writeStringArrayNullable(filterKeys);
134+
if (out.getVersion().onOrAfter(Version.V_1_4_0)) {
135+
out.writeBoolean(queryCache);
136+
}
120137
}
121-
122-
123138
}

src/main/java/org/elasticsearch/action/admin/indices/cache/clear/ClearIndicesCacheRequestBuilder.java

+5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public ClearIndicesCacheRequestBuilder setFilterCache(boolean filterCache) {
3838
return this;
3939
}
4040

41+
public ClearIndicesCacheRequestBuilder setQueryCache(boolean queryCache) {
42+
request.queryCache(queryCache);
43+
return this;
44+
}
45+
4146
public ClearIndicesCacheRequestBuilder setFieldDataCache(boolean fieldDataCache) {
4247
request.fieldDataCache(fieldDataCache);
4348
return this;

src/main/java/org/elasticsearch/action/admin/indices/cache/clear/ShardClearIndicesCacheRequest.java

+13
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.action.admin.indices.cache.clear;
2121

22+
import org.elasticsearch.Version;
2223
import org.elasticsearch.action.support.broadcast.BroadcastShardOperationRequest;
2324
import org.elasticsearch.common.io.stream.StreamInput;
2425
import org.elasticsearch.common.io.stream.StreamOutput;
@@ -34,6 +35,7 @@ class ShardClearIndicesCacheRequest extends BroadcastShardOperationRequest {
3435
private boolean fieldDataCache = false;
3536
private boolean idCache = false;
3637
private boolean recycler;
38+
private boolean queryCache = false;
3739

3840
private String[] fields = null;
3941
private String[] filterKeys = null;
@@ -49,12 +51,17 @@ public ShardClearIndicesCacheRequest(String index, int shardId, ClearIndicesCach
4951
fields = request.fields();
5052
filterKeys = request.filterKeys();
5153
recycler = request.recycler();
54+
queryCache = request.queryCache();
5255
}
5356

5457
public boolean filterCache() {
5558
return filterCache;
5659
}
5760

61+
public boolean queryCache() {
62+
return queryCache;
63+
}
64+
5865
public boolean fieldDataCache() {
5966
return this.fieldDataCache;
6067
}
@@ -89,6 +96,9 @@ public void readFrom(StreamInput in) throws IOException {
8996
recycler = in.readBoolean();
9097
fields = in.readStringArray();
9198
filterKeys = in.readStringArray();
99+
if (in.getVersion().onOrAfter(Version.V_1_4_0)) {
100+
queryCache = in.readBoolean();
101+
}
92102
}
93103

94104
@Override
@@ -100,5 +110,8 @@ public void writeTo(StreamOutput out) throws IOException {
100110
out.writeBoolean(recycler);
101111
out.writeStringArrayNullable(fields);
102112
out.writeStringArrayNullable(filterKeys);
113+
if (out.getVersion().onOrAfter(Version.V_1_4_0)) {
114+
out.writeBoolean(queryCache);
115+
}
103116
}
104117
}

src/main/java/org/elasticsearch/action/admin/indices/cache/clear/TransportClearIndicesCacheAction.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@
3636
import org.elasticsearch.common.settings.Settings;
3737
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
3838
import org.elasticsearch.index.service.IndexService;
39+
import org.elasticsearch.index.shard.service.IndexShard;
3940
import org.elasticsearch.indices.IndicesService;
4041
import org.elasticsearch.indices.cache.filter.terms.IndicesTermsFilterCache;
42+
import org.elasticsearch.indices.cache.query.IndicesQueryCache;
4143
import org.elasticsearch.threadpool.ThreadPool;
4244
import org.elasticsearch.transport.TransportService;
4345

@@ -53,16 +55,16 @@ public class TransportClearIndicesCacheAction extends TransportBroadcastOperatio
5355

5456
private final IndicesService indicesService;
5557
private final IndicesTermsFilterCache termsFilterCache;
56-
private final CacheRecycler cacheRecycler;
58+
private final IndicesQueryCache indicesQueryCache;
5759

5860
@Inject
5961
public TransportClearIndicesCacheAction(Settings settings, ThreadPool threadPool, ClusterService clusterService,
6062
TransportService transportService, IndicesService indicesService, IndicesTermsFilterCache termsFilterCache,
61-
CacheRecycler cacheRecycler, ActionFilters actionFilters) {
63+
IndicesQueryCache indicesQueryCache, ActionFilters actionFilters) {
6264
super(settings, ClearIndicesCacheAction.NAME, threadPool, clusterService, transportService, actionFilters);
6365
this.indicesService = indicesService;
6466
this.termsFilterCache = termsFilterCache;
65-
this.cacheRecycler = cacheRecycler;
67+
this.indicesQueryCache = indicesQueryCache;
6668
}
6769

6870
@Override
@@ -116,6 +118,7 @@ protected ShardClearIndicesCacheResponse newShardResponse() {
116118
protected ShardClearIndicesCacheResponse shardOperation(ShardClearIndicesCacheRequest request) throws ElasticsearchException {
117119
IndexService service = indicesService.indexService(request.index());
118120
if (service != null) {
121+
IndexShard shard = service.shard(request.shardId());
119122
// we always clear the query cache
120123
service.cache().queryParserCache().clear();
121124
boolean clearedAtLeastOne = false;
@@ -139,6 +142,10 @@ protected ShardClearIndicesCacheResponse shardOperation(ShardClearIndicesCacheRe
139142
}
140143
}
141144
}
145+
if (request.queryCache()) {
146+
clearedAtLeastOne = true;
147+
indicesQueryCache.clear(shard);
148+
}
142149
if (request.recycler()) {
143150
logger.debug("Clear CacheRecycler on index [{}]", service.index());
144151
clearedAtLeastOne = true;
@@ -158,6 +165,7 @@ protected ShardClearIndicesCacheResponse shardOperation(ShardClearIndicesCacheRe
158165
service.cache().clear("api");
159166
service.fieldData().clear();
160167
termsFilterCache.clear("api");
168+
indicesQueryCache.clear(shard);
161169
}
162170
}
163171
}

src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStats.java

+32
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.common.xcontent.XContentBuilder;
2929
import org.elasticsearch.index.cache.filter.FilterCacheStats;
3030
import org.elasticsearch.index.cache.id.IdCacheStats;
31+
import org.elasticsearch.index.cache.query.QueryCacheStats;
3132
import org.elasticsearch.index.engine.SegmentsStats;
3233
import org.elasticsearch.index.fielddata.FieldDataStats;
3334
import org.elasticsearch.index.flush.FlushStats;
@@ -111,6 +112,9 @@ public CommonStats(CommonStatsFlags flags) {
111112
case Suggest:
112113
suggest = new SuggestStats();
113114
break;
115+
case QueryCache:
116+
queryCache = new QueryCacheStats();
117+
break;
114118
default:
115119
throw new IllegalStateException("Unknown Flag: " + flag);
116120
}
@@ -174,6 +178,9 @@ public CommonStats(IndexShard indexShard, CommonStatsFlags flags) {
174178
case Suggest:
175179
suggest = indexShard.suggestStats();
176180
break;
181+
case QueryCache:
182+
queryCache = indexShard.queryCache().stats();
183+
break;
177184
default:
178185
throw new IllegalStateException("Unknown Flag: " + flag);
179186
}
@@ -231,6 +238,9 @@ public CommonStats(IndexShard indexShard, CommonStatsFlags flags) {
231238
@Nullable
232239
public SuggestStats suggest;
233240

241+
@Nullable
242+
public QueryCacheStats queryCache;
243+
234244
public void add(CommonStats stats) {
235245
if (docs == null) {
236246
if (stats.getDocs() != null) {
@@ -370,6 +380,14 @@ public void add(CommonStats stats) {
370380
} else {
371381
suggest.add(stats.getSuggest());
372382
}
383+
if (queryCache == null) {
384+
if (stats.getQueryCache() != null) {
385+
queryCache = new QueryCacheStats();
386+
queryCache.add(stats.getQueryCache());
387+
}
388+
} else {
389+
queryCache.add(stats.getQueryCache());
390+
}
373391
}
374392

375393
@Nullable
@@ -457,6 +475,11 @@ public SuggestStats getSuggest() {
457475
return suggest;
458476
}
459477

478+
@Nullable
479+
public QueryCacheStats getQueryCache() {
480+
return queryCache;
481+
}
482+
460483
public static CommonStats readCommonStats(StreamInput in) throws IOException {
461484
CommonStats stats = new CommonStats();
462485
stats.readFrom(in);
@@ -514,6 +537,9 @@ public void readFrom(StreamInput in) throws IOException {
514537
if (in.getVersion().onOrAfter(Version.V_1_2_0)) {
515538
suggest = in.readOptionalStreamable(new SuggestStats());
516539
}
540+
if (in.getVersion().onOrAfter(Version.V_1_4_0)) {
541+
queryCache = in.readOptionalStreamable(new QueryCacheStats());
542+
}
517543
}
518544

519545
@Override
@@ -612,6 +638,9 @@ public void writeTo(StreamOutput out) throws IOException {
612638
if (out.getVersion().onOrAfter(Version.V_1_2_0)) {
613639
out.writeOptionalStreamable(suggest);
614640
}
641+
if (out.getVersion().onOrAfter(Version.V_1_4_0)) {
642+
out.writeOptionalStreamable(queryCache);
643+
}
615644
}
616645

617646
// note, requires a wrapping object
@@ -668,6 +697,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
668697
if (suggest != null) {
669698
suggest.toXContent(builder, params);
670699
}
700+
if (queryCache != null) {
701+
queryCache.toXContent(builder, params);
702+
}
671703
return builder;
672704
}
673705
}

src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStatsFlags.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ public static enum Flag {
224224
Completion("completion"),
225225
Segments("segments"),
226226
Translog("translog"),
227-
Suggest("suggest");
227+
Suggest("suggest"),
228+
QueryCache("query_cache");
228229

229230
private final String restName;
230231

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

+9
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,15 @@ public boolean suggest() {
256256
return flags.isSet(Flag.Suggest);
257257
}
258258

259+
public IndicesStatsRequest queryCache(boolean queryCache) {
260+
flags.set(Flag.QueryCache, queryCache);
261+
return this;
262+
}
263+
264+
public boolean queryCache() {
265+
return flags.isSet(Flag.QueryCache);
266+
}
267+
259268
@Override
260269
public void writeTo(StreamOutput out) throws IOException {
261270
super.writeTo(out);

src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsRequestBuilder.java

+5
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ public IndicesStatsRequestBuilder setSuggest(boolean suggest) {
163163
return this;
164164
}
165165

166+
public IndicesStatsRequestBuilder setQueryCache(boolean queryCache) {
167+
request.queryCache(queryCache);
168+
return this;
169+
}
170+
166171
@Override
167172
protected void doExecute(ActionListener<IndicesStatsResponse> listener) {
168173
client.stats(request, listener);

src/main/java/org/elasticsearch/action/admin/indices/stats/TransportIndicesStatsAction.java

+3
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ protected ShardStats shardOperation(IndexShardStatsRequest request) throws Elast
192192
if (request.request.suggest()) {
193193
flags.set(CommonStatsFlags.Flag.Suggest);
194194
}
195+
if (request.request.queryCache()) {
196+
flags.set(CommonStatsFlags.Flag.QueryCache);
197+
}
195198

196199
return new ShardStats(indexShard, flags);
197200
}

src/main/java/org/elasticsearch/action/search/type/TransportSearchCountAction.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.search.internal.InternalSearchResponse;
3636
import org.elasticsearch.search.internal.ShardSearchRequest;
3737
import org.elasticsearch.search.query.QuerySearchResult;
38+
import org.elasticsearch.search.query.QuerySearchResultProvider;
3839
import org.elasticsearch.threadpool.ThreadPool;
3940

4041
import static org.elasticsearch.action.search.type.TransportSearchHelper.buildScrollId;
@@ -55,7 +56,7 @@ protected void doExecute(SearchRequest searchRequest, ActionListener<SearchRespo
5556
new AsyncAction(searchRequest, listener).start();
5657
}
5758

58-
private class AsyncAction extends BaseAsyncAction<QuerySearchResult> {
59+
private class AsyncAction extends BaseAsyncAction<QuerySearchResultProvider> {
5960

6061
private AsyncAction(SearchRequest request, ActionListener<SearchResponse> listener) {
6162
super(request, listener);
@@ -67,7 +68,7 @@ protected String firstPhaseName() {
6768
}
6869

6970
@Override
70-
protected void sendExecuteFirstPhase(DiscoveryNode node, ShardSearchRequest request, SearchServiceListener<QuerySearchResult> listener) {
71+
protected void sendExecuteFirstPhase(DiscoveryNode node, ShardSearchRequest request, SearchServiceListener<QuerySearchResultProvider> listener) {
7172
searchService.sendExecuteQuery(node, request, listener);
7273
}
7374

src/main/java/org/elasticsearch/action/search/type/TransportSearchQueryThenFetchAction.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.elasticsearch.search.internal.InternalSearchResponse;
4141
import org.elasticsearch.search.internal.ShardSearchRequest;
4242
import org.elasticsearch.search.query.QuerySearchResult;
43+
import org.elasticsearch.search.query.QuerySearchResultProvider;
4344
import org.elasticsearch.threadpool.ThreadPool;
4445

4546
import java.util.concurrent.atomic.AtomicInteger;
@@ -60,7 +61,7 @@ protected void doExecute(SearchRequest searchRequest, ActionListener<SearchRespo
6061
new AsyncAction(searchRequest, listener).start();
6162
}
6263

63-
private class AsyncAction extends BaseAsyncAction<QuerySearchResult> {
64+
private class AsyncAction extends BaseAsyncAction<QuerySearchResultProvider> {
6465

6566
final AtomicArray<FetchSearchResult> fetchResults;
6667
final AtomicArray<IntArrayList> docIdsToLoad;
@@ -77,7 +78,7 @@ protected String firstPhaseName() {
7778
}
7879

7980
@Override
80-
protected void sendExecuteFirstPhase(DiscoveryNode node, ShardSearchRequest request, SearchServiceListener<QuerySearchResult> listener) {
81+
protected void sendExecuteFirstPhase(DiscoveryNode node, ShardSearchRequest request, SearchServiceListener<QuerySearchResultProvider> listener) {
8182
searchService.sendExecuteQuery(node, request, listener);
8283
}
8384

@@ -97,9 +98,9 @@ protected void moveToSecondPhase() throws Exception {
9798
);
9899
final AtomicInteger counter = new AtomicInteger(docIdsToLoad.asList().size());
99100
for (AtomicArray.Entry<IntArrayList> entry : docIdsToLoad.asList()) {
100-
QuerySearchResult queryResult = firstResults.get(entry.index);
101+
QuerySearchResultProvider queryResult = firstResults.get(entry.index);
101102
DiscoveryNode node = nodes.get(queryResult.shardTarget().nodeId());
102-
FetchSearchRequest fetchSearchRequest = createFetchRequest(queryResult, entry, lastEmittedDocPerShard);
103+
FetchSearchRequest fetchSearchRequest = createFetchRequest(queryResult.queryResult(), entry, lastEmittedDocPerShard);
103104
executeFetch(entry.index, queryResult.shardTarget(), counter, fetchSearchRequest, node);
104105
}
105106
}

0 commit comments

Comments
 (0)