Scripting: Increase ingest script cache defaults (#53906)

* Adds ability for contexts to specify their own defaults.
* Context defaults are applied if no context-specific or
  general setting exists.
* See 070ea7e for settings keys.

* Increases the per-context default for the `ingest` context.
  * Cache size is doubled, 200 compared to default of 100
  * Cache expiration is unchanged at no expiration
  * Cache max compilation is quintupled, 375/5m instead of 75/5m

Backport of: 1b37d4b
Refs: #50152
This commit is contained in:
Stuart Tettemer 2020-03-20 16:48:50 -06:00 committed by GitHub
parent 10cabbbade
commit d25c01a373
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 181 additions and 36 deletions

View File

@ -19,6 +19,9 @@
package org.elasticsearch.script;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.unit.TimeValue;
import java.util.Map;
/**
@ -29,7 +32,8 @@ public abstract class IngestConditionalScript {
public static final String[] PARAMETERS = { "ctx" };
/** The context used to compile {@link IngestConditionalScript} factories. */
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("processor_conditional", Factory.class);
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("processor_conditional", Factory.class,
200, TimeValue.timeValueMillis(0), new Tuple<>(375, TimeValue.timeValueMinutes(5)));
/** The generic runtime parameters for the script. */
private final Map<String, Object> params;

View File

@ -20,6 +20,9 @@
package org.elasticsearch.script;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.unit.TimeValue;
import java.util.Map;
/**
@ -30,7 +33,8 @@ public abstract class IngestScript {
public static final String[] PARAMETERS = { "ctx" };
/** The context used to compile {@link IngestScript} factories. */
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("ingest", Factory.class);
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("ingest", Factory.class,
200, TimeValue.timeValueMillis(0), new Tuple<>(375, TimeValue.timeValueMinutes(5)));
/** The generic runtime parameters for the script. */
private final Map<String, Object> params;

View File

@ -19,6 +19,9 @@
package org.elasticsearch.script;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.unit.TimeValue;
import java.lang.reflect.Method;
/**
@ -68,8 +71,18 @@ public final class ScriptContext<FactoryType> {
/** A class that is an instance of a script. */
public final Class<?> instanceClazz;
/** Construct a context with the related instance and compiled classes. */
public ScriptContext(String name, Class<FactoryType> factoryClazz) {
/** The default size of the cache for the context if not overridden */
public final int cacheSizeDefault;
/** 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;
/** 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) {
this.name = name;
this.factoryClazz = factoryClazz;
Method newInstanceMethod = findMethod("FactoryType", factoryClazz, "newInstance");
@ -90,6 +103,17 @@ public final class ScriptContext<FactoryType> {
+ factoryClazz.getName() + "] for script context [" + name + "]");
}
instanceClazz = newInstanceMethod.getReturnType();
this.cacheSizeDefault = cacheSizeDefault;
this.cacheExpireDefault = cacheExpireDefault;
this.maxCompilationRateDefault = maxCompilationRateDefault;
}
/** Construct a context with the related instance and compiled classes with defaults for cacheSizeDefault, cacheExpireDefault and
* maxCompilationRateDefault */
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)));
}
/** Returns a method with the given name, or throws an exception if multiple are found. */

View File

