You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/08/26 01:34:58 UTC

[james-project] branch master updated: [BUILD_TIME] Slightly faster Cassandra startup (#1151)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new e2811cf0c8 [BUILD_TIME] Slightly faster Cassandra startup (#1151)
e2811cf0c8 is described below

commit e2811cf0c8b5f0fe8bbf178362db97d4a939bfc9
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Fri Aug 26 08:34:53 2022 +0700

    [BUILD_TIME] Slightly faster Cassandra startup (#1151)
    
    20% speed up measured locally by:
     - Using a single session for all resource creation
     - Closing it async
     - Creating keyspaces sequentially
     - Avoid creating the user twice
     - Using a faster check method than container exec
---
 .../backends/cassandra/init/ClusterFactory.java    | 28 +++++++++++-
 .../backends/cassandra/init/KeyspaceFactory.java   | 10 ++--
 .../backends/cassandra/CassandraWaitStrategy.java  | 19 +++++---
 .../james/backends/cassandra/DockerCassandra.java  | 53 ++++++++++++----------
 .../SessionWithInitializedTablesFactoryTest.java   |  2 +-
 .../mailbox/CassandraCacheSessionModule.java       |  2 +-
 .../modules/mailbox/CassandraSessionModule.java    |  2 +-
 7 files changed, 80 insertions(+), 36 deletions(-)

diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ClusterFactory.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ClusterFactory.java
index 6637df9815..a9b5a0223f 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ClusterFactory.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ClusterFactory.java
@@ -59,9 +59,35 @@ public class ClusterFactory {
         }
     }
 
+    public static CqlSession createWithoutKeyspace(ClusterConfiguration configuration) {
+        Preconditions.checkState(configuration.getUsername().isPresent() == configuration.getPassword().isPresent(), "If you specify username, you must specify password");
+
+        CqlSessionBuilder sessionBuilder = CqlSession.builder();
+
+        configuration.getHosts().forEach(server -> sessionBuilder
+            .addContactPoint(InetSocketAddress.createUnresolved(server.getHostName(), server.getPort())));
+
+
+        configuration.getUsername().ifPresent(username ->
+            configuration.getPassword().ifPresent(password ->
+                sessionBuilder.withAuthCredentials(username, password)));
+
+        sessionBuilder.withLocalDatacenter(configuration.getLocalDC().orElse("datacenter1"));
+
+        CqlSession session = sessionBuilder.build();
+
+        try {
+            ensureContactable(session);
+            return session;
+        } catch (Exception e) {
+            session.close();
+            throw e;
+        }
+    }
+
     private static void createKeyspace(KeyspaceConfiguration keyspaceConfiguration, CqlSessionBuilder sessionBuilder) {
         CqlSession cqlSession = sessionBuilder.build();
-        KeyspaceFactory.createKeyspace(keyspaceConfiguration, cqlSession);
+        KeyspaceFactory.createKeyspace(keyspaceConfiguration, cqlSession).block();
         cqlSession.forceCloseAsync();
     }
 
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/KeyspaceFactory.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/KeyspaceFactory.java
index a52029ec77..f14c178b8e 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/KeyspaceFactory.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/KeyspaceFactory.java
@@ -27,18 +27,22 @@ import com.datastax.oss.driver.api.querybuilder.SchemaBuilder;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 
+import reactor.core.publisher.Mono;
+
 public class KeyspaceFactory {
-    public static void createKeyspace(KeyspaceConfiguration configuration, CqlSession session) {
+    public static Mono<Void> createKeyspace(KeyspaceConfiguration configuration, CqlSession session) {
         if (!keyspaceExist(session, configuration.getKeyspace())) {
-            session.execute(SchemaBuilder.createKeyspace(configuration.getKeyspace())
+            return Mono.from(session.executeReactive(SchemaBuilder.createKeyspace(configuration.getKeyspace())
                 .ifNotExists()
                 .withReplicationOptions(ImmutableMap.<String, Object>builder()
                     .put("class", "SimpleStrategy")
                     .put("replication_factor", configuration.getReplicationFactor())
                     .build())
                 .withDurableWrites(configuration.isDurableWrites())
-                .build());
+                .build()))
+                .then();
         }
+        return Mono.empty();
     }
 
     @VisibleForTesting
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraWaitStrategy.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraWaitStrategy.java
index 43d81ae14b..3e56fdf756 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraWaitStrategy.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/CassandraWaitStrategy.java
@@ -19,10 +19,12 @@
 
 package org.apache.james.backends.cassandra;
 
-import java.io.IOException;
 import java.time.Duration;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.james.backends.cassandra.init.ClusterFactory;
+import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.util.Host;
 import org.rnorth.ducttape.unreliables.Unreliables;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.containers.wait.strategy.WaitStrategy;
@@ -49,11 +51,16 @@ public class CassandraWaitStrategy implements WaitStrategy {
     public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) {
         Unreliables.retryUntilTrue(Ints.checkedCast(timeout.getSeconds()), TimeUnit.SECONDS, () -> {
                 try {
-                    return cassandraContainer
-                        .execInContainer("cqlsh", "-u", "cassandra", "-p", "cassandra", "-e", "show host")
-                        .getStdout()
-                        .contains("Connected to Test Cluster");
-                } catch (IOException | InterruptedException e) {
+                    ClusterFactory.createWithoutKeyspace(ClusterConfiguration.builder()
+                        .host(Host.from(cassandraContainer.getContainerIpAddress(), cassandraContainer.getMappedPort(9042)))
+                        .username("cassandra")
+                        .password("cassandra")
+                        .maxRetry(1)
+                        .build())
+                        .forceCloseAsync();
+
+                    return true;
+                } catch (Exception e) {
                     return false;
                 }
             }
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandra.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandra.java
index 080119c1ca..a354c5e464 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandra.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/DockerCassandra.java
@@ -20,7 +20,6 @@
 package org.apache.james.backends.cassandra;
 
 import java.io.Closeable;
-import java.io.IOException;
 import java.util.Optional;
 import java.util.UUID;
 
@@ -45,6 +44,9 @@ import com.github.dockerjava.api.model.Event;
 import com.github.dockerjava.api.model.EventType;
 import com.google.common.collect.ImmutableMap;
 
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
 public class DockerCassandra {
 
     /**
@@ -54,35 +56,34 @@ public class DockerCassandra {
      * This process is done by using the default user provided by docker cassandra, it has the capability of creating roles,
      * keyspaces, and granting permissions to those entities.
      */
-    public static class CassandraResourcesManager {
-
+    public static class CassandraResourcesManager implements Closeable {
         private static final String CASSANDRA_SUPER_USER = "cassandra";
         private static final String CASSANDRA_SUPER_USER_PASSWORD = "cassandra";
 
-        private final DockerCassandra cassandra;
+        private final CqlSession privilegedCluster;
 
         private CassandraResourcesManager(DockerCassandra cassandra) {
-            this.cassandra = cassandra;
+            privilegedCluster = ClusterFactory.createWithoutKeyspace(cassandra.superUserConfigurationBuilder().build());
         }
 
-        public void initializeKeyspace(KeyspaceConfiguration configuration) {
-            try (CqlSession privilegedCluster = ClusterFactory.create(cassandra.superUserConfigurationBuilder().build(), configuration)) {
-                KeyspaceFactory.createKeyspace(configuration, privilegedCluster);
-                provisionNonPrivilegedUser(privilegedCluster);
-                grantPermissionToTestingUser(privilegedCluster, configuration.getKeyspace());
-            }
+        @Override
+        public void close() {
+            privilegedCluster.closeAsync();
         }
 
-        private void provisionNonPrivilegedUser(CqlSession privilegedCluster) {
-            privilegedCluster.execute("CREATE ROLE IF NOT EXISTS " + CASSANDRA_TESTING_USER + " WITH PASSWORD = '" + CASSANDRA_TESTING_PASSWORD + "' AND LOGIN = true");
+        public Mono<Void> initializeKeyspace(KeyspaceConfiguration configuration) {
+            return KeyspaceFactory.createKeyspace(configuration, privilegedCluster)
+                .then(grantPermissionToTestingUser(configuration.getKeyspace()));
         }
 
-        private void grantPermissionToTestingUser(CqlSession privilegedCluster, String keyspace) {
-            privilegedCluster.execute("GRANT CREATE ON KEYSPACE " + keyspace + " TO " + CASSANDRA_TESTING_USER);
-            privilegedCluster.execute("GRANT SELECT ON KEYSPACE " + keyspace + " TO " + CASSANDRA_TESTING_USER);
-            privilegedCluster.execute("GRANT MODIFY ON KEYSPACE " + keyspace + " TO " + CASSANDRA_TESTING_USER);
-            // some tests require dropping in setups
-            privilegedCluster.execute("GRANT DROP ON KEYSPACE " + keyspace + " TO " + CASSANDRA_TESTING_USER);
+        public Mono<Void> provisionNonPrivilegedUser() {
+            return Mono.from(privilegedCluster.executeReactive("CREATE ROLE IF NOT EXISTS " + CASSANDRA_TESTING_USER + " WITH PASSWORD = '" + CASSANDRA_TESTING_PASSWORD + "' AND LOGIN = true"))
+                .then();
+        }
+
+        private Mono<Void> grantPermissionToTestingUser(String keyspace) {
+            return Mono.from(privilegedCluster.executeReactive("GRANT ALL PERMISSIONS ON KEYSPACE " + keyspace + " TO " + CASSANDRA_TESTING_USER))
+                .then();
         }
     }
 
@@ -151,7 +152,7 @@ public class DockerCassandra {
 
             @Override
             public void onError(Throwable throwable) {
-                logger.error("event stream failure",throwable);
+                logger.error("event stream failure", throwable);
             }
 
             @Override
@@ -159,7 +160,7 @@ public class DockerCassandra {
             }
 
             @Override
-            public void close() throws IOException {
+            public void close() {
             }
         });
         boolean doNotDeleteImageAfterUsage = false;
@@ -194,8 +195,14 @@ public class DockerCassandra {
     public void start() {
         if (!cassandraContainer.isRunning()) {
             cassandraContainer.start();
-            administrator().initializeKeyspace(mainKeyspaceConfiguration());
-            administrator().initializeKeyspace(cacheKeyspaceConfiguration());
+            try (CassandraResourcesManager resourcesManager = administrator()) {
+                resourcesManager.provisionNonPrivilegedUser()
+                    .then(Flux.merge(
+                        resourcesManager.initializeKeyspace(mainKeyspaceConfiguration()),
+                        resourcesManager.initializeKeyspace(cacheKeyspaceConfiguration()))
+                        .then())
+                    .block();
+            }
         }
     }
 
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java
index b79c2dbc22..3c61094061 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java
@@ -129,7 +129,7 @@ class SessionWithInitializedTablesFactoryTest {
             .build();
         KeyspaceConfiguration keyspaceConfiguration = DockerCassandra.mainKeyspaceConfiguration();
         CqlSession cluster = ClusterFactory.create(clusterConfiguration, keyspaceConfiguration);
-        KeyspaceFactory.createKeyspace(keyspaceConfiguration, cluster);
+        KeyspaceFactory.createKeyspace(keyspaceConfiguration, cluster).block();
 
         return () -> new SessionWithInitializedTablesFactory( cluster, MODULE).get();
     }
diff --git a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraCacheSessionModule.java b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraCacheSessionModule.java
index 027d7b5c83..7d3cc250f3 100644
--- a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraCacheSessionModule.java
+++ b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraCacheSessionModule.java
@@ -76,7 +76,7 @@ public class CassandraCacheSessionModule extends AbstractModule {
             this.cluster = cluster;
 
             if (clusterConfiguration.shouldCreateKeyspace()) {
-                KeyspaceFactory.createKeyspace(keyspacesConfiguration.cacheKeyspaceConfiguration(), cluster);
+                KeyspaceFactory.createKeyspace(keyspacesConfiguration.cacheKeyspaceConfiguration(), cluster).block();
             }
         }
     }
diff --git a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraSessionModule.java b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraSessionModule.java
index c33be3c8b6..38bbc6dce8 100644
--- a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraSessionModule.java
+++ b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraSessionModule.java
@@ -166,7 +166,7 @@ public class CassandraSessionModule extends AbstractModule {
             this.cluster = sessionProvider.get();
 
             if (clusterConfiguration.shouldCreateKeyspace()) {
-                KeyspaceFactory.createKeyspace(keyspacesConfiguration.mainKeyspaceConfiguration(), cluster);
+                KeyspaceFactory.createKeyspace(keyspacesConfiguration.mainKeyspaceConfiguration(), cluster).block();
             }
         }
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org