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 2020/11/30 12:12:31 UTC

[james-project] 05/07: JAMES-3458 Limit Cassandra statements when retrieving all quota limits

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

commit 0320bd4a82d2b95ab70323a181345f3acfb26e96
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Nov 27 10:54:51 2020 +0700

    JAMES-3458 Limit Cassandra statements when retrieving all quota limits
    
    Fire 6 queries instead of 4: reads for count/size hits the same
    primary key for both user limits and domain limits, reading them in double is thus useless
    
    This optimisation do not benefit global quota that are hosted on different primary keys
---
 .../apache/james/mailbox/model/CurrentQuotas.java  |  4 ++
 .../quota/CassandraPerDomainMaxQuotaDao.java       | 34 ++++++++--------
 .../quota/CassandraPerUserMaxQuotaDao.java         | 33 +++++++++-------
 .../quota/CassandraPerUserMaxQuotaManager.java     | 37 ++++++++++++++++++
 .../james/mailbox/cassandra/quota/Limits.java}     | 45 ++++++++++++----------
 .../quota/CassandraPerUserMaxQuotaManagerTest.java | 15 ++++++++
 .../store/quota/GenericMaxQuotaManagerTest.java    |  4 +-
 7 files changed, 120 insertions(+), 52 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/CurrentQuotas.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/CurrentQuotas.java
index f6f0821..47d00af 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/CurrentQuotas.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/CurrentQuotas.java
@@ -25,6 +25,7 @@ import org.apache.james.core.quota.QuotaCountUsage;
 import org.apache.james.core.quota.QuotaSizeUsage;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
 
 public class CurrentQuotas {
     private final QuotaCountUsage count;
@@ -39,6 +40,9 @@ public class CurrentQuotas {
     }
 
     public CurrentQuotas(QuotaCountUsage count, QuotaSizeUsage size) {
+        Preconditions.checkNotNull(count);
+        Preconditions.checkNotNull(size);
+
         this.count = count;
         this.size = size;
     }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerDomainMaxQuotaDao.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerDomainMaxQuotaDao.java
index bc88058..c35ba1b 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerDomainMaxQuotaDao.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerDomainMaxQuotaDao.java
@@ -45,12 +45,10 @@ import com.datastax.driver.core.querybuilder.Select;
 import reactor.core.publisher.Mono;
 
 public class CassandraPerDomainMaxQuotaDao {
-
     private final CassandraAsyncExecutor queryExecutor;
     private final PreparedStatement setMaxStorageStatement;
     private final PreparedStatement setMaxMessageStatement;
-    private final PreparedStatement getMaxStorageStatement;
-    private final PreparedStatement getMaxMessageStatement;
+    private final PreparedStatement getMaxStatement;
     private final PreparedStatement removeMaxStorageStatement;
     private final PreparedStatement removeMaxMessageStatement;
 
@@ -59,8 +57,7 @@ public class CassandraPerDomainMaxQuotaDao {
         this.queryExecutor = new CassandraAsyncExecutor(session);
         this.setMaxStorageStatement = session.prepare(setMaxStorageStatement());
         this.setMaxMessageStatement = session.prepare(setMaxMessageStatement());
-        this.getMaxStorageStatement = session.prepare(getMaxStorageStatement());
-        this.getMaxMessageStatement = session.prepare(getMaxMessageStatement());
+        this.getMaxStatement = session.prepare(getMaxStatement());
         this.removeMaxStorageStatement = session.prepare(removeMaxStorageStatement());
         this.removeMaxMessageStatement = session.prepare(removeMaxMessageStatement());
     }
@@ -77,14 +74,8 @@ public class CassandraPerDomainMaxQuotaDao {
             .where(eq(CassandraDomainMaxQuota.DOMAIN, bindMarker()));
     }
 
-    private Select.Where getMaxMessageStatement() {
-        return select(CassandraDomainMaxQuota.MESSAGE_COUNT)
-            .from(CassandraDomainMaxQuota.TABLE_NAME)
-            .where(eq(CassandraDomainMaxQuota.DOMAIN, bindMarker()));
-    }
-
-    private Select.Where getMaxStorageStatement() {
-        return select(CassandraDomainMaxQuota.STORAGE)
+    private Select.Where getMaxStatement() {
+        return select()
             .from(CassandraDomainMaxQuota.TABLE_NAME)
             .where(eq(CassandraDomainMaxQuota.DOMAIN, bindMarker()));
     }
@@ -110,7 +101,7 @@ public class CassandraPerDomainMaxQuotaDao {
     }
 
     Mono<QuotaSizeLimit> getMaxStorage(Domain domain) {
-        return queryExecutor.executeSingleRow(getMaxStorageStatement.bind(domain.asString()))
+        return queryExecutor.executeSingleRow(getMaxStatement.bind(domain.asString()))
             .map(row -> Optional.ofNullable(row.get(CassandraDomainMaxQuota.STORAGE, Long.class)))
             .handle(publishIfPresent())
             .map(QuotaCodec::longToQuotaSize)
@@ -118,13 +109,26 @@ public class CassandraPerDomainMaxQuotaDao {
     }
 
     Mono<QuotaCountLimit> getMaxMessage(Domain domain) {
-        return queryExecutor.executeSingleRow(getMaxMessageStatement.bind(domain.asString()))
+        return queryExecutor.executeSingleRow(getMaxStatement.bind(domain.asString()))
             .map(row -> Optional.ofNullable(row.get(CassandraDomainMaxQuota.MESSAGE_COUNT, Long.class)))
             .handle(publishIfPresent())
             .map(QuotaCodec::longToQuotaCount)
             .handle(publishIfPresent());
     }
 
+    Mono<Limits> getLimits(Domain domain) {
+        return queryExecutor.executeSingleRow(getMaxStatement.bind(domain.asString()))
+            .map(row -> {
+                Optional<Long> sizeLimit = Optional.ofNullable(row.get(CassandraDomainMaxQuota.STORAGE, Long.class));
+                Optional<Long> countLimit = Optional.ofNullable(row.get(CassandraDomainMaxQuota.MESSAGE_COUNT, Long.class));
+
+                return new Limits(
+                    sizeLimit.flatMap(QuotaCodec::longToQuotaSize),
+                    countLimit.flatMap(QuotaCodec::longToQuotaCount));
+            })
+            .switchIfEmpty(Mono.just(Limits.empty()));
+    }
+
     Mono<Void> removeMaxMessage(Domain domain) {
         return queryExecutor.executeVoid(removeMaxMessageStatement.bind(domain.asString()));
     }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaDao.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaDao.java
index 9085623..0d7b8ff 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaDao.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaDao.java
@@ -49,8 +49,7 @@ public class CassandraPerUserMaxQuotaDao {
     private final CassandraAsyncExecutor queryExecutor;
     private final PreparedStatement setMaxStorageStatement;
     private final PreparedStatement setMaxMessageStatement;
-    private final PreparedStatement getMaxStorageStatement;
-    private final PreparedStatement getMaxMessageStatement;
+    private final PreparedStatement getMaxStatement;
     private final PreparedStatement removeMaxStorageStatement;
     private final PreparedStatement removeMaxMessageStatement;
 
@@ -59,8 +58,7 @@ public class CassandraPerUserMaxQuotaDao {
         this.queryExecutor = new CassandraAsyncExecutor(session);
         this.setMaxStorageStatement = session.prepare(setMaxStorageStatement());
         this.setMaxMessageStatement = session.prepare(setMaxMessageStatement());
-        this.getMaxStorageStatement = session.prepare(getMaxStorageStatement());
-        this.getMaxMessageStatement = session.prepare(getMaxMessageStatement());
+        this.getMaxStatement = session.prepare(getMaxStatement());
         this.removeMaxStorageStatement = session.prepare(removeMaxStorageStatement());
         this.removeMaxMessageStatement = session.prepare(removeMaxMessageStatement());
     }
@@ -77,14 +75,8 @@ public class CassandraPerUserMaxQuotaDao {
             .where(eq(CassandraMaxQuota.QUOTA_ROOT, bindMarker()));
     }
 
-    private Select.Where getMaxMessageStatement() {
-        return select(CassandraMaxQuota.MESSAGE_COUNT)
-            .from(CassandraMaxQuota.TABLE_NAME)
-            .where(eq(CassandraMaxQuota.QUOTA_ROOT, bindMarker()));
-    }
-
-    private Select.Where getMaxStorageStatement() {
-        return select(CassandraMaxQuota.STORAGE)
+    private Select.Where getMaxStatement() {
+        return select()
             .from(CassandraMaxQuota.TABLE_NAME)
             .where(eq(CassandraMaxQuota.QUOTA_ROOT, bindMarker()));
     }
@@ -110,7 +102,7 @@ public class CassandraPerUserMaxQuotaDao {
     }
 
     Mono<QuotaSizeLimit> getMaxStorage(QuotaRoot quotaRoot) {
-        return queryExecutor.executeSingleRow(getMaxStorageStatement.bind(quotaRoot.getValue()))
+        return queryExecutor.executeSingleRow(getMaxStatement.bind(quotaRoot.getValue()))
             .map(row -> Optional.ofNullable(row.get(CassandraMaxQuota.STORAGE, Long.class)))
             .handle(publishIfPresent())
             .map(QuotaCodec::longToQuotaSize)
@@ -118,13 +110,26 @@ public class CassandraPerUserMaxQuotaDao {
     }
 
     Mono<QuotaCountLimit> getMaxMessage(QuotaRoot quotaRoot) {
-        return queryExecutor.executeSingleRow(getMaxMessageStatement.bind(quotaRoot.getValue()))
+        return queryExecutor.executeSingleRow(getMaxStatement.bind(quotaRoot.getValue()))
             .map(row -> Optional.ofNullable(row.get(CassandraMaxQuota.MESSAGE_COUNT, Long.class)))
             .handle(publishIfPresent())
             .map(QuotaCodec::longToQuotaCount)
             .handle(publishIfPresent());
     }
 
+    Mono<Limits> getLimits(QuotaRoot quotaRoot) {
+        return queryExecutor.executeSingleRow(getMaxStatement.bind(quotaRoot.getValue()))
+            .map(row -> {
+                Optional<Long> sizeLimit = Optional.ofNullable(row.get(CassandraMaxQuota.STORAGE, Long.class));
+                Optional<Long> countLimit = Optional.ofNullable(row.get(CassandraMaxQuota.MESSAGE_COUNT, Long.class));
+
+                return new Limits(
+                    sizeLimit.flatMap(QuotaCodec::longToQuotaSize),
+                    countLimit.flatMap(QuotaCodec::longToQuotaCount));
+            })
+            .switchIfEmpty(Mono.just(Limits.empty()));
+    }
+
     Mono<Void> removeMaxMessage(QuotaRoot quotaRoot) {
         return queryExecutor.executeVoid(removeMaxMessageStatement.bind(quotaRoot.getValue()));
     }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManager.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManager.java
index 12d90c1..5fe0095 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManager.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManager.java
@@ -21,6 +21,8 @@ package org.apache.james.mailbox.cassandra.quota;
 
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
@@ -163,4 +165,39 @@ public class CassandraPerUserMaxQuotaManager implements MaxQuotaManager {
                 Pair::getValue))
             .block();
     }
+
+    @Override
+    public QuotaDetails quotaDetails(QuotaRoot quotaRoot) {
+        return Mono.zip(
+                perUserQuota.getLimits(quotaRoot),
+                Mono.justOrEmpty(quotaRoot.getDomain()).flatMap(perDomainQuota::getLimits).switchIfEmpty(Mono.just(Limits.empty())),
+                globalQuota.getGlobalMaxStorage().map(Optional::of).switchIfEmpty(Mono.just(Optional.empty())),
+                globalQuota.getGlobalMaxMessage().map(Optional::of).switchIfEmpty(Mono.just(Optional.empty())))
+            .map(tuple -> new QuotaDetails(
+                countDetails(tuple.getT1(), tuple.getT2(), tuple.getT4()),
+                sizeDetails(tuple.getT1(), tuple.getT2(), tuple.getT3())))
+            .block();
+    }
+
+    private Map<Quota.Scope, QuotaSizeLimit> sizeDetails(Limits userLimits, Limits domainLimits, Optional<QuotaSizeLimit> globalLimits) {
+        return Stream.of(
+                userLimits.getSizeLimit().stream().map(limit -> Pair.of(Quota.Scope.User, limit)),
+                domainLimits.getSizeLimit().stream().map(limit -> Pair.of(Quota.Scope.Domain, limit)),
+                globalLimits.stream().map(limit -> Pair.of(Quota.Scope.Global, limit)))
+            .flatMap(Function.identity())
+            .collect(Guavate.toImmutableMap(
+                Pair::getKey,
+                Pair::getValue));
+    }
+
+    private Map<Quota.Scope, QuotaCountLimit> countDetails(Limits userLimits, Limits domainLimits, Optional<QuotaCountLimit> globalLimits) {
+        return Stream.of(
+                userLimits.getCountLimit().stream().map(limit -> Pair.of(Quota.Scope.User, limit)),
+                domainLimits.getCountLimit().stream().map(limit -> Pair.of(Quota.Scope.Domain, limit)),
+                globalLimits.stream().map(limit -> Pair.of(Quota.Scope.Global, limit)))
+            .flatMap(Function.identity())
+            .collect(Guavate.toImmutableMap(
+                Pair::getKey,
+                Pair::getValue));
+    }
 }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/Limits.java
similarity index 54%
copy from mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
copy to mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/Limits.java
index b1117a0..3ef7aec 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/Limits.java
@@ -19,26 +19,29 @@
 
 package org.apache.james.mailbox.cassandra.quota;
 
-import org.apache.james.backends.cassandra.CassandraClusterExtension;
-import org.apache.james.backends.cassandra.components.CassandraModule;
-import org.apache.james.blob.cassandra.CassandraBlobModule;
-import org.apache.james.mailbox.cassandra.mail.utils.GuiceUtils;
-import org.apache.james.mailbox.cassandra.modules.CassandraQuotaModule;
-import org.apache.james.mailbox.quota.MaxQuotaManager;
-import org.apache.james.mailbox.store.quota.GenericMaxQuotaManagerTest;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-class CassandraPerUserMaxQuotaManagerTest extends GenericMaxQuotaManagerTest {
-
-    @RegisterExtension
-    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraModule.aggregateModules(
-        CassandraBlobModule.MODULE,
-        CassandraQuotaModule.MODULE));
-
-    @Override
-    protected MaxQuotaManager provideMaxQuotaManager() {
-        return GuiceUtils.testInjector(cassandraCluster.getCassandraCluster())
-            .getInstance(CassandraPerUserMaxQuotaManager.class);
+import java.util.Optional;
+
+import org.apache.james.core.quota.QuotaCountLimit;
+import org.apache.james.core.quota.QuotaSizeLimit;
+
+public class Limits {
+    public static Limits empty() {
+        return new Limits(Optional.empty(), Optional.empty());
+    }
+
+    private final Optional<QuotaSizeLimit> sizeLimit;
+    private final Optional<QuotaCountLimit> countLimit;
+
+    public Limits(Optional<QuotaSizeLimit> sizeLimit, Optional<QuotaCountLimit> countLimit) {
+        this.sizeLimit = sizeLimit;
+        this.countLimit = countLimit;
+    }
+
+    public Optional<QuotaSizeLimit> getSizeLimit() {
+        return sizeLimit;
     }
 
-}
+    public Optional<QuotaCountLimit> getCountLimit() {
+        return countLimit;
+    }
+}
\ No newline at end of file
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
index b1117a0..9159211 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
@@ -19,13 +19,18 @@
 
 package org.apache.james.mailbox.cassandra.quota;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.StatementRecorder;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
 import org.apache.james.mailbox.cassandra.mail.utils.GuiceUtils;
 import org.apache.james.mailbox.cassandra.modules.CassandraQuotaModule;
 import org.apache.james.mailbox.quota.MaxQuotaManager;
 import org.apache.james.mailbox.store.quota.GenericMaxQuotaManagerTest;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 class CassandraPerUserMaxQuotaManagerTest extends GenericMaxQuotaManagerTest {
@@ -41,4 +46,14 @@ class CassandraPerUserMaxQuotaManagerTest extends GenericMaxQuotaManagerTest {
             .getInstance(CassandraPerUserMaxQuotaManager.class);
     }
 
+    @Test
+    void quotaDetailsShouldGroupStatements(CassandraCluster cassandra) {
+        StatementRecorder statementRecorder = new StatementRecorder();
+        cassandra.getConf().recordStatements(statementRecorder);
+
+        maxQuotaManager.quotaDetails(QUOTA_ROOT);
+
+        assertThat(statementRecorder.listExecutedStatements()).hasSize(4);
+        // 1 statement for user limits, 1 for domain limits, 2 for global limits (as count and size don't share the same primary key)
+    }
 }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/GenericMaxQuotaManagerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/GenericMaxQuotaManagerTest.java
index dbc8e49..c3f467d 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/GenericMaxQuotaManagerTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/quota/GenericMaxQuotaManagerTest.java
@@ -36,8 +36,8 @@ public abstract class GenericMaxQuotaManagerTest {
 
     private static final Domain DOMAIN = Domain.of("domain");
     private static final Domain DOMAIN_CASE_VARIATION = Domain.of("doMain");
-    private static final QuotaRoot QUOTA_ROOT = QuotaRoot.quotaRoot("benwa@domain", Optional.of(DOMAIN));
-    private MaxQuotaManager maxQuotaManager;
+    protected static final QuotaRoot QUOTA_ROOT = QuotaRoot.quotaRoot("benwa@domain", Optional.of(DOMAIN));
+    protected MaxQuotaManager maxQuotaManager;
 
     protected abstract MaxQuotaManager provideMaxQuotaManager();
 


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