[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [akonadi] server/src: Disable filesystem Copy-On-Write on database files when running on Btrfs
From:       Dan_Vrátil <dvratil () redhat ! com>
Date:       2014-05-27 19:28:21
Message-ID: E1WpN2r-0005WO-R3 () scm ! kde ! org
[Download RAW message or body]

Git commit a307fb6e1a288a92fcd79a44b016004a20d7adfc by Dan Vrátil.
Committed on 27/05/2014 at 17:45.
Pushed by dvratil into branch 'master'.

Disable filesystem Copy-On-Write on database files when running on Btrfs

Btrfs documentation recommends disabling Copy-on-Write for databases (or any
large files with small random writes in general) in order to improve performance.

This patch adds a code to detect what filesystem ~/.local/share/akonadi/ is
stored on, and if Btrfs is detected, it will disable COW on this directory (or
on the akonadi.db file in case of SQLite) using an ioctl call.

When CoW is disabled on non-empty directory, only newly created files will be
affected. In case of non-empty files, it's not defined when it will take effect.
For this reason the code is only executed before initializing a new database,
which means that only new users will be affected.

Obviously this feature only works on Linux and the code is disabled on any other
platform.

M  +12   -0    server/src/storage/dbconfigmysql.cpp
M  +10   -0    server/src/storage/dbconfigpostgresql.cpp
M  +13   -0    server/src/storage/dbconfigsqlite.cpp
M  +82   -0    server/src/utils.cpp
M  +17   -0    server/src/utils.h

http://commits.kde.org/akonadi/a307fb6e1a288a92fcd79a44b016004a20d7adfc

diff --git a/server/src/storage/dbconfigmysql.cpp b/server/src/storage/dbconfigmysql.cpp
index a36c3c7..10f99db 100644
--- a/server/src/storage/dbconfigmysql.cpp
+++ b/server/src/storage/dbconfigmysql.cpp
@@ -195,6 +195,18 @@ void DbConfigMysql::startInternalServer()
     akFatal() << "Did not find MySQL server default configuration (mysql-global.conf)";
   }
 
+#ifdef Q_OS_LINUX
+  // It is recommended to disable CoW feature when running on Btrfs to improve
+  // database performance. Disabling CoW only has effect on empty directory (since
+  // it affects only new files), so we check whether MySQL has not yet been initialized.
+  QDir dir(dataDir + QDir::separator() + QLatin1String("mysql"));
+  if (!dir.exists()) {
+    if (Utils::getDirectoryFileSystem(dataDir) == QLatin1String("btrfs")) {
+        Utils::disableCoW(dataDir);
+    }
+  }
+#endif
+
   bool confUpdate = false;
   QFile actualFile ( actualConfig );
   // update conf only if either global (or local) is newer than actual
diff --git a/server/src/storage/dbconfigpostgresql.cpp b/server/src/storage/dbconfigpostgresql.cpp
index d94723d..ebad22c 100644
--- a/server/src/storage/dbconfigpostgresql.cpp
+++ b/server/src/storage/dbconfigpostgresql.cpp
@@ -203,6 +203,16 @@ void DbConfigPostgresql::startInternalServer()
 #endif
 
   if ( !QFile::exists( QString::fromLatin1( "%1/PG_VERSION" ).arg( mPgData ) ) ) {
+
+    #ifdef Q_OS_LINUX
+    // It is recommended to disable CoW feature when running on Btrfs to improve
+    // database performance. This only has effect when done on empty directory,
+    // so we only call this before calling initdb
+    if (Utils::getDirectoryFileSystem(mPgData) == QLatin1String("btrfs")) {
+        Utils::disableCoW(mPgData);
+    }
+#endif
+
     // postgres data directory not initialized yet, so call initdb on it
 
     // call 'initdb --pgdata=/home/user/.local/share/akonadi/data_db'
diff --git a/server/src/storage/dbconfigsqlite.cpp b/server/src/storage/dbconfigsqlite.cpp
index 5fa46b9..39b52b8 100644
--- a/server/src/storage/dbconfigsqlite.cpp
+++ b/server/src/storage/dbconfigsqlite.cpp
@@ -18,6 +18,7 @@
 */
 
 #include "dbconfigsqlite.h"
+#include "utils.h"
 
 #include <libs/xdgbasedirs_p.h>
 #include <akdebug.h>
@@ -148,6 +149,18 @@ void DbConfigSqlite::setup()
     dir.mkpath(finfo.path());
   }
 
