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 <subkan...@gmail.com> 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: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org