@ -47,6 +47,7 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@ -242,7 +243,7 @@ public class ScriptService implements Closeable, ClusterStateApplier {
// Validation requires knowing which contexts exist.
this.validateCacheSettings(settings);
cacheHolder = new AtomicReference<>(new CacheHolder(settings, contexts.keySet(), compilationLimitsEnabled()));
cacheHolder = new AtomicReference<>(new CacheHolder(settings, contexts.values(), compilationLimitsEnabled()));
}
/**
@ -256,12 +257,12 @@ public class ScriptService implements Closeable, ClusterStateApplier {
clusterSettings.addSettingsUpdateConsumer(SCRIPT_MAX_SIZE_IN_BYTES, this::setMaxSizeInBytes);
// Handle all updatable per-context settings at once for each context.
for (String context: contexts.keySet()) {
for (ScriptContext<?> context: contexts.values()) {
clusterSettings.addSettingsUpdateConsumer(
(settings) -> cacheHolder.get().updateContextSettings(settings, context),
Arrays.asList(SCRIPT_CACHE_SIZE_SETTING.getConcreteSettingForNamespace(context),
SCRIPT_CACHE_EXPIRE_SETTING.getConcreteSettingForNamespace(context),
SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace(context),
Arrays.asList(SCRIPT_CACHE_SIZE_SETTING.getConcreteSettingForNamespace(context.name),
SCRIPT_CACHE_EXPIRE_SETTING.getConcreteSettingForNamespace(context.name),
SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace(context.name),
SCRIPT_GENERAL_CACHE_EXPIRE_SETTING,
// general settings used for fallbacks
SCRIPT_GENERAL_CACHE_SIZE_SETTING)
@ -581,17 +582,18 @@ public class ScriptService implements Closeable, ClusterStateApplier {
final ScriptCache general;
final Map<String, AtomicReference<ScriptCache>> contextCache;
final Set<String> contexts;
final Set<ScriptContext<?>> contexts;
final boolean compilationLimitsEnabled;
CacheHolder(Settings settings, Set<String> contexts, boolean compilationLimitsEnabled) {
CacheHolder(Settings settings, Collection<ScriptContext<?>> contexts, boolean compilationLimitsEnabled) {
this.compilationLimitsEnabled = compilationLimitsEnabled;
this.contexts = Collections.unmodifiableSet(contexts);
this.contexts = Collections.unmodifiableSet(new HashSet<>(contexts));
if (SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings).equals(USE_CONTEXT_RATE_VALUE)) {
this.general = null;
Map<String, AtomicReference<ScriptCache>> contextCache = new HashMap<>(this.contexts.size());
for (String context : this.contexts) {
contextCache.put(context, new AtomicReference<>(contextFromSettings(settings, context, this.compilationLimitsEnabled)));
for (ScriptContext<?> context : this.contexts) {
contextCache.put(context.name,
new AtomicReference<>(contextFromSettings(settings, context, this.compilationLimitsEnabled)));
}
this.contextCache = Collections.unmodifiableMap(contextCache);
} else {
@ -608,12 +610,24 @@ public class ScriptService implements Closeable, ClusterStateApplier {
/**
* Create a ScriptCache for the given context.
*/
private static ScriptCache contextFromSettings(Settings settings, String context, boolean compilationLimitsEnabled) {
return new ScriptCache(SCRIPT_CACHE_SIZE_SETTING.getConcreteSettingForNamespace(context).get(settings),
SCRIPT_CACHE_EXPIRE_SETTING.getConcreteSettingForNamespace(context).get(settings),
compilationLimitsEnabled ?
SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace(context).get(settings) :
SCRIPT_COMPILATION_RATE_ZERO);
private static ScriptCache contextFromSettings(Settings settings, ScriptContext<?> context, boolean compilationLimitsEnabled) {
String name = context.name;
Tuple<Integer, TimeValue> compileRate;
Setting<Tuple<Integer, TimeValue>> rateSetting = SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace(name);
if (compilationLimitsEnabled == false) {
compileRate = SCRIPT_COMPILATION_RATE_ZERO;
} else if (rateSetting.existsOrFallbackExists(settings)) {
compileRate = rateSetting.get(settings);
} else {
compileRate = context.maxCompilationRateDefault;
}
Setting<TimeValue> cacheExpire = SCRIPT_CACHE_EXPIRE_SETTING.getConcreteSettingForNamespace(name);
Setting<Integer> cacheSize = SCRIPT_CACHE_SIZE_SETTING.getConcreteSettingForNamespace(name);
return new ScriptCache(cacheSize.existsOrFallbackExists(settings) ? cacheSize.get(settings) : context.cacheSizeDefault,
cacheExpire.existsOrFallbackExists(settings) ? cacheExpire.get(settings) : context.cacheExpireDefault,
compileRate);
}
/**
@ -667,16 +681,16 @@ public class ScriptService implements Closeable, ClusterStateApplier {
/**
* Update settings for the context cache, if we're in the context cache mode otherwise no-op.
*/
void updateContextSettings(Settings settings, String context) {
void updateContextSettings(Settings settings, ScriptContext<?> context) {
if (general != null) {
return;
}
AtomicReference<ScriptCache> ref = contextCache.get(context);
assert ref != null : "expected script cache to exist for context [" + context + "]";
AtomicReference<ScriptCache> ref = contextCache.get(context.name);
assert ref != null : "expected script cache to exist for context [" + context.name + "]";
ScriptCache cache = ref.get();
assert cache != null : "expected script cache to be non-null for context [" + context + "]";
assert cache != null : "expected script cache to be non-null for context [" + context.name + "]";
ref.set(contextFromSettings(settings, context, compilationLimitsEnabled));
logger.debug("Replaced context [" + context + "] with new settings");
logger.debug("Replaced context [" + context.name + "] with new settings");
}
}
}

