Skip to content

Commit 808b70d

Browse files
authored
Script: Restore the scripting general cache (#79453)
Deprecate the script context cache in favor of the general cache. Users should use the following settings: `script.max_compilations_rate` to set the max compilation rate for user scripts such as filter scripts. Certain script contexts that submit scripts outside of the control of the user are exempted from this rate limit. Examples include runtime fields, ingest and watcher. `script.cache.max_size` to set the max size of the cache. `script.cache.expire` to set the expiration time for entries in the cache. Whats deprecated? `script.max_compilations_rate: use-context`. This special setting value was used to turn on the script context-specific caches. `script.context.$CONTEXT.cache_max_size`, use `script.cache.max_size` instead. `script.context.$CONTEXT.cache_expire`, use `script.cache.expire` instead. `script.context.$CONTEXT.max_compilations_rate`, use `script.max_compilations_rate` instead. The default cache size was increased from `100` to `3000`, which was approximately the max cache size when using context-specific caches. The default compilation rate limit was increased from `75/5m` to `150/5m` to account for increasing uses of scripts. System script contexts can now opt-out of compilation rate limiting using a flag rather than a sentinel rate limit value. 7.16: Script: Deprecate script context cache #79508 Refs: #62899 7.16: Script: Opt-out system contexts from script compilation rate limit #79459 Refs: #62899
1 parent 78fcd0e commit 808b70d

File tree

31 files changed

+989
-116
lines changed

31 files changed

+989
-116
lines changed

docs/reference/modules/indices/circuit_breaker.asciidoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,11 @@ within a period of time.
126126
See the "prefer-parameters" section of the <<modules-scripting-using,scripting>>
127127
documentation for more information.
128128

129-
`script.context.$CONTEXT.max_compilations_rate`::
129+
`script.max_compilations_rate`::
130130
(<<dynamic-cluster-setting,Dynamic>>)
131131
Limit for the number of unique dynamic scripts within a certain interval
132-
that are allowed to be compiled for a given context. Defaults to `75/5m`,
133-
meaning 75 every 5 minutes.
132+
that are allowed to be compiled. Defaults to `150/5m`,
133+
meaning 150 every 5 minutes.
134134

135135
[[regex-circuit-breaker]]
136136
[discrete]

docs/reference/scripting/using.asciidoc

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,8 @@ the `multiplier` parameter without {es} recompiling the script.
120120
}
121121
----
122122

123-
For most contexts, you can compile up to 75 scripts per 5 minutes by default.
124-
For ingest contexts, the default script compilation rate is unlimited. You
125-
can change these settings dynamically by setting
126-
`script.context.$CONTEXT.max_compilations_rate`. For example, the following
127-
setting limits script compilation to 100 scripts every 10 minutes for the
128-
{painless}/painless-field-context.html[field context]:
123+
You can compile up to 150 scripts per 5 minutes by default.
124+
For ingest contexts, the default script compilation rate is unlimited.
129125

130126
[source,js]
131127
----
@@ -406,8 +402,8 @@ small.
406402

407403
All scripts are cached by default so that they only need to be recompiled
408404
when updates occur. By default, scripts do not have a time-based expiration.
409-
You can change this behavior by using the `script.context.$CONTEXT.cache_expire` setting.
410-
Use the `script.context.$CONTEXT.cache_max_size` setting to configure the size of the cache.
405+
You can change this behavior by using the `script.cache.expire` setting.
406+
Use the `script.cache.max_size` setting to configure the size of the cache.
411407

