Add serial collector bootstrap check

The serial collector is not suitable for running with a server
application like Elasticsearch and can decimate performance and lead to
cluster instability. This commit adds a bootstrap check to prevent usage
of the serial collector when Elasticsearch is running in production
mode.

Relates #20558
This commit is contained in:
Jason Tedor 2016-09-19 20:25:50 -04:00 committed by GitHub
parent aa510159c2
commit 05b4e0c0e3
4 changed files with 87 additions and 5 deletions

View File

@ -23,8 +23,6 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.apache.lucene.util.Constants;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.logging.Loggers;
@ -178,6 +176,7 @@ final class BootstrapCheck {
checks.add(new MaxMapCountCheck());
}
checks.add(new ClientJvmCheck());
checks.add(new UseSerialGCCheck());
checks.add(new OnErrorCheck());
checks.add(new OnOutOfMemoryErrorCheck());
return Collections.unmodifiableList(checks);
@ -500,6 +499,38 @@ final class BootstrapCheck {
}
/**
* Checks if the serial collector is in use. This collector is single-threaded and devastating
* for performance and should not be used for a server application like Elasticsearch.
*/
static class UseSerialGCCheck implements BootstrapCheck.Check {
@Override
public boolean check() {
return getUseSerialGC().equals("true");
}
// visible for testing
String getUseSerialGC() {
return JvmInfo.jvmInfo().useSerialGC();
}
@Override
public String errorMessage() {
return String.format(
Locale.ROOT,
"JVM is using the serial collector but should not be for the best performance; " +
"either it's the default for the VM [%s] or -XX:+UseSerialGC was explicitly specified",
JvmInfo.jvmInfo().getVmName());
}
@Override
public boolean isSystemCheck() {
return false;
}
}
abstract static class MightForkCheck implements BootstrapCheck.Check {
@Override

View File

@ -103,6 +103,7 @@ public class JvmInfo implements Writeable, ToXContent {
String onOutOfMemoryError = null;
String useCompressedOops = "unknown";
String useG1GC = "unknown";
String useSerialGC = "unknown";
long configuredInitialHeapSize = -1;
long configuredMaxHeapSize = -1;
try {
@ -148,6 +149,13 @@ public class JvmInfo implements Writeable, ToXContent {
configuredMaxHeapSize = Long.parseLong((String) valueMethod.invoke(maxHeapSizeVmOptionObject));
} catch (Exception ignored) {
}
try {
Object useSerialGCVmOptionObject = vmOptionMethod.invoke(hotSpotDiagnosticMXBean, "UseSerialGC");
useSerialGC = (String) valueMethod.invoke(useSerialGCVmOptionObject);
} catch (Exception ignored) {
}
} catch (Exception ignored) {
}
@ -155,7 +163,7 @@ public class JvmInfo implements Writeable, ToXContent {
INSTANCE = new JvmInfo(pid, System.getProperty("java.version"), runtimeMXBean.getVmName(), runtimeMXBean.getVmVersion(),
runtimeMXBean.getVmVendor(), runtimeMXBean.getStartTime(), configuredInitialHeapSize, configuredMaxHeapSize,
mem, inputArguments, bootClassPath, classPath, systemProperties, gcCollectors, memoryPools, onError, onOutOfMemoryError,
useCompressedOops, useG1GC);
useCompressedOops, useG1GC, useSerialGC);
}
public static JvmInfo jvmInfo() {
@ -186,11 +194,12 @@ public class JvmInfo implements Writeable, ToXContent {
private final String onOutOfMemoryError;
private final String useCompressedOops;
private final String useG1GC;
private final String useSerialGC;
private JvmInfo(long pid, String version, String vmName, String vmVersion, String vmVendor, long startTime,
long configuredInitialHeapSize, long configuredMaxHeapSize, Mem mem, String[] inputArguments, String bootClassPath,
String classPath, Map<String, String> systemProperties, String[] gcCollectors, String[] memoryPools, String onError,
String onOutOfMemoryError, String useCompressedOops, String useG1GC) {
String onOutOfMemoryError, String useCompressedOops, String useG1GC, String useSerialGC) {
this.pid = pid;
this.version = version;
this.vmName = vmName;
@ -210,6 +219,7 @@ public class JvmInfo implements Writeable, ToXContent {
this.onOutOfMemoryError = onOutOfMemoryError;
this.useCompressedOops = useCompressedOops;
this.useG1GC = useG1GC;
this.useSerialGC = useSerialGC;
}
public JvmInfo(StreamInput in) throws IOException {
@ -230,12 +240,13 @@ public class JvmInfo implements Writeable, ToXContent {
gcCollectors = in.readStringArray();
memoryPools = in.readStringArray();
useCompressedOops = in.readString();
//the following members are only used locally for boostrap checks, never serialized nor printed out
//the following members are only used locally for bootstrap checks, never serialized nor printed out
this.configuredMaxHeapSize = -1;
this.configuredInitialHeapSize = -1;
this.onError = null;
this.onOutOfMemoryError = null;
this.useG1GC = "unknown";
this.useSerialGC = "unknown";
}
@Override
@ -415,6 +426,10 @@ public class JvmInfo implements Writeable, ToXContent {
return this.useG1GC;
}
public String useSerialGC() {
return this.useSerialGC;
}
public String[] getGcCollectors() {
return gcCollectors;
}

View File

@ -24,6 +24,7 @@ import org.apache.lucene.util.Constants;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.BoundTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.monitor.jvm.JvmInfo;
import org.elasticsearch.node.NodeValidationException;
import org.elasticsearch.test.ESTestCase;
@ -407,6 +408,27 @@ public class BootstrapCheckTests extends ESTestCase {
BootstrapCheck.check(true, false, Collections.singletonList(check), "testClientJvmCheck");
}
public void testUseSerialGCCheck() throws NodeValidationException {
final AtomicReference<String> useSerialGC = new AtomicReference<>("true");
final BootstrapCheck.Check check = new BootstrapCheck.UseSerialGCCheck() {
@Override
String getUseSerialGC() {
return useSerialGC.get();
}
};
final NodeValidationException e = expectThrows(
NodeValidationException.class,
() -> BootstrapCheck.check(true, false, Collections.singletonList(check), "testUseSerialGCCheck"));
assertThat(
e.getMessage(),
containsString("JVM is using the serial collector but should not be for the best performance; " + "" +
"either it's the default for the VM [" + JvmInfo.jvmInfo().getVmName() +"] or -XX:+UseSerialGC was explicitly specified"));
useSerialGC.set("false");
BootstrapCheck.check(true, false, Collections.singletonList(check), "testUseSerialGCCheck");
}
public void testMightForkCheck() throws NodeValidationException {
final AtomicBoolean isSeccompInstalled = new AtomicBoolean();
final AtomicBoolean mightFork = new AtomicBoolean();

View File

@ -127,6 +127,20 @@ systems and operating systems, the server VM is the
default. Additionally, Elasticsearch is configured by default to force
the server VM.
=== Use serial collector check
There are various garbage collectors for the OpenJDK-derived JVMs targeting
different workloads. The serial collector in particular is best suited for
single logical CPU machines or extremely small heaps, neither of which are
suitable for running Elasticsearch. Using the serial collector with
Elasticsearch can be devastating for performance. The serial collector check
ensures that Elasticsearch is not configured to run with the serial
collector. To pass the serial collector check, you must not start Elasticsearch
with the serial collector (whether it's from the defaults for the JVM that
you're using, or you've explicitly specified it with `-XX:+UseSerialGC`). Note
that the default JVM configuration that ship with Elasticsearch configures
Elasticsearch to use the CMS collector.
=== OnError and OnOutOfMemoryError checks
The JVM options `OnError` and `OnOutOfMemoryError` enable executing