View File

@ -37,16 +37,22 @@ import org.junit.Before;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.elasticsearch.script.ScriptService.MAX_COMPILATION_RATE_FUNCTION;
import static org.elasticsearch.script.ScriptService.SCRIPT_CACHE_EXPIRE_SETTING;
import static org.elasticsearch.script.ScriptService.SCRIPT_CACHE_SIZE_SETTING;
import static org.elasticsearch.script.ScriptService.SCRIPT_GENERAL_CACHE_EXPIRE_SETTING;
import static org.elasticsearch.script.ScriptService.SCRIPT_GENERAL_CACHE_SIZE_SETTING;
import static org.elasticsearch.script.ScriptService.SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING;
import static org.elasticsearch.script.ScriptService.SCRIPT_MAX_COMPILATIONS_RATE_SETTING;
import static org.elasticsearch.script.ScriptService.USE_CONTEXT_RATE_KEY;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -227,7 +233,7 @@ public class ScriptServiceTests extends ESTestCase {
public void testCompilationStatsOnCacheHit() throws IOException {
Settings.Builder builder = Settings.builder();
builder.put(ScriptService.SCRIPT_GENERAL_CACHE_SIZE_SETTING.getKey(), 1);
builder.put(SCRIPT_GENERAL_CACHE_SIZE_SETTING.getKey(), 1);
buildScriptService(builder.build());
Script script = new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap());
ScriptContext<?> context = randomFrom(contexts.values());
@ -244,7 +250,7 @@ public class ScriptServiceTests extends ESTestCase {
public void testCacheEvictionCountedInCacheEvictionsStats() throws IOException {
Settings.Builder builder = Settings.builder();
builder.put(ScriptService.SCRIPT_GENERAL_CACHE_SIZE_SETTING.getKey(), 1);
builder.put(SCRIPT_GENERAL_CACHE_SIZE_SETTING.getKey(), 1);
buildScriptService(builder.build());
scriptService.compile(new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap()), randomFrom(contexts.values()));
scriptService.compile(new Script(ScriptType.INLINE, "test", "2+2", Collections.emptyMap()), randomFrom(contexts.values()));
@ -362,7 +368,7 @@ public class ScriptServiceTests extends ESTestCase {
TimeValue cacheExpireFooParsed = TimeValue.parseTimeValue(cacheExpireFoo, "");
Settings s = Settings.builder()
.put(ScriptService.SCRIPT_GENERAL_CACHE_SIZE_SETTING.getKey(), cacheSizeBackup)
.put(SCRIPT_GENERAL_CACHE_SIZE_SETTING.getKey(), cacheSizeBackup)
.put(ScriptService.SCRIPT_CACHE_SIZE_SETTING.getConcreteSettingForNamespace("foo").getKey(), cacheSizeFoo)
.put(ScriptService.SCRIPT_GENERAL_CACHE_EXPIRE_SETTING.getKey(), cacheExpireBackup)
.put(ScriptService.SCRIPT_CACHE_EXPIRE_SETTING.getConcreteSettingForNamespace("foo").getKey(), cacheExpireFoo)
@ -397,7 +403,7 @@ public class ScriptServiceTests extends ESTestCase {
boolean compilationLimitsEnabled = true;
ScriptService.CacheHolder holder = new ScriptService.CacheHolder(
Settings.builder().put(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey(), compilationRate).build(),
new HashSet<>(Collections.singleton("foo")),
Collections.unmodifiableSet(Collections.singleton(newContext("foo"))),
compilationLimitsEnabled
);
@ -408,7 +414,7 @@ public class ScriptServiceTests extends ESTestCase {
compilationLimitsEnabled = false;
holder = new ScriptService.CacheHolder(
Settings.builder().put(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey(), compilationRate).build(),
new HashSet<>(Collections.singleton("foo")),
new HashSet<>(Collections.singleton(newContext("foo"))),
compilationLimitsEnabled
);
@ -427,13 +433,14 @@ public class ScriptServiceTests extends ESTestCase {
.put(SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace("foo").getKey(), fooCompilationRate)
.put(SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace("bar").getKey(), barCompilationRate)
.build();
Set<String> contexts = new HashSet<>(Arrays.asList("foo", "bar", "baz"));
Collection<ScriptContext<?>> contexts = new HashSet<>(Arrays.asList(newContext("foo"), newContext("bar"),
newContext("baz")));
ScriptService.CacheHolder holder = new ScriptService.CacheHolder(s, contexts, compilationLimitsEnabled);
assertNull(holder.general);
assertNotNull(holder.contextCache);
assertEquals(3, holder.contextCache.size());
assertEquals(contexts, holder.contextCache.keySet());
assertEquals(contexts.stream().map(c -> c.name).collect(Collectors.toSet()), holder.contextCache.keySet());
assertEquals(ScriptService.MAX_COMPILATION_RATE_FUNCTION.apply(fooCompilationRate), holder.contextCache.get("foo").get().rate);
assertEquals(ScriptService.MAX_COMPILATION_RATE_FUNCTION.apply(barCompilationRate), holder.contextCache.get("bar").get().rate);
@ -478,7 +485,8 @@ public class ScriptServiceTests extends ESTestCase {
Settings s = Settings.builder()
.put(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey(), compilationRate)
.build();
Set<String> contexts = new HashSet<>(Arrays.asList("foo", "bar", "baz", "qux"));
Collection<ScriptContext<?>> contexts = new HashSet<>(Arrays.asList(newContext("foo"), newContext("bar"),
newContext("baz"), newContext("qux")));
ScriptService.CacheHolder holder = new ScriptService.CacheHolder(s, contexts, true);
assertNotNull(holder.general);
@ -497,7 +505,7 @@ public class ScriptServiceTests extends ESTestCase {
assertNull(holder.general);
assertNotNull(holder.contextCache);
assertEquals(4, holder.contextCache.size());
assertEquals(contexts, holder.contextCache.keySet());
assertEquals(contexts.stream().map(c -> c.name).collect(Collectors.toSet()), holder.contextCache.keySet());
assertEquals(ScriptService.MAX_COMPILATION_RATE_FUNCTION.apply(fooCompilationRate), holder.contextCache.get("foo").get().rate);
assertEquals(ScriptService.MAX_COMPILATION_RATE_FUNCTION.apply(barCompilationRate), holder.contextCache.get("bar").get().rate);
@ -507,7 +515,7 @@ public class ScriptServiceTests extends ESTestCase {
holder.updateContextSettings(Settings.builder()
.put(SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace("bar").getKey(), fooCompilationRate).build(),
"bar"
newContext("bar")
);
assertEquals(ScriptService.MAX_COMPILATION_RATE_FUNCTION.apply(fooCompilationRate), holder.contextCache.get("bar").get().rate);
@ -530,6 +538,93 @@ public class ScriptServiceTests extends ESTestCase {
assertSame(holder, update);
}
public void testFallbackToContextDefaults() {
Tuple<Integer, TimeValue> contextDefaultRate = new Tuple<>(randomIntBetween(10, 1024),
TimeValue.timeValueMinutes(randomIntBetween(10, 200)));
String name = "foo";
ScriptContext<?> foo = new ScriptContext<>(name,
ScriptContextTests.DummyScript.Factory.class,
randomIntBetween(1, 1024),
TimeValue.timeValueMinutes(randomIntBetween(10, 200)),
contextDefaultRate);
int generalCacheSize = randomIntBetween(1, 1024);
TimeValue generalExpire = TimeValue.timeValueMinutes(randomIntBetween(10, 200));
String contextRateStr = randomIntBetween(10, 1024) + "/" + randomIntBetween(10, 200) + "m";
Tuple<Integer, TimeValue> contextRate = MAX_COMPILATION_RATE_FUNCTION.apply(contextRateStr);
int contextCacheSize = randomIntBetween(1, 1024);
TimeValue contextExpire = TimeValue.timeValueMinutes(randomIntBetween(10, 200));
// Use context specific
ScriptService.CacheHolder contextCache = new ScriptService.CacheHolder(
Settings.builder()
.put(SCRIPT_CACHE_SIZE_SETTING.getConcreteSettingForNamespace(name).getKey(), contextCacheSize)
.put(SCRIPT_CACHE_EXPIRE_SETTING.getConcreteSettingForNamespace(name).getKey(), contextExpire)
.put(SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace(name).getKey(), contextRateStr)
.put(SCRIPT_GENERAL_CACHE_SIZE_SETTING.getKey(), generalCacheSize)
.put(SCRIPT_GENERAL_CACHE_EXPIRE_SETTING.getKey(), generalExpire)
.put(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey(), USE_CONTEXT_RATE_KEY)
.build(),
Arrays.asList(foo),
true);
assertNotNull(contextCache.contextCache);
assertNotNull(contextCache.contextCache.get(name));
assertNotNull(contextCache.contextCache.get(name).get());
assertEquals(contextRate, contextCache.contextCache.get(name).get().rate);
assertEquals(contextCacheSize, contextCache.contextCache.get(name).get().cacheSize);
assertEquals(contextExpire, contextCache.contextCache.get(name).get().cacheExpire);
// Fallback to general
contextCache = new ScriptService.CacheHolder(
Settings.builder()
.put(SCRIPT_GENERAL_CACHE_SIZE_SETTING.getKey(), generalCacheSize)
.put(SCRIPT_GENERAL_CACHE_EXPIRE_SETTING.getKey(), generalExpire)
.put(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey(), USE_CONTEXT_RATE_KEY)
.build(),
Arrays.asList(foo),
true);
assertNotNull(contextCache.contextCache);
assertNotNull(contextCache.contextCache.get(name));
assertNotNull(contextCache.contextCache.get(name).get());
assertEquals(contextDefaultRate, contextCache.contextCache.get(name).get().rate);
assertEquals(generalCacheSize, contextCache.contextCache.get(name).get().cacheSize);
assertEquals(generalExpire, contextCache.contextCache.get(name).get().cacheExpire);
// Fallback to context defaults
contextCache = new ScriptService.CacheHolder(
Settings.builder()
.put(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey(), USE_CONTEXT_RATE_KEY)
.build(),
Arrays.asList(foo),
true);
assertNotNull(contextCache.contextCache);
assertNotNull(contextCache.contextCache.get(name));
assertNotNull(contextCache.contextCache.get(name).get());
assertEquals(contextDefaultRate, contextCache.contextCache.get(name).get().rate);
assertEquals(foo.cacheSizeDefault, contextCache.contextCache.get(name).get().cacheSize);
assertEquals(foo.cacheExpireDefault, contextCache.contextCache.get(name).get().cacheExpire);
// Use context specific for ingest
contextCache = new ScriptService.CacheHolder(
Settings.builder()
.put(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey(), USE_CONTEXT_RATE_KEY)
.build(),
Arrays.asList(foo, IngestScript.CONTEXT, IngestConditionalScript.CONTEXT),
true);
assertEquals(new Tuple<>(375, TimeValue.timeValueMinutes(5)),
contextCache.contextCache.get("ingest").get().rate);
assertEquals(200, contextCache.contextCache.get("ingest").get().cacheSize);
assertEquals(TimeValue.timeValueMillis(0), contextCache.contextCache.get("ingest").get().cacheExpire);
}
private void assertCompileRejected(String lang, String script, ScriptType scriptType, ScriptContext scriptContext) {
try {
scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext);
@ -546,4 +641,8 @@ public class ScriptServiceTests extends ESTestCase {
notNullValue()
);
}
ScriptContext<ScriptContextTests.DummyScript.Factory> newContext(String name) {
return new ScriptContext<>(name, ScriptContextTests.DummyScript.Factory.class);
}
}