Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Script: Deprecate script context cache #79505

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions docs/reference/migration/migrate_7_16.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,77 @@ avoid deprecation warnings and opt into the future behaviour, include the query
parameter `?return_200_for_cluster_health_timeout` in your request.
====

[[script-context-cache-removed]]
.The `script.max_compilations_rate` setting cannot be `use-context`.
[%collapsible]
====
*Details* +
The `script.max_compilations_rate` node setting cannot be `use-context`.
Previously, you could set this setting to `use-context` to have a
script cache, compilation rate limit settings, cache expiration and
cache size per cluster.

However, these settings were used to handle system scripts triggering
compilation rate limits and to compensate for an undersized general script
cache.
Now, system scripts are exempt from compilation rate limiting and
the general script cache size is dynamically with a higher default
size.

Do not use `use-context` as a value for this setting. Instead, remove
the setting to use the default or set it to a custom rate limit, such as
`200/10m`, which allows 200 compilations for every 10 minute period.

*Impact* +
Discontinue use of the removed setting value. Specifying the setting value
in `elasticsearch.yml` will result in an error on startup.
====

[[script-context-cache-max-compile-removed]]
.The `script.context.$CONTEXT.max_compilations_rate` setting has been removed.
[%collapsible]
====
*Details* +
Script context-specific compilation rate limit settings have been removed. Use
`script.max_compilations_rate` to set the rate limit for all user scripts.

The context-specific settings were used to handle system scripts triggering
compilation rate limits and to compensate for an undersized general script
cache.

Now, system scripts are exempt from compilation rate limiting and
the general script cache size is dynamically updatable with a higher default
size.

*Impact* +
Discontinue use of the removed setting. Specifying the setting in
`elasticsearch.yml` will result in an error on startup.
====

[[script-context-cache-size-removed]]
.The `script.context.$CONTEXT.cache_max_size` setting has been removed.
[%collapsible]
====
*Details* +
Script context-specific cache settings have been removed. Use
`script.cache.max_size` to set the size of the script cache for all scripts.

*Impact* +
Discontinue use of the removed setting. Specifying the setting in
`elasticsearch.yml` will result in an error on startup.
====

[[script-context-cache-expire-removed]]
.The `script.context.$CONTEXT.cache_expire` setting has been removed.
[%collapsible]
====
*Details* +
Script context-specific cache settings have been removed. Use
`script.cache.expire` to set the expiration of scripts in the script cache
for all scripts.

*Impact* +
Discontinue use of the removed setting. Specifying the setting in
`elasticsearch.yml` will result in an error on startup.
====
// end::notable-breaking-changes[]
6 changes: 3 additions & 3 deletions docs/reference/modules/indices/circuit_breaker.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,11 @@ within a period of time.
See the "prefer-parameters" section of the <<modules-scripting-using,scripting>>
documentation for more information.

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

[[regex-circuit-breaker]]
[discrete]
Expand Down
12 changes: 4 additions & 8 deletions docs/reference/scripting/using.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,8 @@ the `multiplier` parameter without {es} recompiling the script.
}
----

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

