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

ScriptService: Replace max compilation per minute setting with max compilation rate #26399

Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,11 @@ class ClusterFormationTasks {
esConfig['cluster.routing.allocation.disk.watermark.flood_stage'] = '1b'
}
// increase script compilation limit since tests can rapid-fire script compilations
esConfig['script.max_compilations_per_minute'] = 2048
if (Version.fromString(node.nodeVersion).major > 6) {
esConfig['script.max_compilations_rate'] = 2048
} else {
esConfig['script.max_compilations_per_minute'] = 2048
}
esConfig.putAll(node.config.settings)

Task writeConfig = project.tasks.create(name: name, type: DefaultTask, dependsOn: setup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ public void apply(Settings value, Settings current, Settings previous) {
ScriptService.SCRIPT_CACHE_SIZE_SETTING,
ScriptService.SCRIPT_CACHE_EXPIRE_SETTING,
ScriptService.SCRIPT_MAX_SIZE_IN_BYTES,
ScriptService.SCRIPT_MAX_COMPILATIONS_PER_MINUTE,
ScriptService.SCRIPT_MAX_COMPILATIONS_RATE,
ScriptService.TYPES_ALLOWED_SETTING,
ScriptService.CONTEXTS_ALLOWED_SETTING,
IndicesService.INDICES_CACHE_CLEAN_INTERVAL_SETTING,
Expand Down
41 changes: 23 additions & 18 deletions core/src/main/java/org/elasticsearch/script/ScriptService.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
Setting.positiveTimeSetting("script.cache.expire", TimeValue.timeValueMillis(0), Property.NodeScope);
public static final Setting<Integer> SCRIPT_MAX_SIZE_IN_BYTES =
Setting.intSetting("script.max_size_in_bytes", 65535, Property.NodeScope);
public static final Setting<Integer> SCRIPT_MAX_COMPILATIONS_PER_MINUTE =
Setting.intSetting("script.max_compilations_per_minute", 15, 0, Property.Dynamic, Property.NodeScope);
public static final Setting<Integer> SCRIPT_MAX_COMPILATIONS_RATE =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this new name is better. It has no indication what the base of the rate is? One must find docs to know what a value means, while before it was very clear from the name (per minute). Can we have something to indicate this, or have the values not be a plain integer, but instead a string with some kind of unit? I realize this is creeping the scope a bit, but I think this new setting will just be confusing (eg look at the history of size values without units in 1.x days).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, that it should be obvious by reading the setting how it works.

I changed the setting to be a real rate, which consists of an nonnegative integer and a positive timevalue split by a / defaulting to 75/5m. Is this what you were after?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, exactly what I was hoping for.

Setting.intSetting("script.max_compilations_rate", 75, 0, Property.Dynamic, Property.NodeScope);

public static final String ALLOW_NONE = "none";

Expand All @@ -88,9 +88,9 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust

private ClusterState clusterState;

private int totalCompilesPerMinute;
private int totalCompilesPerFiveMinutes;
private long lastInlineCompileTime;
private double scriptsPerMinCounter;
private double scriptsPerFiveMinsCounter;
private double compilesAllowedPerNano;

public ScriptService(Settings settings, Map<String, ScriptEngine> engines, Map<String, ScriptContext<?>> contexts) {
Expand Down Expand Up @@ -188,11 +188,11 @@ public ScriptService(Settings settings, Map<String, ScriptEngine> engines, Map<S
this.cache = cacheBuilder.removalListener(new ScriptCacheRemovalListener()).build();

this.lastInlineCompileTime = System.nanoTime();
this.setMaxCompilationsPerMinute(SCRIPT_MAX_COMPILATIONS_PER_MINUTE.get(settings));
this.setMaxCompilationRate(SCRIPT_MAX_COMPILATIONS_RATE.get(settings));
}

void registerClusterSettingsListeners(ClusterSettings clusterSettings) {
clusterSettings.addSettingsUpdateConsumer(SCRIPT_MAX_COMPILATIONS_PER_MINUTE, this::setMaxCompilationsPerMinute);
clusterSettings.addSettingsUpdateConsumer(SCRIPT_MAX_COMPILATIONS_RATE, this::setMaxCompilationRate);
}

@Override
Expand All @@ -208,11 +208,16 @@ private ScriptEngine getEngine(String lang) {
return scriptEngine;
}

void setMaxCompilationsPerMinute(Integer newMaxPerMinute) {
this.totalCompilesPerMinute = newMaxPerMinute;
/**
* This configures the maximum script compilations per five minute window.
*
* @param newRate the new expected maximum number of compilations per five minute window
*/
void setMaxCompilationRate(Integer newRate) {
this.totalCompilesPerFiveMinutes = newRate;
// Reset the counter to allow new compilations
this.scriptsPerMinCounter = totalCompilesPerMinute;
this.compilesAllowedPerNano = ((double) totalCompilesPerMinute) / TimeValue.timeValueMinutes(1).nanos();
this.scriptsPerFiveMinsCounter = totalCompilesPerFiveMinutes;
this.compilesAllowedPerNano = ((double) totalCompilesPerFiveMinutes) / TimeValue.timeValueMinutes(5).nanos();
}

/**
Expand Down Expand Up @@ -325,22 +330,22 @@ void checkCompilationLimit() {
long timePassed = now - lastInlineCompileTime;
lastInlineCompileTime = now;

scriptsPerMinCounter += (timePassed) * compilesAllowedPerNano;
scriptsPerFiveMinsCounter += (timePassed) * compilesAllowedPerNano;

// It's been over the time limit anyway, readjust the bucket to be level
if (scriptsPerMinCounter > totalCompilesPerMinute) {
scriptsPerMinCounter = totalCompilesPerMinute;
if (scriptsPerFiveMinsCounter > totalCompilesPerFiveMinutes) {
scriptsPerFiveMinsCounter = totalCompilesPerFiveMinutes;
}

// If there is enough tokens in the bucket, allow the request and decrease the tokens by 1
if (scriptsPerMinCounter >= 1) {
scriptsPerMinCounter -= 1.0;
if (scriptsPerFiveMinsCounter >= 1) {
scriptsPerFiveMinsCounter -= 1.0;
} else {
scriptMetrics.onCompilationLimit();
// Otherwise reject the request
throw new CircuitBreakingException("[script] Too many dynamic script compilations within one minute, max: [" +
totalCompilesPerMinute + "/min]; please use on-disk, indexed, or scripts with parameters instead; " +
"this limit can be changed by the [" + SCRIPT_MAX_COMPILATIONS_PER_MINUTE.getKey() + "] setting");
throw new CircuitBreakingException("[script] Too many dynamic script compilations within five minute window, max: [" +
totalCompilesPerFiveMinutes + "/min]; please use indexed, or scripts with parameters instead; " +
"this limit can be changed by the [" + SCRIPT_MAX_COMPILATIONS_RATE.getKey() + "] setting");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class ScriptServiceTests extends ESTestCase {
public void setup() throws IOException {
baseSettings = Settings.builder()
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
.put(ScriptService.SCRIPT_MAX_COMPILATIONS_PER_MINUTE.getKey(), 10000)
.put(ScriptService.SCRIPT_MAX_COMPILATIONS_RATE.getKey(), 10000)
.build();
Map<String, Function<Map<String, Object>, Object>> scripts = new HashMap<>();
for (int i = 0; i < 20; ++i) {
Expand All @@ -82,24 +82,26 @@ StoredScriptSource getScriptFromClusterState(String id) {
};
}

// even though circuit breaking is allowed to be configured per minute, we actually weigh this over five minutes
// simply by multiplying by five, so even setting it to one, requires five compilations to break
public void testCompilationCircuitBreaking() throws Exception {
buildScriptService(Settings.EMPTY);
scriptService.setMaxCompilationsPerMinute(1);
scriptService.setMaxCompilationRate(1);
scriptService.checkCompilationLimit(); // should pass
expectThrows(CircuitBreakingException.class, () -> scriptService.checkCompilationLimit());
scriptService.setMaxCompilationsPerMinute(2);
scriptService.setMaxCompilationRate(2);
scriptService.checkCompilationLimit(); // should pass
scriptService.checkCompilationLimit(); // should pass
expectThrows(CircuitBreakingException.class, () -> scriptService.checkCompilationLimit());
int count = randomIntBetween(5, 50);
scriptService.setMaxCompilationsPerMinute(count);
scriptService.setMaxCompilationRate(count);
for (int i = 0; i < count; i++) {
scriptService.checkCompilationLimit(); // should pass
}
expectThrows(CircuitBreakingException.class, () -> scriptService.checkCompilationLimit());
scriptService.setMaxCompilationsPerMinute(0);
scriptService.setMaxCompilationRate(0);
expectThrows(CircuitBreakingException.class, () -> scriptService.checkCompilationLimit());
scriptService.setMaxCompilationsPerMinute(Integer.MAX_VALUE);
scriptService.setMaxCompilationRate(Integer.MAX_VALUE);
int largeLimit = randomIntBetween(1000, 10000);
for (int i = 0; i < largeLimit; i++) {
scriptService.checkCompilationLimit();
Expand Down
2 changes: 1 addition & 1 deletion docs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ buildRestTests.expectedUnconvertedCandidates = [
]

integTestCluster {
setting 'script.max_compilations_per_minute', '1000'
setting 'script.max_compilations_rate', '1000'
/* Enable regexes in painless so our tests don't complain about example
* snippets that use them. */
setting 'script.painless.regex.enabled', 'true'
Expand Down
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 @@ -83,7 +83,7 @@ within a period of time.
See the "prefer-parameters" section of the <<modules-scripting-using,scripting>>
documentation for more information.

`script.max_compilations_per_minute`::
`script.max_compilations_rate`::

Limit for the number of unique dynamic scripts within a minute that are
allowed to be compiled. Defaults to 15.
Limit for the number of unique dynamic scripts within a five minute interval
that are allowed to be compiled. Defaults to 75.
2 changes: 1 addition & 1 deletion docs/reference/modules/scripting/using.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ If you compile too many unique scripts within a small amount of time,
Elasticsearch will reject the new dynamic scripts with a
`circuit_breaking_exception` error. By default, up to 15 inline scripts per
minute will be compiled. You can change this setting dynamically by setting
`script.max_compilations_per_minute`.
`script.max_compilations_rate`.

========================================

Expand Down
2 changes: 1 addition & 1 deletion modules/lang-expression/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ dependencyLicenses {
}

integTestCluster {
setting 'script.max_compilations_per_minute', '1000'
setting 'script.max_compilations_rate', '1000'
}
2 changes: 1 addition & 1 deletion modules/lang-mustache/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ dependencies {
}

integTestCluster {
setting 'script.max_compilations_per_minute', '1000'
setting 'script.max_compilations_rate', '1000'
}
2 changes: 1 addition & 1 deletion modules/lang-painless/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ test {
}

integTestCluster {
setting 'script.max_compilations_per_minute', '1000'
setting 'script.max_compilations_rate', '1000'
}

/* Build Javadoc for the Java classes in Painless's public API that are in the
Expand Down
2 changes: 1 addition & 1 deletion qa/smoke-test-ingest-with-all-dependencies/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ dependencies {

integTestCluster {
plugin ':plugins:ingest-geoip'
setting 'script.max_compilations_per_minute', '1000'
setting 'script.max_compilations_rate', '1000'
}
4 changes: 2 additions & 2 deletions qa/smoke-test-reindex-with-all-modules/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.rest-test'

integTestCluster {
setting 'script.max_compilations_per_minute', '1000'
setting 'script.max_compilations_rate', '1000'
// Whitelist reindexing from the local node so we can test it.
setting 'reindex.remote.whitelist', '127.0.0.1:*'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1694,7 +1694,7 @@ protected Settings nodeSettings(int nodeOrdinal) {
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_LOW_DISK_WATERMARK_SETTING.getKey(), "1b")
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK_SETTING.getKey(), "1b")
.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_FLOOD_STAGE_WATERMARK_SETTING.getKey(), "1b")
.put(ScriptService.SCRIPT_MAX_COMPILATIONS_PER_MINUTE.getKey(), 2048)
.put(ScriptService.SCRIPT_MAX_COMPILATIONS_RATE.getKey(), 2048)
// by default we never cache below 10k docs in a segment,
// bypass this limit so that caching gets some testing in
// integration tests that usually create few documents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ private Node newNode() {
// This needs to tie into the ESIntegTestCase#indexSettings() method
.put(Environment.PATH_SHARED_DATA_SETTING.getKey(), createTempDir().getParent())
.put("node.name", "node_s_0")
.put(ScriptService.SCRIPT_MAX_COMPILATIONS_PER_MINUTE.getKey(), 1000)
.put(ScriptService.SCRIPT_MAX_COMPILATIONS_RATE.getKey(), 1000)
.put(EsExecutors.PROCESSORS_SETTING.getKey(), 1) // limit the number of threads created
.put(NetworkModule.HTTP_ENABLED.getKey(), false)
.put("transport.type", getTestTransportType())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ public InternalTestCluster(long clusterSeed, Path baseDir,
builder.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_HIGH_DISK_WATERMARK_SETTING.getKey(), "1b");
builder.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_FLOOD_STAGE_WATERMARK_SETTING.getKey(), "1b");
// Some tests make use of scripting quite a bit, so increase the limit for integration tests
builder.put(ScriptService.SCRIPT_MAX_COMPILATIONS_PER_MINUTE.getKey(), 1000);
builder.put(ScriptService.SCRIPT_MAX_COMPILATIONS_RATE.getKey(), 1000);
if (TEST_NIGHTLY) {
builder.put(ThrottlingAllocationDecider.CLUSTER_ROUTING_ALLOCATION_NODE_CONCURRENT_INCOMING_RECOVERIES_SETTING.getKey(), RandomNumbers.randomIntBetween(random, 5, 10));
builder.put(ThrottlingAllocationDecider.CLUSTER_ROUTING_ALLOCATION_NODE_CONCURRENT_OUTGOING_RECOVERIES_SETTING.getKey(), RandomNumbers.randomIntBetween(random, 5, 10));
Expand Down