Skip to content

Commit

Permalink
Script: Restore the scripting general cache
Browse files Browse the repository at this point in the history
Restore the scripting general cache in preparation for deprecation of
the context cache, then removal of the context cache in master.

Brings back the general cache settings:
* `script.max_compilations_rate`
* `script.cache.expire`
* `script.cache.max_size`

`script.cache.max_size` and `script.cache.expire` are now dynamic settings.

The context cache is still default, the switch to general cache as default will happen on context cache deprecation.

System script contexts can now opt-out of compilation rate limiting using a flag rather than a sentinel rate limit value.  Duplicated defaults for system contexts have been coalesced.

Other than that, this code is the same as what was in 7.x to make a `master`-first commit and backport strategy easy.

Refs: elastic#62899
  • Loading branch information
stu-elastic committed Oct 19, 2021
1 parent 6192cb2 commit e709412
Show file tree
Hide file tree
Showing 16 changed files with 644 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import java.util.Map;
import java.util.function.Function;

import static org.elasticsearch.core.TimeValue.timeValueMillis;

/**
* Abstract base for scripts to execute to build scripted fields. Inspired by
* {@link AggregationScript} but hopefully with less historical baggage.
Expand All @@ -32,33 +30,21 @@ public abstract class AbstractFieldScript extends DocBasedScript {
public static final int MAX_VALUES = 100;

static <F> ScriptContext<F> newContext(String name, Class<F> factoryClass) {
return new ScriptContext<>(
name,
factoryClass,
/*
* We rely on the script cache in two ways:
* 1. It caches the "heavy" part of mappings generated at runtime.
* 2. Mapping updates tend to try to compile the script twice. Not
* for any good reason. They just do.
* Thus we use the default 100.
*/
100,
timeValueMillis(0),
/*
* Disable compilation rate limits for runtime fields so we
* don't prevent mapping updates because we've performed too
* many recently. That'd just be lame. We also compile these
* scripts during search requests so this could totally be a
* source of runaway script compilations. We think folks will
* mostly reuse scripts though.
*/
ScriptCache.UNLIMITED_COMPILATION_RATE.asTuple(),
/*
* Disable runtime fields scripts from being allowed
* to be stored as part of the script meta data.
*/
false
);
/*
* We rely on the script cache in two ways:
* 1. It caches the "heavy" part of mappings generated at runtime.
* 2. Mapping updates tend to try to compile the script twice. Not
* for any good reason. They just do.
* Thus we use the default cache size.
*
* We also disable compilation rate limits for runtime fields so we
* don't prevent mapping updates because we've performed too
* many recently. That'd just be lame. We also compile these
* scripts during search requests so this could totally be a
* source of runaway script compilations. We think folks will
* mostly reuse scripts though.
*/
return new ScriptContext<>(name, factoryClass, false);
}

private static final Map<String, Function<Object, Object>> PARAMS_FUNCTIONS = Map.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

package org.elasticsearch.script;

import org.elasticsearch.core.TimeValue;

import java.util.Map;

/**
Expand All @@ -20,8 +18,7 @@ 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,
200, TimeValue.timeValueMillis(0), ScriptCache.UNLIMITED_COMPILATION_RATE.asTuple(), true);
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("processor_conditional", Factory.class, 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 @@ -9,8 +9,6 @@

package org.elasticsearch.script;

import org.elasticsearch.core.TimeValue;

import java.util.Map;

/**
Expand All @@ -21,8 +19,7 @@ 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,
200, TimeValue.timeValueMillis(0), ScriptCache.UNLIMITED_COMPILATION_RATE.asTuple(), true);
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("ingest", Factory.class, true);

/** The generic runtime parameters for the script. */
private final Map<String, Object> params;
Expand Down
17 changes: 9 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 All @@ -121,6 +118,10 @@ static <T extends Throwable> void rethrow(Throwable t) throws T {
throw (T) t;
}

public ScriptStats stats() {
return scriptMetrics.stats();
}

public ScriptContextStats stats(String context) {
return scriptMetrics.stats(context);
}
Expand Down
149 changes: 149 additions & 0 deletions server/src/main/java/org/elasticsearch/script/ScriptCacheStats.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.script;

import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.xcontent.ToXContentFragment;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

// 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;

public ScriptCacheStats(Map<String, ScriptStats> context) {
this.context = Collections.unmodifiableMap(context);
this.general = null;
}

public ScriptCacheStats(ScriptStats general) {
this.general = Objects.requireNonNull(general);
this.context = null;
}

