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:
parent
aa510159c2
commit
05b4e0c0e3
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue