This is an automated email from the ASF dual-hosted git repository.
paulo pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new 9b291f1 Add startup check for read_ahead_kb setting
9b291f1 is described below
commit 9b291f18abfc62ab45e725effe75a8ceb9163760
Author: Kanthi Subramanian <[email protected]>
AuthorDate: Sun Dec 5 13:07:54 2021 -0500
Add startup check for read_ahead_kb setting
Patch by Kanthi Subramanian; Reviewed by Paulo Motta and Brandon Williams
for CASSANDRA-16436
Closes #1354
---
CHANGES.txt | 1 +
.../apache/cassandra/service/StartupChecks.java | 110 +++++++++++++++++++++
.../cassandra/service/StartupChecksTest.java | 25 +++++
3 files changed, 136 insertions(+)
diff --git a/CHANGES.txt b/CHANGES.txt
index f6918d9..eb773af 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
4.1
+ * Added startup check for read_ahead_kb setting (CASSANDRA-16436)
* Avoid unecessary array allocations and initializations when performing
query checks (CASSANDRA-17209)
* Add guardrail for list operations that require read before write
(CASSANDRA-17154)
* Migrate thresholds for number of keyspaces and tables to guardrails
(CASSANDRA-17195)
diff --git a/src/java/org/apache/cassandra/service/StartupChecks.java
b/src/java/org/apache/cassandra/service/StartupChecks.java
index b8fc082..0758dbc 100644
--- a/src/java/org/apache/cassandra/service/StartupChecks.java
+++ b/src/java/org/apache/cassandra/service/StartupChecks.java
@@ -31,7 +31,10 @@ import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import org.apache.commons.lang3.StringUtils;
+
import org.apache.cassandra.io.util.File;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -98,6 +101,7 @@ public class StartupChecks
checkNativeLibraryInitialization,
initSigarLibrary,
checkMaxMapCount,
+
checkReadAheadKbSetting,
checkDataDirs,
checkSSTablesFormat,
checkSystemKeyspaceState,
@@ -285,6 +289,86 @@ public class StartupChecks
}
};
+ public static final StartupCheck checkReadAheadKbSetting = new
StartupCheck()
+ {
+ // This value is in KB.
+ private static final long MAX_RECOMMENDED_READ_AHEAD_KB_SETTING = 128;
+
+ /**
+ * Function to get the block device system path(Example: /dev/sda)
from the
+ * data directories defined in cassandra config.(cassandra.yaml)
+ * @param dataDirectories list of data directories from cassandra.yaml
+ * @return Map of block device path and data directory
+ */
+ private Map<String, String> getBlockDevices(String[] dataDirectories) {
+ Map<String, String> blockDevices = new HashMap<String, String>();
+
+ for (String dataDirectory : dataDirectories)
+ {
+ try
+ {
+ Path p = Paths.get(dataDirectory);
+ FileStore fs = Files.getFileStore(p);
+
+ String blockDirectory = fs.name();
+ if(StringUtils.isNotEmpty(blockDirectory))
+ {
+ blockDevices.put(blockDirectory, dataDirectory);
+ }
+ }
+ catch (IOException e)
+ {
+ logger.warn("IO exception while reading file {}.",
dataDirectory, e);
+ }
+ }
+ return blockDevices;
+ }
+
+ @Override
+ public void execute()
+ {
+ if (!FBUtilities.isLinux)
+ return;
+
+ String[] dataDirectories =
DatabaseDescriptor.getRawConfig().data_file_directories;
+ Map<String, String> blockDevices =
getBlockDevices(dataDirectories);
+
+ for (Map.Entry<String, String> entry: blockDevices.entrySet())
+ {
+ String blockDeviceDirectory = entry.getKey();
+ String dataDirectory = entry.getValue();
+ try
+ {
+ Path readAheadKBPath =
StartupChecks.getReadAheadKBPath(blockDeviceDirectory);
+
+ if (readAheadKBPath == null ||
Files.notExists(readAheadKBPath))
+ {
+ logger.debug("No 'read_ahead_kb' setting found for
device {} of data directory {}.", blockDeviceDirectory, dataDirectory);
+ continue;
+ }
+
+ final List<String> data =
Files.readAllLines(readAheadKBPath);
+ if (data.isEmpty())
+ continue;
+
+ int readAheadKbSetting = Integer.parseInt(data.get(0));
+
+ if (readAheadKbSetting >
MAX_RECOMMENDED_READ_AHEAD_KB_SETTING)
+ {
+ logger.warn("Detected high '{}' setting of {} for
device '{}' of data directory '{}'. It is " +
+ "recommended to set this value to 8KB (or
lower) on SSDs or 64KB (or lower) on HDDs " +
+ "to prevent excessive IO usage and page
cache churn on read-intensive workloads.",
+ readAheadKBPath, readAheadKbSetting,
blockDeviceDirectory, dataDirectory);
+ }
+ }
+ catch (final IOException e)
+ {
+ logger.warn("IO exception while reading file {}.",
blockDeviceDirectory, e);
+ }
+ }
+ }
+ };
+
public static final StartupCheck checkMaxMapCount = new StartupCheck()
{
private final long EXPECTED_MAX_MAP_COUNT = 1048575;
@@ -499,6 +583,32 @@ public class StartupChecks
};
@VisibleForTesting
+ public static Path getReadAheadKBPath(String blockDirectoryPath)
+ {
+ Path readAheadKBPath = null;
+
+ final String READ_AHEAD_KB_SETTING_PATH =
"/sys/block/%s/queue/read_ahead_kb";
+ try
+ {
+ String[] blockDirComponents = blockDirectoryPath.split("/");
+ if (blockDirComponents.length >= 2 &&
blockDirComponents[1].equals("dev"))
+ {
+ String deviceName =
blockDirComponents[2].replaceAll("[0-9]*$", "");
+ if (StringUtils.isNotEmpty(deviceName))
+ {
+ readAheadKBPath =
Paths.get(String.format(READ_AHEAD_KB_SETTING_PATH, deviceName));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ logger.error("Error retrieving device path for {}.",
blockDirectoryPath);
+ }
+
+ return readAheadKBPath;
+ }
+
+ @VisibleForTesting
static Optional<String> checkLegacyAuthTablesMessage()
{
List<String> existing = new
ArrayList<>(SchemaConstants.LEGACY_AUTH_TABLES).stream().filter((legacyAuthTable)
->
diff --git a/test/unit/org/apache/cassandra/service/StartupChecksTest.java
b/test/unit/org/apache/cassandra/service/StartupChecksTest.java
index 56cd089..a422a42 100644
--- a/test/unit/org/apache/cassandra/service/StartupChecksTest.java
+++ b/test/unit/org/apache/cassandra/service/StartupChecksTest.java
@@ -104,6 +104,31 @@ public class StartupChecksTest
}
@Test
+ public void checkReadAheadKbSettingCheck() throws Exception
+ {
+ // This test just validates if the verify function
+ // doesn't throw any exceptions
+ startupChecks =
startupChecks.withTest(StartupChecks.checkReadAheadKbSetting);
+ startupChecks.verify();
+ }
+
+ @Test
+ public void testGetReadAheadKBPath()
+ {
+ Path sdaDirectory = StartupChecks.getReadAheadKBPath("/dev/sda12");
+ Assert.assertEquals(Paths.get("/sys/block/sda/queue/read_ahead_kb"),
sdaDirectory);
+
+ Path scsiDirectory = StartupChecks.getReadAheadKBPath("/dev/scsi1");
+ Assert.assertEquals(Paths.get("/sys/block/scsi/queue/read_ahead_kb"),
scsiDirectory);
+
+ Path dirWithoutNumbers = StartupChecks.getReadAheadKBPath("/dev/sca");
+ Assert.assertEquals(Paths.get("/sys/block/sca/queue/read_ahead_kb"),
dirWithoutNumbers);
+
+ Path invalidDir = StartupChecks.getReadAheadKBPath("/tmp/xpto");
+ Assert.assertNull(invalidDir);
+ }
+
+ @Test
public void maxMapCountCheck() throws Exception
{
startupChecks = startupChecks.withTest(StartupChecks.checkMaxMapCount);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]