public ScriptCacheStats(StreamInput in) throws IOException {
boolean isContext = in.readBoolean();
if (isContext == false) {
general = new ScriptStats(in);
context = null;
return;
}

general = null;
int size = in.readInt();
Map<String,ScriptStats> context = new HashMap<>(size);
for (int i=0; i < size; i++) {
String name = in.readString();
context.put(name, new ScriptStats(in));
}
this.context = Collections.unmodifiableMap(context);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
if (general != null) {
out.writeBoolean(false);
general.writeTo(out);
return;
}

out.writeBoolean(true);
out.writeInt(context.size());
for (String name: context.keySet().stream().sorted().collect(Collectors.toList())) {
out.writeString(name);
context.get(name).writeTo(out);
}
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(Fields.SCRIPT_CACHE_STATS);
builder.startObject(Fields.SUM);
if (general != null) {
builder.field(ScriptStats.Fields.COMPILATIONS, general.getCompilations());
builder.field(ScriptStats.Fields.CACHE_EVICTIONS, general.getCacheEvictions());
builder.field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, general.getCompilationLimitTriggered());
builder.endObject().endObject();
return builder;
}

ScriptStats sum = sum();
builder.field(ScriptStats.Fields.COMPILATIONS, sum.getCompilations());
builder.field(ScriptStats.Fields.CACHE_EVICTIONS, sum.getCacheEvictions());
builder.field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, sum.getCompilationLimitTriggered());
builder.endObject();

builder.startArray(Fields.CONTEXTS);
for (String name: context.keySet().stream().sorted().collect(Collectors.toList())) {
ScriptStats stats = context.get(name);
builder.startObject();
builder.field(Fields.CONTEXT, name);
builder.field(ScriptStats.Fields.COMPILATIONS, stats.getCompilations());
builder.field(ScriptStats.Fields.CACHE_EVICTIONS, stats.getCacheEvictions());
builder.field(ScriptStats.Fields.COMPILATION_LIMIT_TRIGGERED, stats.getCompilationLimitTriggered());
builder.endObject();
}
builder.endArray();
builder.endObject();

return builder;
}

/**
* Get the context specific stats, null if using general cache
*/
public Map<String, ScriptStats> getContextStats() {
return context;
}

/**
* Get the general stats, null if using context cache
*/
public ScriptStats getGeneralStats() {
return general;
}

/**
* The sum of all script stats, either the general stats or the sum of all stats of the context stats.
*/
public ScriptStats sum() {
if (general != null) {
return general;
}
long compilations = 0;
long cacheEvictions = 0;
long compilationLimitTriggered = 0;
for (ScriptStats stat: context.values()) {
compilations += stat.getCompilations();
cacheEvictions += stat.getCacheEvictions();
compilationLimitTriggered += stat.getCompilationLimitTriggered();
}
return new ScriptStats(
compilations,
cacheEvictions,
compilationLimitTriggered
);
}

static final class Fields {
static final String SCRIPT_CACHE_STATS = "script_cache";
static final String CONTEXT = "context";
static final String SUM = "sum";
static final String CONTEXTS = "contexts";
}
}
24 changes: 18 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) {
ScriptContext(String name, Class<FactoryType> factoryClazz, int cacheSizeDefault, TimeValue cacheExpireDefault,
boolean compilationRateLimited, boolean allowStoredScript) {
this.name = name;
this.factoryClazz = factoryClazz;
Method newInstanceMethod = findMethod("FactoryType", factoryClazz, "newInstance");
Expand All @@ -98,15 +100,25 @@ 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 */
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);
}

/**
* Construct a context for a system script usage, using the:
* - default cache size (only relevant when context caching enabled): 200
* - default cache expiration: no expiration
* - unlimited compilation rate
*/
public ScriptContext(String name, Class<FactoryType> factoryClazz, boolean allowStoredScript) {
this(name, factoryClazz, 200, TimeValue.timeValueMillis(0), false, allowStoredScript);
}

/** Returns a method with the given name, or throws an exception if multiple are found. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public void onCompilationLimit() {
compilationLimitTriggered.inc();
}

public ScriptStats stats() {
return new ScriptStats(compilationsMetric.count(), cacheEvictionsMetric.count(), compilationLimitTriggered.count());
}

public ScriptContextStats stats(String context) {
return new ScriptContextStats(
context,
Expand All @@ -36,4 +40,5 @@ public ScriptContextStats stats(String context) {
new ScriptContextStats.TimeSeries(),
new ScriptContextStats.TimeSeries()
);
}}
}
}
Loading

0 comments on commit e709412

Please sign in to comment.