[source,js]
----
Expand Down Expand Up @@ -406,8 +402,8 @@ small.

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

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
really large, then consider using a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static <F> ScriptContext<F> newContext(String name, Class<F> factoryClass) {
* source of runaway script compilations. We think folks will
* mostly reuse scripts though.
*/
ScriptCache.UNLIMITED_COMPILATION_RATE.asTuple(),
false,
/*
* Disable runtime fields scripts from being allowed
* to be stored as part of the script meta data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public abstract class IngestConditionalScript {

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

/** The generic runtime parameters for the script. */
private final Map<String, Object> params;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public abstract class IngestScript {

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

/** The generic runtime parameters for the script. */
private final Map<String, Object> params;
Expand Down
13 changes: 5 additions & 8 deletions server/src/main/java/org/elasticsearch/script/ScriptCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,7 @@ public class ScriptCache {
private final double compilesAllowedPerNano;
private final String contextRateSetting;

ScriptCache(
int cacheMaxSize,
TimeValue cacheExpire,
CompilationRate maxCompilationRate,
String contextRateSetting
) {
ScriptCache(int cacheMaxSize, TimeValue cacheExpire, CompilationRate maxCompilationRate, String contextRateSetting) {
this.cacheSize = cacheMaxSize;
this.cacheExpire = cacheExpire;
this.contextRateSetting = contextRateSetting;
Expand Down Expand Up @@ -94,8 +89,10 @@ <FactoryType> FactoryType compile(
logger.trace("context [{}]: compiling script, type: [{}], lang: [{}], options: [{}]", context.name, type,
lang, options);
}
// Check whether too many compilations have happened
checkCompilationLimit();
if (context.compilationRateLimited) {
// Check whether too many compilations have happened
checkCompilationLimit();
}
Object compiledScript = scriptEngine.compile(id, idOrCode, context, options);
// Since the cache key is the script content itself we don't need to
// invalidate/check the cache if an indexed script changes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import java.util.Objects;
import java.util.stream.Collectors;

// This class is deprecated in favor of ScriptStats and ScriptContextStats. It is removed in 8.
// This class is deprecated in favor of ScriptStats and ScriptContextStats
public class ScriptCacheStats implements Writeable, ToXContentFragment {
private final Map<String, ScriptStats> context;
private final ScriptStats general;
Expand Down
14 changes: 8 additions & 6 deletions server/src/main/java/org/elasticsearch/script/ScriptContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
* be {@code boolean needs_score()}.
*/
public final class ScriptContext<FactoryType> {
/** The default compilation rate limit for contexts with compilation rate limiting enabled */
public static final Tuple<Integer, TimeValue> DEFAULT_COMPILATION_RATE_LIMIT = new Tuple<>(150, TimeValue.timeValueMinutes(5));

/** A unique identifier for this context. */
public final String name;
Expand All @@ -66,15 +68,15 @@ public final class ScriptContext<FactoryType> {
/** The default expiration of a script in the cache for the context, if not overridden */
public final TimeValue cacheExpireDefault;

/** The default max compilation rate for scripts in this context. Script compilation is throttled if this is exceeded */
public final Tuple<Integer, TimeValue> maxCompilationRateDefault;
/** Is compilation rate limiting enabled for this context? */
public final boolean compilationRateLimited;

/** Determines if the script can be stored as part of the cluster state. */
public final boolean allowStoredScript;

/** Construct a context with the related instance and compiled classes with caller provided cache defaults */
public ScriptContext(String name, Class<FactoryType> factoryClazz, int cacheSizeDefault, TimeValue cacheExpireDefault,
Tuple<Integer, TimeValue> maxCompilationRateDefault, boolean allowStoredScript) {
boolean compilationRateLimited, boolean allowStoredScript) {
this.name = name;
this.factoryClazz = factoryClazz;
Method newInstanceMethod = findMethod("FactoryType", factoryClazz, "newInstance");
Expand All @@ -98,15 +100,15 @@ public ScriptContext(String name, Class<FactoryType> factoryClazz, int cacheSize

this.cacheSizeDefault = cacheSizeDefault;
this.cacheExpireDefault = cacheExpireDefault;
this.maxCompilationRateDefault = maxCompilationRateDefault;
this.compilationRateLimited = compilationRateLimited;
this.allowStoredScript = allowStoredScript;
}

/** Construct a context with the related instance and compiled classes with defaults for cacheSizeDefault, cacheExpireDefault and
* maxCompilationRateDefault and allow scripts of this context to be stored scripts */
* compilationRateLimited and allow scripts of this context to be stored scripts */
public ScriptContext(String name, Class<FactoryType> factoryClazz) {
// cache size default, cache expire default, max compilation rate are defaults from ScriptService.
this(name, factoryClazz, 100, TimeValue.timeValueMillis(0), new Tuple<>(75, TimeValue.timeValueMinutes(5)), true);
this(name, factoryClazz, 100, TimeValue.timeValueMillis(0), true, true);
}

/** Returns a method with the given name, or throws an exception if multiple are found. */
Expand Down
48 changes: 35 additions & 13 deletions server/src/main/java/org/elasticsearch/script/ScriptService.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
Expand Down Expand Up @@ -50,6 +52,7 @@
public class ScriptService implements Closeable, ClusterStateApplier, ScriptCompiler {

private static final Logger logger = LogManager.getLogger(ScriptService.class);
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(ScriptService.class);

static final String DISABLE_DYNAMIC_SCRIPTING_SETTING = "script.disable_dynamic";

Expand All @@ -59,16 +62,22 @@ public class ScriptService implements Closeable, ClusterStateApplier, ScriptComp
static final String USE_CONTEXT_RATE_KEY = "use-context";

public static final Setting<Integer> SCRIPT_GENERAL_CACHE_SIZE_SETTING =
Setting.intSetting("script.cache.max_size", 100, 0, Property.NodeScope);
Setting.intSetting("script.cache.max_size", 3000, 0, Property.Dynamic, Property.NodeScope);
public static final Setting<TimeValue> SCRIPT_GENERAL_CACHE_EXPIRE_SETTING =
Setting.positiveTimeSetting("script.cache.expire", TimeValue.timeValueMillis(0), Property.NodeScope);
Setting.positiveTimeSetting("script.cache.expire", TimeValue.timeValueMillis(0), Property.Dynamic, Property.NodeScope);
public static final Setting<Integer> SCRIPT_MAX_SIZE_IN_BYTES =
Setting.intSetting("script.max_size_in_bytes", 65535, 0, Property.Dynamic, Property.NodeScope);
public static final Setting<ScriptCache.CompilationRate> SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING =
new Setting<>("script.max_compilations_rate", USE_CONTEXT_RATE_KEY,
new Setting<>("script.max_compilations_rate", "150/5m",
(String value) -> value.equals(USE_CONTEXT_RATE_KEY) ? USE_CONTEXT_RATE_VALUE: new ScriptCache.CompilationRate(value),
Property.Dynamic, Property.NodeScope);

public static final String USE_CONTEXT_RATE_KEY_DEPRECATION_MESSAGE = "[" + USE_CONTEXT_RATE_KEY + "] is deprecated for the setting [" +
SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey() + "] as system scripts are now exempt from the rate limit." +
"Set to a value such as [150/5m] (a rate of 150 compilations per five minutes) to rate limit user scripts in case the" +
"script cache [" + SCRIPT_GENERAL_CACHE_SIZE_SETTING.getKey() + "] is undersized causing script compilation thrashing.";


// Per-context settings
static final String CONTEXT_PREFIX = "script.context.";

Expand All @@ -77,13 +86,14 @@ public class ScriptService implements Closeable, ClusterStateApplier, ScriptComp
public static final Setting.AffixSetting<Integer> SCRIPT_CACHE_SIZE_SETTING =
Setting.affixKeySetting(CONTEXT_PREFIX,
"cache_max_size",
key -> Setting.intSetting(key, SCRIPT_GENERAL_CACHE_SIZE_SETTING, 0, Property.NodeScope, Property.Dynamic));
key -> Setting.intSetting(key, SCRIPT_GENERAL_CACHE_SIZE_SETTING, 0,
Property.NodeScope, Property.Dynamic, Property.Deprecated));

public static final Setting.AffixSetting<TimeValue> SCRIPT_CACHE_EXPIRE_SETTING =
Setting.affixKeySetting(CONTEXT_PREFIX,
"cache_expire",
key -> Setting.positiveTimeSetting(key, SCRIPT_GENERAL_CACHE_EXPIRE_SETTING, TimeValue.timeValueMillis(0),
Property.NodeScope, Property.Dynamic));
Property.NodeScope, Property.Dynamic, Property.Deprecated));

// Unlimited compilation rate for context-specific script caches
static final String UNLIMITED_COMPILATION_RATE_KEY = "unlimited";
Expand All @@ -94,7 +104,7 @@ public class ScriptService implements Closeable, ClusterStateApplier, ScriptComp
key -> new Setting<ScriptCache.CompilationRate>(key, "75/5m",
(String value) -> value.equals(UNLIMITED_COMPILATION_RATE_KEY) ? ScriptCache.UNLIMITED_COMPILATION_RATE:
new ScriptCache.CompilationRate(value),
Property.NodeScope, Property.Dynamic));
Property.NodeScope, Property.Dynamic, Property.Deprecated));

private static final ScriptCache.CompilationRate SCRIPT_COMPILATION_RATE_ZERO = new ScriptCache.CompilationRate(0, TimeValue.ZERO);

Expand Down Expand Up @@ -205,6 +215,10 @@ public ScriptService(Settings settings, Map<String, ScriptEngine> engines, Map<S
this.setCacheHolder(settings);
}

public static boolean isUseContextCacheSet(Settings settings) {
return SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings).equals(USE_CONTEXT_RATE_VALUE);
}

/**
* This is overridden in tests to disable compilation rate limiting.
*/
Expand All @@ -231,7 +245,7 @@ void registerClusterSettingsListeners(ClusterSettings clusterSettings) {

// Handle all settings for context and general caches, this flips between general and context caches.
clusterSettings.addSettingsUpdateConsumer(
(settings) -> setCacheHolder(settings),
this::setCacheHolder,
Arrays.asList(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING,
SCRIPT_GENERAL_CACHE_EXPIRE_SETTING,
SCRIPT_GENERAL_CACHE_SIZE_SETTING,
Expand All @@ -249,6 +263,10 @@ void registerClusterSettingsListeners(ClusterSettings clusterSettings) {
*/
void validateCacheSettings(Settings settings) {
boolean useContext = SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings).equals(USE_CONTEXT_RATE_VALUE);
if (useContext) {
deprecationLogger.critical(DeprecationCategory.SCRIPTING, "scripting-context-cache",
USE_CONTEXT_RATE_KEY_DEPRECATION_MESSAGE);
}
List<Setting.AffixSetting<?>> affixes = Arrays.asList(SCRIPT_MAX_COMPILATIONS_RATE_SETTING, SCRIPT_CACHE_EXPIRE_SETTING,
SCRIPT_CACHE_SIZE_SETTING);
List<String> customRates = new ArrayList<>();
Expand Down Expand Up @@ -277,7 +295,7 @@ void validateCacheSettings(Settings settings) {
String.join(", ", customRates) + "] if compile rates disabled via [" +
SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING.getKey() + "]");
}
if (useContext == false) {
if (useContext == false && SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.exists(settings)) {
throw new IllegalArgumentException("Cannot set custom general compilation rates [" +
SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey() + "] to [" +
SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings) + "] if compile rates disabled via [" +
Expand Down Expand Up @@ -565,8 +583,10 @@ void setCacheHolder(Settings settings) {
} else if (current.general == null) {
// Flipping to general
cacheHolder.set(generalCacheHolder(settings));
} else if (current.general.rate.equals(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings)) == false) {
// General compilation rate changed, that setting is the only dynamically updated general setting
} else if (current.general.rate.equals(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings)) == false ||
current.general.cacheExpire.equals(SCRIPT_GENERAL_CACHE_EXPIRE_SETTING.get(settings)) == false ||
current.general.cacheSize != SCRIPT_GENERAL_CACHE_SIZE_SETTING.get(settings)) {
// General compilation rate, cache expiration or cache size changed
cacheHolder.set(generalCacheHolder(settings));
}
}
Expand All @@ -592,13 +612,15 @@ ScriptCache contextCache(Settings settings, ScriptContext<?> context) {

Setting<ScriptCache.CompilationRate> rateSetting =
SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace(context.name);
ScriptCache.CompilationRate rate = null;
if (SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING.get(settings) || compilationLimitsEnabled() == false) {
ScriptCache.CompilationRate rate;
if (SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING.get(settings)
|| compilationLimitsEnabled() == false
|| context.compilationRateLimited == false) {
rate = SCRIPT_COMPILATION_RATE_ZERO;
} else if (rateSetting.existsOrFallbackExists(settings)) {
rate = rateSetting.get(settings);
} else {
rate = new ScriptCache.CompilationRate(context.maxCompilationRateDefault);
rate = new ScriptCache.CompilationRate(ScriptContext.DEFAULT_COMPILATION_RATE_LIMIT);
}

return new ScriptCache(cacheSize, cacheExpire, rate, rateSetting.getKey());
Expand Down
Loading