Repository: diennea/herddb (java)

Issue:
HerdDB Collections  - do not perform Files#createDirectories at every page write
Under heavy load (frequent index page writes) Files#createDirectories adds too much overhead on the FS.

We can simply ensure that the directory for an index or for a page exists at boot and fail in case is is removed during server execution.

Pull request description:
HerdDB Collections - do not perform Files#createDirectories at every page write
closes #418

Patch:
diff --git a/herddb-core/src/main/java/herddb/core/TableManager.java b/herddb-core/src/main/java/herddb/core/TableManager.java
index 309b5be1..f040c32f 100644
--- a/herddb-core/src/main/java/herddb/core/TableManager.java
+++ b/herddb-core/src/main/java/herddb/core/TableManager.java
@@ -463,8 +463,10 @@ public final class TableManager implements AbstractTableManager, Page.Owner {
     public void start() throws DataStorageManagerException {
 
         Map<Long, DataPageMetaData> activePagesAtBoot = new HashMap<>();
-
+        dataStorageManager.initTable(tableSpaceUUID, table.uuid);
+        keyToPage.init();
         bootSequenceNumber = LogSequenceNumber.START_OF_TIME;
+
         boolean requireLoadAtStartup = keyToPage.requireLoadAtStartup();
 
         if (requireLoadAtStartup) {
@@ -518,7 +520,6 @@ public final class TableManager implements AbstractTableManager, Page.Owner {
             bootSequenceNumber = tableStatus.sequenceNumber;
             activePagesAtBoot.putAll(tableStatus.activePages);
         }
-
         keyToPage.start(bootSequenceNumber);
 
         dataStorageManager.cleanupAfterBoot(tableSpaceUUID, table.uuid, activePagesAtBoot.keySet());
diff --git a/herddb-core/src/main/java/herddb/core/TableSpaceManager.java b/herddb-core/src/main/java/herddb/core/TableSpaceManager.java
index 0ee35455..0ff9206f 100644
--- a/herddb-core/src/main/java/herddb/core/TableSpaceManager.java
+++ b/herddb-core/src/main/java/herddb/core/TableSpaceManager.java
@@ -1434,7 +1434,7 @@ public class TableSpaceManager {
 
     private AbstractIndexManager bootIndex(Index index, AbstractTableManager tableManager, long transaction, boolean rebuild) throws DataStorageManagerException {
         long _start = System.currentTimeMillis();
-        LOGGER.log(Level.INFO, "bootIndex {0} {1}.{2}.{3} uuid {4}", new Object[]{nodeId, tableSpaceName, index.table, index.name, index.uuid});
+        LOGGER.log(Level.INFO, "bootIndex {0} {1}.{2}.{3} uuid {4} - {5}", new Object[]{nodeId, tableSpaceName, index.table, index.name, index.uuid, index.type});
         if (indexes.containsKey(index.name)) {
             throw new DataStorageManagerException("Index" + index.name + " already present in tableSpace " + tableSpaceName);
         }
diff --git a/herddb-core/src/main/java/herddb/file/FileDataStorageManager.java b/herddb-core/src/main/java/herddb/file/FileDataStorageManager.java
index e679e063..0b3511eb 100644
--- a/herddb-core/src/main/java/herddb/file/FileDataStorageManager.java
+++ b/herddb-core/src/main/java/herddb/file/FileDataStorageManager.java
@@ -238,6 +238,30 @@ public class FileDataStorageManager extends DataStorageManager {
         return filename != null && filename.toString().endsWith(EXTENSION_TABLEORINDExCHECKPOINTINFOFILE);
     }
 
+    @Override
+    public void initIndex(String tableSpace, String uuid) throws DataStorageManagerException {
+        Path indexDir = getIndexDirectory(tableSpace, uuid);
+        LOGGER.log(Level.FINE, "initIndex {0} {1} at {2}", new Object[] {tableSpace, uuid, indexDir} );
+        try {
+            Files.createDirectories(indexDir);
+        } catch (IOException err) {
+            throw new DataStorageManagerException(err);
+        }
+    }
+
+    @Override
+    public void initTable(String tableSpace, String uuid) throws DataStorageManagerException {
+        Path tableDir = getTableDirectory(tableSpace, uuid);
+        LOGGER.log(Level.FINE, "initTable {0} {1} at {3}", new Object[] {tableSpace, uuid, tableDir} );
+        try {
+            Files.createDirectories(tableDir);
+        } catch (IOException err) {
+            throw new DataStorageManagerException(err);
+        }
+    }
+
+
+
     @Override
     public List<Record> readPage(String tableSpace, String tableName, Long pageId) throws DataStorageManagerException, DataPageDoesNotExistException {
         long _start = System.currentTimeMillis();
@@ -546,7 +570,6 @@ public class FileDataStorageManager extends DataStorageManager {
         Path dir = getTableDirectory(tableSpace, tableName);
         Path checkpointFile = getTableCheckPointsFile(dir, logPosition);
         try {
-            Files.createDirectories(dir);
             if (Files.isRegularFile(checkpointFile)) {
                 TableStatus actualStatus = readTableStatusFromFile(checkpointFile);
                 if (actualStatus != null && actualStatus.equals(tableStatus)) {
@@ -629,17 +652,14 @@ public class FileDataStorageManager extends DataStorageManager {
         Path checkpointFile = getTableCheckPointsFile(dir, logPosition);
         Path parent = getParent(checkpointFile);
         Path checkpointFileTemp = parent.resolve(checkpointFile.getFileName() + ".tmp");
-        try {
-            Files.createDirectories(dir);
-            if (Files.isRegularFile(checkpointFile)) {
-                IndexStatus actualStatus = readIndexStatusFromFile(checkpointFile);
-                if (actualStatus != null && actualStatus.equals(indexStatus)) {
-                    LOGGER.log(Level.SEVERE, "indexCheckpoint " + tableSpace + ", " + indexName + ": " + indexStatus + " already saved on" + checkpointFile);
-                    return Collections.emptyList();
-                }
+
+        if (Files.isRegularFile(checkpointFile)) {
+            IndexStatus actualStatus = readIndexStatusFromFile(checkpointFile);
+            if (actualStatus != null && actualStatus.equals(indexStatus)) {
+                LOGGER.log(Level.SEVERE,
+                        "indexCheckpoint " + tableSpace + ", " + indexName + ": " + indexStatus + " already saved on" + checkpointFile);
+                return Collections.emptyList();
             }
-        } catch (IOException err) {
-            throw new DataStorageManagerException(err);
         }
 
         LOGGER.log(Level.FINE, "indexCheckpoint " + tableSpace + ", " + indexName + ": " + indexStatus + " to file " + checkpointFile);
@@ -754,11 +774,6 @@ public class FileDataStorageManager extends DataStorageManager {
 
     public List<Path> getTablePageFiles(String tableSpace, String tableName) throws DataStorageManagerException {
         Path tableDir = getTableDirectory(tableSpace, tableName);
-        try {
-            Files.createDirectories(tableDir);
-        } catch (IOException err) {
-            throw new DataStorageManagerException(err);
-        }
 
         try (DirectoryStream<Path> files = Files.newDirectoryStream(tableDir, new DirectoryStream.Filter<Path>() {
             @Override
@@ -777,11 +792,6 @@ public class FileDataStorageManager extends DataStorageManager {
 
     public List<Path> getIndexPageFiles(String tableSpace, String indexName) throws DataStorageManagerException {
         Path indexDir = getIndexDirectory(tableSpace, indexName);
-        try {
-            Files.createDirectories(indexDir);
-        } catch (IOException err) {
-            throw new DataStorageManagerException(err);
-        }
 
         try (DirectoryStream<Path> files = Files.newDirectoryStream(indexDir, new DirectoryStream.Filter<Path>() {
             @Override
@@ -856,11 +866,6 @@ public class FileDataStorageManager extends DataStorageManager {
         // synch on table is done by the TableManager
         long _start = System.currentTimeMillis();
         Path tableDir = getTableDirectory(tableSpace, tableName);
-        try {
-            Files.createDirectories(tableDir);
-        } catch (IOException err) {
-            throw new DataStorageManagerException(err);
-        }
         Path pageFile = getPageFile(tableDir, pageId);
         long size;
 
@@ -916,11 +921,7 @@ public class FileDataStorageManager extends DataStorageManager {
             long pageId, DataWriter writer) throws DataStorageManagerException {
         long _start = System.currentTimeMillis();
         Path tableDir = getIndexDirectory(tableSpace, indexName);
-        try {
-            Files.createDirectories(tableDir);
-        } catch (IOException err) {
-            throw new DataStorageManagerException(err);
-        }
+
         Path pageFile = getPageFile(tableDir, pageId);
         long size;
         try {
diff --git a/herddb-core/src/main/java/herddb/index/KeyToPageIndex.java b/herddb-core/src/main/java/herddb/index/KeyToPageIndex.java
index 0ac6f436..2dd89edf 100644
--- a/herddb-core/src/main/java/herddb/index/KeyToPageIndex.java
+++ b/herddb-core/src/main/java/herddb/index/KeyToPageIndex.java
@@ -41,9 +41,17 @@ public interface KeyToPageIndex extends AutoCloseable {
 
     public long getUsedMemory();
 
-    public boolean requireLoadAtStartup();
+    boolean requireLoadAtStartup();
 
-    public long size();
+    long size();
+
+    /**
+     * Initialize the index
+     *
+     * @throws DataStorageManagerException
+     */
+    default void init() throws DataStorageManagerException {
+    }
 
     void start(LogSequenceNumber sequenceNumber) throws DataStorageManagerException;
 
diff --git a/herddb-core/src/main/java/herddb/index/MemoryHashIndexManager.java b/herddb-core/src/main/java/herddb/index/MemoryHashIndexManager.java
index 2ce091e3..18ba7cc4 100644
--- a/herddb-core/src/main/java/herddb/index/MemoryHashIndexManager.java
+++ b/herddb-core/src/main/java/herddb/index/MemoryHashIndexManager.java
@@ -76,6 +76,8 @@ public class MemoryHashIndexManager extends AbstractIndexManager {
         LOGGER.log(Level.INFO, "loading in memory all the keys for mem index {0}", new Object[]{index.name});
         bootSequenceNumber = sequenceNumber;
 
+        dataStorageManager.initIndex(tableSpaceUUID, index.uuid);
+
         if (LogSequenceNumber.START_OF_TIME.equals(sequenceNumber)) {
             /* Empty index (booting from the start) */
             LOGGER.log(Level.INFO, "loaded empty index {0}", new Object[]{index.name});
@@ -129,6 +131,7 @@ public class MemoryHashIndexManager extends AbstractIndexManager {
     public void rebuild() throws DataStorageManagerException {
         long _start = System.currentTimeMillis();
         LOGGER.log(Level.INFO, "rebuilding index {0}", index.name);
+        dataStorageManager.initIndex(tableSpaceUUID, index.uuid);
         data.clear();
         Table table = tableManager.getTable();
         tableManager.scanForIndexRebuild(r -> {
diff --git a/herddb-core/src/main/java/herddb/index/blink/BLinkKeyToPageIndex.java b/herddb-core/src/main/java/herddb/index/blink/BLinkKeyToPageIndex.java
index bf0028e2..c70102ea 100644
--- a/herddb-core/src/main/java/herddb/index/blink/BLinkKeyToPageIndex.java
+++ b/herddb-core/src/main/java/herddb/index/blink/BLinkKeyToPageIndex.java
@@ -237,6 +237,11 @@ public class BLinkKeyToPageIndex implements KeyToPageIndex {
         return false;
     }
 
+    @Override
+    public void init() throws DataStorageManagerException {
+        dataStorageManager.initIndex(tableSpace, indexName);
+    }
+
     @Override
     public void start(LogSequenceNumber sequenceNumber) throws DataStorageManagerException {
 
diff --git a/herddb-core/src/main/java/herddb/index/brin/BRINIndexManager.java b/herddb-core/src/main/java/herddb/index/brin/BRINIndexManager.java
index 6f2ede6e..bc5fefe6 100644
--- a/herddb-core/src/main/java/herddb/index/brin/BRINIndexManager.java
+++ b/herddb-core/src/main/java/herddb/index/brin/BRINIndexManager.java
@@ -243,7 +243,10 @@ public class BRINIndexManager extends AbstractIndexManager {
 
     @Override
     protected boolean doStart(LogSequenceNumber sequenceNumber) throws DataStorageManagerException {
-        LOGGER.log(Level.SEVERE, " start index {0} uuid {1}", new Object[]{index.name, index.uuid});
+        LOGGER.log(Level.SEVERE, " start BRIN index {0} uuid {1}", new Object[]{index.name, index.uuid});
+
+        dataStorageManager.initIndex(tableSpaceUUID, index.uuid);
+
         bootSequenceNumber = sequenceNumber;
 
         if (LogSequenceNumber.START_OF_TIME.equals(sequenceNumber)) {
@@ -283,6 +286,7 @@ public class BRINIndexManager extends AbstractIndexManager {
     public void rebuild() throws DataStorageManagerException {
         long _start = System.currentTimeMillis();
         LOGGER.log(Level.INFO, "rebuilding index {0}", index.name);
+        dataStorageManager.initIndex(tableSpaceUUID, index.uuid);
         data.reset();
         Table table = tableManager.getTable();
         tableManager.scanForIndexRebuild(r -> {
diff --git a/herddb-core/src/main/java/herddb/mem/MemoryDataStorageManager.java b/herddb-core/src/main/java/herddb/mem/MemoryDataStorageManager.java
index 6f7ce4ab..524130c5 100644
--- a/herddb-core/src/main/java/herddb/mem/MemoryDataStorageManager.java
+++ b/herddb-core/src/main/java/herddb/mem/MemoryDataStorageManager.java
@@ -135,6 +135,14 @@ public class MemoryDataStorageManager extends DataStorageManager {
         }
     }
 
+    @Override
+    public void initIndex(String tableSpace, String indexName) throws DataStorageManagerException {
+    }
+
+    @Override
+    public void initTable(String tableSpace, String uuid) throws DataStorageManagerException {
+    }
+
     private static final Pattern LOG_SEQUENCE_PATTERN = Pattern.compile("(?<ledgerId>\\d+)\\.(?<offset>\\d+)");
 
     private LogSequenceNumber evaluateLogSequenceNumber(String string) {
diff --git a/herddb-core/src/main/java/herddb/storage/DataStorageManager.java b/herddb-core/src/main/java/herddb/storage/DataStorageManager.java
index c5b47f46..8c37216f 100644
--- a/herddb-core/src/main/java/herddb/storage/DataStorageManager.java
+++ b/herddb-core/src/main/java/herddb/storage/DataStorageManager.java
@@ -64,6 +64,10 @@ public abstract class DataStorageManager implements AutoCloseable {
     public abstract List<Record> readPage(String tableSpace, String uuid, Long pageId)
         throws DataStorageManagerException, DataPageDoesNotExistException;
 
+    public abstract void initIndex(String tableSpace, String uuid) throws DataStorageManagerException;
+    
+    public abstract void initTable(String tableSpace, String uuid) throws DataStorageManagerException;
+
     @FunctionalInterface
     public static interface DataReader<X> {
 


Test patch:
diff --git a/herddb-core/src/test/java/herddb/file/FileDataStorageManagerTest.java b/herddb-core/src/test/java/herddb/file/FileDataStorageManagerTest.java
index 6eec6ee3..2ed2e4f2 100644
--- a/herddb-core/src/test/java/herddb/file/FileDataStorageManagerTest.java
+++ b/herddb-core/src/test/java/herddb/file/FileDataStorageManagerTest.java
@@ -45,6 +45,7 @@ public class FileDataStorageManagerTest {
         try (FileDataStorageManager man = new FileDataStorageManager(folder.newFolder().toPath());) {
             List<Record> page = Arrays.asList(
                 new Record(Bytes.from_int(1), Bytes.from_int(2)));
+            man.initTable("test1", "table1");
             man.writePage("test1", "table1", 1L, page);
             List<Record> result = man.readPage("test1", "table1", 1L);
             assertEquals(result, page);
@@ -55,6 +56,7 @@ public class FileDataStorageManagerTest {
     public void testReadWriteIndexPage() throws Exception {
         try (FileDataStorageManager man = new FileDataStorageManager(folder.newFolder().toPath());) {
             byte[] page = Bytes.intToByteArray(1);
+            man.initIndex("test1", "table1");
             man.writeIndexPage("test1", "table1", 1L, (out) -> {
                 out.writeArray(page);
             });