412408
NOTE: The size of scripts is limited to 65,535 bytes. Set the value of `script.max_size_in_bytes` to increase that soft limit. If your scripts are
413409
really large, then consider using a

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.monitor.os.OsStats;
2828
import org.elasticsearch.monitor.process.ProcessStats;
2929
import org.elasticsearch.node.AdaptiveSelectionStats;
30+
import org.elasticsearch.script.ScriptCacheStats;
3031
import org.elasticsearch.script.ScriptStats;
3132
import org.elasticsearch.threadpool.ThreadPoolStats;
3233
import org.elasticsearch.transport.TransportStats;
@@ -71,6 +72,9 @@ public class NodeStats extends BaseNodeResponse implements ToXContentFragment {
7172
@Nullable
7273
private ScriptStats scriptStats;
7374

75+
@Nullable
76+
private ScriptCacheStats scriptCacheStats;
77+
7478
@Nullable
7579
private DiscoveryStats discoveryStats;
7680

@@ -98,6 +102,7 @@ public NodeStats(StreamInput in) throws IOException {
98102
http = in.readOptionalWriteable(HttpStats::new);
99103
breaker = in.readOptionalWriteable(AllCircuitBreakerStats::new);
100104
scriptStats = in.readOptionalWriteable(ScriptStats::new);
105+
scriptCacheStats = scriptStats != null ? scriptStats.toScriptCacheStats() : null;
101106
discoveryStats = in.readOptionalWriteable(DiscoveryStats::new);
102107
ingestStats = in.readOptionalWriteable(IngestStats::new);
103108
adaptiveSelectionStats = in.readOptionalWriteable(AdaptiveSelectionStats::new);
@@ -112,6 +117,7 @@ public NodeStats(DiscoveryNode node, long timestamp, @Nullable NodeIndicesStats
112117
@Nullable DiscoveryStats discoveryStats,
113118
@Nullable IngestStats ingestStats,
114119
@Nullable AdaptiveSelectionStats adaptiveSelectionStats,
120+
@Nullable ScriptCacheStats scriptCacheStats,
115121
@Nullable IndexingPressureStats indexingPressureStats) {
116122
super(node);
117123
this.timestamp = timestamp;
@@ -128,6 +134,7 @@ public NodeStats(DiscoveryNode node, long timestamp, @Nullable NodeIndicesStats
128134
this.discoveryStats = discoveryStats;
129135
this.ingestStats = ingestStats;
130136
this.adaptiveSelectionStats = adaptiveSelectionStats;
137+
this.scriptCacheStats = scriptCacheStats;
131138
this.indexingPressureStats = indexingPressureStats;
132139
}
133140

@@ -223,6 +230,11 @@ public AdaptiveSelectionStats getAdaptiveSelectionStats() {
223230
return adaptiveSelectionStats;
224231
}
225232

233+
@Nullable
234+
public ScriptCacheStats getScriptCacheStats() {
235+
return scriptCacheStats;
236+
}
237+
226238
@Nullable
227239
public IndexingPressureStats getIndexingPressureStats() {
228240
return indexingPressureStats;
@@ -314,6 +326,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
314326
if (getAdaptiveSelectionStats() != null) {
315327
getAdaptiveSelectionStats().toXContent(builder, params);
316328
}
329+
if (getScriptCacheStats() != null) {
330+
getScriptCacheStats().toXContent(builder, params);
331+
}
317332
if (getIndexingPressureStats() != null) {
318333
getIndexingPressureStats().toXContent(builder, params);
319334
}

server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,9 @@ public void apply(Settings value, Settings current, Settings previous) {
372372
ScriptService.SCRIPT_CACHE_SIZE_SETTING,
373373
ScriptService.SCRIPT_CACHE_EXPIRE_SETTING,
374374
ScriptService.SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING,
375+
ScriptService.SCRIPT_GENERAL_CACHE_EXPIRE_SETTING,
376+
ScriptService.SCRIPT_GENERAL_CACHE_SIZE_SETTING,
377+
ScriptService.SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING,
375378
ScriptService.SCRIPT_MAX_COMPILATIONS_RATE_SETTING,
376379
ScriptService.SCRIPT_MAX_SIZE_IN_BYTES,
377380
ScriptService.TYPES_ALLOWED_SETTING,

server/src/main/java/org/elasticsearch/node/NodeService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public NodeStats stats(CommonStatsFlags indices, boolean os, boolean process, bo
118118
discoveryStats ? coordinator.stats() : null,
119119
ingest ? ingestService.stats() : null,
120120
adaptiveSelection ? responseCollectorService.getAdaptiveStats(searchTransportService.getPendingSearchRequests()) : null,
121+
scriptCache ? scriptService.cacheStats() : null,
121122
indexingPressure ? this.indexingPressure.stats() : null);
122123
}
123124

server/src/main/java/org/elasticsearch/script/AbstractFieldScript.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ static <F> ScriptContext<F> newContext(String name, Class<F> factoryClass) {
5252
* source of runaway script compilations. We think folks will
5353
* mostly reuse scripts though.
5454
*/
55-
ScriptCache.UNLIMITED_COMPILATION_RATE.asTuple(),
55+
false,
5656
/*
5757
* Disable runtime fields scripts from being allowed
5858
* to be stored as part of the script meta data.

server/src/main/java/org/elasticsearch/script/IngestConditionalScript.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public abstract class IngestConditionalScript {
2121

2222
/** The context used to compile {@link IngestConditionalScript} factories. */
2323
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("processor_conditional", Factory.class,
24-
200, TimeValue.timeValueMillis(0), ScriptCache.UNLIMITED_COMPILATION_RATE.asTuple(), true);
24+
200, TimeValue.timeValueMillis(0), false, true);
2525

2626
/** The generic runtime parameters for the script. */
2727
private final Map<String, Object> params;

server/src/main/java/org/elasticsearch/script/IngestScript.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public abstract class IngestScript {
2222

2323
/** The context used to compile {@link IngestScript} factories. */
2424
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("ingest", Factory.class,
25-
200, TimeValue.timeValueMillis(0), ScriptCache.UNLIMITED_COMPILATION_RATE.asTuple(), true);
25+
200, TimeValue.timeValueMillis(0), false, true);
2626

2727
/** The generic runtime parameters for the script. */
2828
private final Map<String, Object> params;

server/src/main/java/org/elasticsearch/script/ScriptCache.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,7 @@ public class ScriptCache {
4444
private final double compilesAllowedPerNano;
4545
private final String contextRateSetting;
4646

47-
ScriptCache(
48-
int cacheMaxSize,
49-
TimeValue cacheExpire,
50-
CompilationRate maxCompilationRate,
51-
String contextRateSetting
52-
) {
47+
ScriptCache(int cacheMaxSize, TimeValue cacheExpire, CompilationRate maxCompilationRate, String contextRateSetting) {
5348
this.cacheSize = cacheMaxSize;
5449
this.cacheExpire = cacheExpire;
5550
this.contextRateSetting = contextRateSetting;
@@ -94,8 +89,10 @@ <FactoryType> FactoryType compile(
9489
logger.trace("context [{}]: compiling script, type: [{}], lang: [{}], options: [{}]", context.name, type,
9590
lang, options);
9691
}
97-
// Check whether too many compilations have happened
98-
checkCompilationLimit();
92+
if (context.compilationRateLimited) {
93+
// Check whether too many compilations have happened
94+
checkCompilationLimit();
95+
}
9996
Object compiledScript = scriptEngine.compile(id, idOrCode, context, options);
10097
// Since the cache key is the script content itself we don't need to
10198
// invalidate/check the cache if an indexed script changes.
@@ -121,6 +118,10 @@ static <T extends Throwable> void rethrow(Throwable t) throws T {
121118
throw (T) t;
122119
}
123120

121+
public ScriptStats stats() {
122+
return scriptMetrics.stats();
123+
}
124+
124125
public ScriptContextStats stats(String context) {
125126
return scriptMetrics.stats(context);
126127
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.script;
10+
11+
import org.elasticsearch.common.io.stream.StreamInput;
12+
import org.elasticsearch.common.io.stream.StreamOutput;
13+
import org.elasticsearch.common.io.stream.Writeable;
14+
import org.elasticsearch.xcontent.ToXContentFragment;
15+
import org.elasticsearch.xcontent.XContentBuilder;
16+
17+
import java.io.IOException;
18+
import java.util.Collections;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.Objects;
22+
import java.util.stream.Collectors;
23+
24+
// This class is deprecated in favor of ScriptStats and ScriptContextStats
25+
public class ScriptCacheStats implements Writeable, ToXContentFragment {
26+
private final Map<String, ScriptStats> context;
27+
private final ScriptStats general;
28+
29+
public ScriptCacheStats(Map<String, ScriptStats> context) {
30+
this.context = Collections.unmodifiableMap(context);
31+
this.general = null;
32+
}
33+
34+
public ScriptCacheStats(ScriptStats general) {
35+
this.general = Objects.requireNonNull(general);
36+
this.context = null;
37+
}
38+
39+
public ScriptCacheStats(StreamInput in) throws IOException {
40+
boolean isContext = in.readBoolean();
41+
if (isContext == false) {
42+
general = new ScriptStats(in);
43+
context = null;
44+
return;
45+
}
46+
47+
general = null;
48+
int size = in.readInt();
49+
Map<String,ScriptStats> context = new HashMap<>(size);
50+
for (int i=0; i < size; i++) {
51+
String name = in.readString();
52+
context.put(name, new ScriptStats(in));
53+
}
54+
this.context = Collections.unmodifiableMap(context);
55+
}
56+
57+
@Override
58+
public void writeTo(StreamOutput out) throws IOException {
59+
if (general != null) {
60+
out.writeBoolean(false);
61+
general.writeTo(out);
62+
return;
63+
}
64+
65+
out.writeBoolean(true);
66+
out.writeInt(context.size());
67+
for (String name: context.keySet().stream().sorted().collect(Collectors.toList())) {
68+
out.writeString(name);
69+
context.get(name).writeTo(out);
70+
}
71+
}
72+
73+
@Override
74+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
75+
builder.startObject(Fields.SCRIPT_CACHE_STATS);
76+
builder.startObject(Fields.SUM);
77+
if (general != null) {
78+
builder.field(ScriptStats.Fields.COMPILATIONS, general.getCompilations());
79+
builder.field(ScriptStats.Fields.CACHE_EVICTIONS, general.getCacheEvictions());
80+
builder.field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, general.getCompilationLimitTriggered());
81+
builder.endObject().endObject();
82+
return builder;
83+
}
84+
85+
ScriptStats sum = sum();
86+
builder.field(ScriptStats.Fields.COMPILATIONS, sum.getCompilations());
87+
builder.field(ScriptStats.Fields.CACHE_EVICTIONS, sum.getCacheEvictions());
88+
builder.field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, sum.getCompilationLimitTriggered());
89+
builder.endObject();
90+
91+
builder.startArray(Fields.CONTEXTS);
92+
for (String name: context.keySet().stream().sorted().collect(Collectors.toList())) {
93+
ScriptStats stats = context.get(name);
94+
builder.startObject();
95+
builder.field(Fields.CONTEXT, name);
96+
builder.field(ScriptStats.Fields.COMPILATIONS, stats.getCompilations());
97+
builder.field(ScriptStats.Fields.CACHE_EVICTIONS, stats.getCacheEvictions());
98+
builder.field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, stats.getCompilationLimitTriggered());
99+
builder.endObject();
100+
}
101+
builder.endArray();
102+
builder.endObject();
103+
104+
return builder;
105+
}
106+
107+
/**
108+
* Get the context specific stats, null if using general cache
109+
*/
110+
public Map<String, ScriptStats> getContextStats() {
111+
return context;
112+
}
113+
114+
/**
115+
* Get the general stats, null if using context cache
116+
*/
117+
public ScriptStats getGeneralStats() {
118+
return general;
119+
}
120+
121+
/**
122+
* The sum of all script stats, either the general stats or the sum of all stats of the context stats.
123+
*/
124+
public ScriptStats sum() {
125+
if (general != null) {
126+
return general;
127+
}
128+
long compilations = 0;
129+
long cacheEvictions = 0;
130+
long compilationLimitTriggered = 0;
131+
for (ScriptStats stat: context.values()) {
132+
compilations += stat.getCompilations();
133+
cacheEvictions += stat.getCacheEvictions();
134+
compilationLimitTriggered += stat.getCompilationLimitTriggered();
135+
}
136+
return new ScriptStats(
137+
compilations,
138+
cacheEvictions,
139+
compilationLimitTriggered
140+
);
141+
}
142+
143+
static final class Fields {
144+
static final String SCRIPT_CACHE_STATS = "script_cache";
145+
static final String CONTEXT = "context";
146+
static final String SUM = "sum";
147+
static final String CONTEXTS = "contexts";
148+
}
149+
}

0 commit comments

Comments
 (0)