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

Reply via email to