+#ifdef Q_OS_LINUX
+  QFile dbFile(mDatabaseName);
+  // It is recommended to disable CoW feature when running on Btrfs to improve
+  // database performance. It does not have any effect on non-empty files, so
+  // we check, whether the database has not yet been initialized.
+  if (dbFile.size() == 0) {
+    if (Utils::getDirectoryFileSystem(mDatabaseName) == QLatin1String("btrfs")) {
+        Utils::disableCoW(mDatabaseName);
+    }
+  }
+#endif
+
   db.setDatabaseName( mDatabaseName );
   if ( !db.open() ) {
     akDebug() << "Could not open sqlite database "
diff --git a/server/src/utils.cpp b/server/src/utils.cpp
index cb0e091..b04a812 100644
--- a/server/src/utils.cpp
+++ b/server/src/utils.cpp
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2010 Tobias Koenig <tokoe@kde.org>
+ * Copyright (C) 2014 Daniel Vrátil <dvratil@redhat.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -20,6 +21,7 @@
 
 #include "utils.h"
 
+#include <akdebug.h>
 #include <akstandarddirs.h>
 #include <libs/xdgbasedirs_p.h>
 
@@ -41,6 +43,13 @@ static bool checkSocketDirectory( const QString &path );
 static bool createSocketDirectory( const QString &link, const QString &tmpl );
 #endif
 
+#ifdef Q_OS_LINUX
+#include <stdio.h>
+#include <mntent.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#endif
+
 using namespace Akonadi;
 using namespace Akonadi::Server;
 
@@ -160,3 +169,76 @@ static bool createSocketDirectory( const QString &link, const QString &tmpl )
   return true;
 }
 #endif
+
+QString Utils::getDirectoryFileSystem(const QString &directory)
+{
+#ifndef Q_OS_LINUX
+    return QString();
+#else
+    QString bestMatchPath;
+    QString bestMatchFS;
+
+    FILE *mtab = setmntent("/etc/mtab", "r");
+    while (mntent *mnt = getmntent(mtab)) {
+        if (qstrcmp(mnt->mnt_type, MNTTYPE_IGNORE) == 0) {
+            continue;
+        }
+
+        const QString dir = QString::fromLocal8Bit(mnt->mnt_dir);
+        if (!directory.startsWith(dir) || dir.length() < bestMatchPath.length()) {
+            continue;
+        }
+
+        bestMatchPath = dir;
+        bestMatchFS = QString::fromLocal8Bit(mnt->mnt_type);
+    }
+
+    endmntent(mtab);
+
+    return bestMatchFS;
+#endif
+}
+
+void Utils::disableCoW(const QString& path)
+{
+#ifndef Q_OS_LINUX
+    Q_UNUSED(path);
+#else
+    qDebug() << "Detected Btrfs, disabling copy-on-write on database files";
+
+    // from linux/fs.h, so that Akonadi does not depend on Linux header files
+    #ifndef FS_IOC_GETFLAGS
+    #define FS_IOC_GETFLAGS     _IOR('f', 1, long)
+    #endif
+    #ifndef FS_IOC_SETFLAGS
+    #define FS_IOC_SETFLAGS     _IOW('f', 2, long)
+    #endif
+
+    // Disable COW on file
+    #ifndef FS_NOCOW_FL
+    #define FS_NOCOW_FL         0x00800000
+    #endif
+
+    ulong flags = 0;
+    const int fd = open(qPrintable(path), O_RDONLY);
+    if (fd == -1) {
+        qWarning() << "Failed to open" << path << "to modify flags (" << errno << ")";
+        return;
+    }
+
+    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
+        qWarning() << "ioctl error: failed to get file flags (" << errno << ")";
+        close(fd);
+        return;
+    }
+    if (!(flags & FS_NOCOW_FL)) {
+        flags |= FS_NOCOW_FL;
+        if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) {
+            qWarning() << "ioctl error: failed to set file flags (" << errno << ")";
+            close(fd);
+            return;
+        }
+    }
+    close(fd);
+#endif
+}
diff --git a/server/src/utils.h b/server/src/utils.h
index 6cb8e2f..97e1fb5 100644
--- a/server/src/utils.h
+++ b/server/src/utils.h
@@ -65,6 +65,23 @@ static inline QByteArray variantToByteArray( const QVariant &variant )
  */
 QString preferredSocketDirectory( const QString &directory );
 
+/**
+ * Returns name of filesystem that @p directory is stored on. This
+ * only works on Linux and returns empty string on other platforms or when it's
+ * unable to detect the filesystem.
+ */
+QString getDirectoryFileSystem(const QString &directory);
+
+/**
+ * Disables filesystem copy-on-write feature on given file or directory.
+ * Only works on Linux and does nothing on other platforms.
+ *
+ * It was tested only with Btrfs but in theory can be called on any FS that
+ * supports NOCOW.
+ */
+void disableCoW(const QString &path);
+
+
 } // namespace Utils
 } // namespace Server
 } // namespace Akonadi
[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic