You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2023/11/10 02:47:27 UTC

(james-project) branch master updated: JAMES-3960 Hints to ensure UID/ModSeq consistency in case of disaster

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

rcordier 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 8d95aec8f4 JAMES-3960 Hints to ensure UID/ModSeq consistency in case of disaster
8d95aec8f4 is described below

commit 8d95aec8f4bf9448b819c14d94ffc706c5997bbc
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Wed Nov 8 18:08:19 2023 +0100

    JAMES-3960 Hints to ensure UID/ModSeq consistency in case of disaster
---
 .../init/configuration/CassandraConfiguration.java | 32 +++++++++++++++++++---
 .../java/org/apache/james/mailbox/MessageUid.java  |  4 +++
 .../main/java/org/apache/james/mailbox/ModSeq.java |  4 +++
 .../cassandra/mail/CassandraModSeqProvider.java    |  9 ++++--
 .../cassandra/mail/CassandraUidProvider.java       |  8 ++++--
 .../mail/CassandraModSeqProviderTest.java          | 30 +++++++++++++++++++-
 .../cassandra/mail/CassandraUidProviderTest.java   | 32 +++++++++++++++++++---
 .../sample-configuration/cassandra.properties      |  6 +++-
 .../modules/ROOT/pages/configure/cassandra.adoc    |  4 +++
 .../sample-configuration/cassandra.properties      |  6 +++-
 .../james/DistributedPOP3JamesServerMain.java      |  1 -
 src/site/xdoc/server/config-cassandra.xml          |  3 ++
 12 files changed, 122 insertions(+), 17 deletions(-)

diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/CassandraConfiguration.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/CassandraConfiguration.java
index b1a7fcc55e..9346821aaa 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/CassandraConfiguration.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/CassandraConfiguration.java
@@ -83,6 +83,7 @@ public class CassandraConfiguration {
     private static final String OPTIMISTIC_CONSISTENCY_LEVEL = "optimistic.consistency.level.enabled";
     private static final String MAIL_REPOSITORY_STRONG_CONSISTENCY = "mailrepository.strong.consistency";
     private static final String ACL_ENABLED = "acl.enabled";
+    private static final String UID_MODSEQ_INCREMENT = "uid.modseq.increment";
 
     public static final CassandraConfiguration DEFAULT_CONFIGURATION = builder().build();
 
@@ -110,6 +111,7 @@ public class CassandraConfiguration {
         private Optional<Boolean> optimisticConsistencyLevel = Optional.empty();
         private Optional<Boolean> mailRepositoryStrongConsistency = Optional.empty();
         private Optional<Boolean> aclEnabled = Optional.empty();
+        private Optional<Integer> uidModseqIncrement = Optional.empty();
 
         public Builder mailboxReadStrongConsistency(boolean value) {
             this.mailboxReadStrongConsistency = Optional.of(value);
@@ -162,6 +164,17 @@ public class CassandraConfiguration {
             return this;
         }
 
+        public Builder uidModseqIncrement(int value) {
+            Preconditions.checkArgument(value > 0, "uidModseqIncrement needs to be strictly positive");
+            this.uidModseqIncrement = Optional.of(value);
+            return this;
+        }
+
+        public Builder uidModseqIncrement(Optional<Integer> value) {
+            value.ifPresent(this::uidModseqIncrement);
+            return this;
+        }
+
         public Builder flagsUpdateMessageIdMaxRetry(int value) {
             Preconditions.checkArgument(value > 0, "flagsUpdateMessageIdMaxRetry needs to be strictly positive");
             this.flagsUpdateMessageIdMaxRetry = Optional.of(value);
@@ -372,7 +385,8 @@ public class CassandraConfiguration {
                 mailRepositoryStrongConsistency.orElse(DEFAULT_MAIL_REPOSITORY_STRONG_CONSISTENCY),
                 uidReadStrongConsistency.orElse(DEFAULT_STRONG_CONSISTENCY),
                 modseqReadStrongConsistency.orElse(DEFAULT_STRONG_CONSISTENCY),
-                aclEnabled.orElse(true));
+                aclEnabled.orElse(true),
+                uidModseqIncrement.orElse(0));
         }
     }
 
@@ -427,6 +441,7 @@ public class CassandraConfiguration {
             .mailRepositoryStrongConsistency(Optional.ofNullable(
                 propertiesConfiguration.getBoolean(MAIL_REPOSITORY_STRONG_CONSISTENCY, null)))
             .aclEnabled(Optional.ofNullable(propertiesConfiguration.getBoolean(ACL_ENABLED, null)))
+            .uidModseqIncrement(Optional.ofNullable(propertiesConfiguration.getInteger(UID_MODSEQ_INCREMENT, null)))
             .build();
     }
 
@@ -453,6 +468,7 @@ public class CassandraConfiguration {
     private final boolean uidReadStrongConsistency;
     private final boolean modseqReadStrongConsistency;
     private final boolean aclEnabled;
+    private final int uidModseqIncrement;
 
     @VisibleForTesting
     CassandraConfiguration(int aclMaxRetry, int expungeChunkSize,
@@ -464,7 +480,8 @@ public class CassandraConfiguration {
                            float mailboxCountersReadRepairChanceOneHundred, boolean mailboxReadStrongConsistency,
                            boolean messageReadStrongConsistency, boolean messageWriteStrongConsistency,
                            boolean optimisticConsistencyLevel, boolean mailRepositoryStrongConsistency,
-                           boolean uidReadStrongConsistency, boolean modseqReadStrongConsistency, boolean aclEnabled) {
+                           boolean uidReadStrongConsistency, boolean modseqReadStrongConsistency, boolean aclEnabled,
+                           int uidModseqIncrement) {
         this.aclMaxRetry = aclMaxRetry;
         this.expungeChunkSize = expungeChunkSize;
         this.flagsUpdateMessageIdMaxRetry = flagsUpdateMessageIdMaxRetry;
@@ -488,6 +505,7 @@ public class CassandraConfiguration {
         this.uidReadStrongConsistency = uidReadStrongConsistency;
         this.modseqReadStrongConsistency = modseqReadStrongConsistency;
         this.aclEnabled = aclEnabled;
+        this.uidModseqIncrement = uidModseqIncrement;
     }
 
     public boolean isUidReadStrongConsistency() {
@@ -582,6 +600,10 @@ public class CassandraConfiguration {
         return aclEnabled;
     }
 
+    public int getUidModseqIncrement() {
+        return uidModseqIncrement;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof CassandraConfiguration) {
@@ -609,7 +631,8 @@ public class CassandraConfiguration {
                 && Objects.equals(this.uidReadStrongConsistency, that.uidReadStrongConsistency)
                 && Objects.equals(this.modseqReadStrongConsistency, that.modseqReadStrongConsistency)
                 && Objects.equals(this.mailRepositoryStrongConsistency, that.mailRepositoryStrongConsistency)
-                && Objects.equals(this.aclEnabled, that.aclEnabled);
+                && Objects.equals(this.aclEnabled, that.aclEnabled)
+                && Objects.equals(this.uidModseqIncrement, that.uidModseqIncrement);
         }
         return false;
     }
@@ -623,7 +646,7 @@ public class CassandraConfiguration {
             consistencyLevelRegular, consistencyLevelLightweightTransaction, mailboxReadRepair,
             messageReadStrongConsistency, mailboxReadStrongConsistency, messageWriteStrongConsistency,
             optimisticConsistencyLevel, mailRepositoryStrongConsistency, uidReadStrongConsistency,
-            modseqReadStrongConsistency, aclEnabled);
+            modseqReadStrongConsistency, aclEnabled, uidModseqIncrement);
     }
 
     @Override
@@ -652,6 +675,7 @@ public class CassandraConfiguration {
             .add("modseqReadStrongConsistency", modseqReadStrongConsistency)
             .add("uidReadStrongConsistency", uidReadStrongConsistency)
             .add("aclEnabled", aclEnabled)
+            .add("uidModseqIncrement", uidModseqIncrement)
             .toString();
     }
 }
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageUid.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageUid.java
index c598dd21fa..daf210bc10 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageUid.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageUid.java
@@ -50,6 +50,10 @@ public class MessageUid implements Comparable<MessageUid> {
     public long asLong() {
         return uid;
     }
+
+    public MessageUid add(int offset) {
+        return MessageUid.of(uid + offset);
+    }
     
     @Override
     public int hashCode() {
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/ModSeq.java b/mailbox/api/src/main/java/org/apache/james/mailbox/ModSeq.java
index b4bd08613f..92020abdab 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/ModSeq.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/ModSeq.java
@@ -53,6 +53,10 @@ public class ModSeq implements Comparable<ModSeq> {
         return this.equals(ModSeq.first());
     }
 
+    public ModSeq add(int offset) {
+        return ModSeq.of(modSeq + offset);
+    }
+
     @Override
     public int compareTo(ModSeq o) {
         return Long.compare(modSeq, o.modSeq);
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProvider.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProvider.java
index 4aeca8e044..7bcabe9b2f 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProvider.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProvider.java
@@ -152,7 +152,8 @@ public class CassandraModSeqProvider implements ModSeqProvider {
     public ModSeq highestModSeq(MailboxId mailboxId) throws MailboxException {
         return unbox(() -> findHighestModSeq((CassandraId) mailboxId,
             Optional.of(lwtProfile).filter(any -> cassandraConfiguration.isUidReadStrongConsistency()))
-            .block().orElse(ModSeq.first()));
+            .block().orElse(ModSeq.first()))
+            .add(cassandraConfiguration.getUidModseqIncrement());
     }
 
     private Mono<Optional<ModSeq>> findHighestModSeq(CassandraId mailboxId, Optional<DriverExecutionProfile> executionProfile) {
@@ -200,12 +201,14 @@ public class CassandraModSeqProvider implements ModSeqProvider {
                 .map(highestModSeq -> tryUpdateModSeq(cassandraId, highestModSeq))
                 .orElseGet(() -> tryInsertModSeq(cassandraId, ModSeq.first())))
             .single()
-            .retryWhen(retrySpec);
+            .retryWhen(retrySpec)
+            .map(modSeq -> modSeq.add(cassandraConfiguration.getUidModseqIncrement()));
     }
 
     @Override
     public Mono<ModSeq> highestModSeqReactive(Mailbox mailbox) {
         return findHighestModSeq((CassandraId) mailbox.getMailboxId(), Optional.empty())
-            .map(optional -> optional.orElse(ModSeq.first()));
+            .map(optional -> optional.orElse(ModSeq.first()))
+            .map(modSeq -> modSeq.add(cassandraConfiguration.getUidModseqIncrement()));
     }
 }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProvider.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProvider.java
index 1a019d35fa..27586783fc 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProvider.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProvider.java
@@ -128,7 +128,8 @@ public class CassandraUidProvider implements UidProvider {
             .switchIfEmpty(tryInsert(cassandraId))
             .switchIfEmpty(updateUid)
             .single()
-            .retryWhen(retrySpec);
+            .retryWhen(retrySpec)
+            .map(uid -> uid.add(cassandraConfiguration.getUidModseqIncrement()));
     }
 
     @Override
@@ -150,18 +151,21 @@ public class CassandraUidProvider implements UidProvider {
     private List<MessageUid> range(MessageUid lowerExclusive, MessageUid higherInclusive) {
         return LongStream.range(lowerExclusive.asLong() + 1, higherInclusive.asLong() + 1)
             .mapToObj(MessageUid::of)
+            .map(uid -> uid.add(cassandraConfiguration.getUidModseqIncrement()))
             .collect(ImmutableList.toImmutableList());
     }
 
     @Override
     public Optional<MessageUid> lastUid(Mailbox mailbox) {
         return findHighestUid((CassandraId) mailbox.getMailboxId(), Optional.of(lwtProfile).filter(any -> cassandraConfiguration.isUidReadStrongConsistency()))
-            .blockOptional();
+            .blockOptional()
+            .map(uid -> uid.add(cassandraConfiguration.getUidModseqIncrement()));
     }
 
     @Override
     public Mono<Optional<MessageUid>> lastUidReactive(Mailbox mailbox) {
         return findHighestUid((CassandraId) mailbox.getMailboxId(), Optional.of(lwtProfile).filter(any -> cassandraConfiguration.isUidReadStrongConsistency()))
+            .map(uid -> uid.add(cassandraConfiguration.getUidModseqIncrement()))
             .map(Optional::of)
             .switchIfEmpty(Mono.just(Optional.empty()));
     }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProviderTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProviderTest.java
index e89b30b321..326fbcfb64 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProviderTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProviderTest.java
@@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
 
 import java.time.Duration;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentSkipListSet;
 import java.util.concurrent.ExecutionException;
@@ -40,6 +41,7 @@ import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.Scenario.Barrier;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.core.Username;
+import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.ModSeq;
 import org.apache.james.mailbox.cassandra.ids.CassandraId;
 import org.apache.james.mailbox.cassandra.modules.CassandraModSeqModule;
@@ -47,6 +49,7 @@ import org.apache.james.mailbox.model.Mailbox;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.UidValidity;
 import org.apache.james.util.concurrency.ConcurrentTestRunner;
+import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -64,7 +67,6 @@ class CassandraModSeqProviderTest {
     private CassandraModSeqProvider modSeqProvider;
     private Mailbox mailbox;
 
-
     @BeforeEach
     void setUp(CassandraCluster cassandra) {
         modSeqProvider = new CassandraModSeqProvider(
@@ -155,4 +157,30 @@ class CassandraModSeqProviderTest {
 
         assertThat(modSeqs).hasSize(100);
     }
+
+    @Test
+    void shouldHandleOffset(CassandraCluster cassandra) throws Exception {
+        modSeqProvider = new CassandraModSeqProvider(cassandra.getConf(),
+            CassandraConfiguration.builder()
+                .uidModseqIncrement(10)
+                .build());
+
+        ModSeq modseq0 = modSeqProvider.highestModSeq(mailbox);
+        ModSeq modseq1 = modSeqProvider.nextModSeq(mailbox);
+        ModSeq modseq2 = modSeqProvider.nextModSeq(mailbox.getMailboxId());
+        ModSeq modseq3 = modSeqProvider.nextModSeqReactive(mailbox.getMailboxId()).block();
+        ModSeq modseq4 = modSeqProvider.highestModSeq(mailbox);
+        ModSeq modseq5 = modSeqProvider.highestModSeq(mailbox.getMailboxId());
+        ModSeq modseq6 = modSeqProvider.highestModSeqReactive(mailbox).block();
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(modseq0).isEqualTo(ModSeq.of(10));
+            softly.assertThat(modseq1).isEqualTo(ModSeq.of(11));
+            softly.assertThat(modseq2).isEqualTo(ModSeq.of(12));
+            softly.assertThat(modseq3).isEqualTo(ModSeq.of(13));
+            softly.assertThat(modseq4).isEqualTo(ModSeq.of(13));
+            softly.assertThat(modseq5).isEqualTo(ModSeq.of(13));
+            softly.assertThat(modseq6).isEqualTo(ModSeq.of(13));
+        });
+    }
 }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProviderTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProviderTest.java
index 0f0fab0f0c..e02ff3d80c 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProviderTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProviderTest.java
@@ -23,7 +23,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import java.time.Duration;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.concurrent.ExecutionException;
 import java.util.stream.LongStream;
 
 import org.apache.james.backends.cassandra.CassandraCluster;
@@ -37,6 +36,7 @@ import org.apache.james.mailbox.model.Mailbox;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.UidValidity;
 import org.apache.james.util.concurrency.ConcurrentTestRunner;
+import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -62,7 +62,7 @@ class CassandraUidProviderTest {
     }
 
     @Test
-    void lastUidShouldRetrieveValueStoredByNextUid() throws Exception {
+    void lastUidShouldRetrieveValueStoredByNextUid() {
         int nbEntries = 100;
         Optional<MessageUid> result = uidProvider.lastUid(mailbox);
         assertThat(result).isEmpty();
@@ -85,7 +85,7 @@ class CassandraUidProviderTest {
     }
 
     @Test
-    void nextUidShouldGenerateUniqueValuesWhenParallelCalls() throws ExecutionException, InterruptedException {
+    void nextUidShouldGenerateUniqueValuesWhenParallelCalls() throws Exception {
         int threadCount = 10;
         int nbEntries = 100;
 
@@ -100,7 +100,7 @@ class CassandraUidProviderTest {
     }
 
     @Test
-    void nextUidsShouldGenerateUniqueValuesWhenParallelCalls() throws ExecutionException, InterruptedException {
+    void nextUidsShouldGenerateUniqueValuesWhenParallelCalls() throws Exception {
         int threadCount = 10;
         int nbOperations = 100;
 
@@ -113,4 +113,28 @@ class CassandraUidProviderTest {
 
         assertThat(messageUids).hasSize(nbOperations * 10);
     }
+
+    @Test
+    void shouldHandleOffset(CassandraCluster cassandra) throws Exception {
+        uidProvider = new CassandraUidProvider(cassandra.getConf(),
+            CassandraConfiguration.builder()
+                .uidModseqIncrement(10)
+                .build());
+
+        Optional<MessageUid> uid0 = uidProvider.lastUid(mailbox);
+        MessageUid uid1 = uidProvider.nextUid(mailbox);
+        MessageUid uid2 = uidProvider.nextUid(mailbox.getMailboxId());
+        MessageUid uid3 = uidProvider.nextUidReactive(mailbox.getMailboxId()).block();
+        Optional<MessageUid> uid4 = uidProvider.lastUid(mailbox);
+        Optional<MessageUid> uid5 = uidProvider.lastUidReactive(mailbox).block();
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(uid0).isEmpty();
+            softly.assertThat(uid1).isEqualTo(MessageUid.of(11));
+            softly.assertThat(uid2).isEqualTo(MessageUid.of(12));
+            softly.assertThat(uid3).isEqualTo(MessageUid.of(13));
+            softly.assertThat(uid4).contains(MessageUid.of(13));
+            softly.assertThat(uid5).contains(MessageUid.of(13));
+        });
+    }
 }
diff --git a/server/apps/cassandra-app/sample-configuration/cassandra.properties b/server/apps/cassandra-app/sample-configuration/cassandra.properties
index 892dc8a358..591ac9f934 100644
--- a/server/apps/cassandra-app/sample-configuration/cassandra.properties
+++ b/server/apps/cassandra-app/sample-configuration/cassandra.properties
@@ -37,4 +37,8 @@ cassandra.retryConnection.minDelay=5000
 # Optional. Allows not to use lightweight transactions in CassandraMailRepository.
 # If disabled we implement an idempotent behaviour (duplicates are overridden, missing entries upon deletes are ignored).
 # Defaults to true.
-# mailrepository.strong.consistency=true
\ No newline at end of file
+# mailrepository.strong.consistency=true
+
+# Optional, default to 0. Defensive value to add to uids and modseqs generated. This can be used as an heuristic to maintain
+# consistency even when consensus of Lightweight Transactions is broken, exemple during a disaster recovery process.
+# uid.modseq.increment=0
\ No newline at end of file
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/cassandra.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/cassandra.adoc
index 0bdb101ace..38ba7b40df 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/cassandra.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/cassandra.adoc
@@ -158,4 +158,8 @@ features to your users, you can consider disabling them in order to improve perf
 | mailbox.change.ttl
 | Optional. Duration, default to 60 days. Cassandra Time-to-live for Mailbox change records. Setting time-to-live to zero means refusing to use time-to-live on mailbox changes.
 
+| uid.modseq.increment
+| Optional, default to 0. Defensive value to add to uids and modseqs generated. This can be used as an heuristic to maintain
+consistency even when consensus of Lightweight Transactions is broken, exemple during a disaster recovery process.
+
 |===
\ No newline at end of file
diff --git a/server/apps/distributed-app/sample-configuration/cassandra.properties b/server/apps/distributed-app/sample-configuration/cassandra.properties
index 892dc8a358..591ac9f934 100644
--- a/server/apps/distributed-app/sample-configuration/cassandra.properties
+++ b/server/apps/distributed-app/sample-configuration/cassandra.properties
@@ -37,4 +37,8 @@ cassandra.retryConnection.minDelay=5000
 # Optional. Allows not to use lightweight transactions in CassandraMailRepository.
 # If disabled we implement an idempotent behaviour (duplicates are overridden, missing entries upon deletes are ignored).
 # Defaults to true.
-# mailrepository.strong.consistency=true
\ No newline at end of file
+# mailrepository.strong.consistency=true
+
+# Optional, default to 0. Defensive value to add to uids and modseqs generated. This can be used as an heuristic to maintain
+# consistency even when consensus of Lightweight Transactions is broken, exemple during a disaster recovery process.
+# uid.modseq.increment=0
\ No newline at end of file
diff --git a/server/apps/distributed-pop3-app/src/main/java/org/apache/james/DistributedPOP3JamesServerMain.java b/server/apps/distributed-pop3-app/src/main/java/org/apache/james/DistributedPOP3JamesServerMain.java
index 2350fba760..d094aaab94 100644
--- a/server/apps/distributed-pop3-app/src/main/java/org/apache/james/DistributedPOP3JamesServerMain.java
+++ b/server/apps/distributed-pop3-app/src/main/java/org/apache/james/DistributedPOP3JamesServerMain.java
@@ -133,7 +133,6 @@ public class DistributedPOP3JamesServerMain implements JamesServerMain {
         new SMTPServerModule(),
         WEBADMIN);
 
-
     private static final Module BLOB_MODULE = new BlobExportMechanismModule();
 
     private static final Module CASSANDRA_EVENT_STORE_JSON_SERIALIZATION_DEFAULT_MODULE = binder ->
diff --git a/src/site/xdoc/server/config-cassandra.xml b/src/site/xdoc/server/config-cassandra.xml
index ec509dd51b..660b5cff49 100644
--- a/src/site/xdoc/server/config-cassandra.xml
+++ b/src/site/xdoc/server/config-cassandra.xml
@@ -132,6 +132,9 @@
         <dt><strong>mailbox.change.ttl</strong></dt>
         <dd>Optional. Duration, default to 60 days. Cassandra Time-to-live for Mailbox change records.
             Setting time-to-live to zero means refusing to use time-to-live on mailbox changes.</dd>
+        <dt><strong>uid.modseq.increment</strong></dt>
+        <dd>Optional, default to 0. Defensive value to add to uids and modseqs generated. This can be used as an heuristic to maintain
+            consistency even when consensus of Lightweight Transactions is broken, exemple during a disaster recovery process.</dd>
       </dl>
 
 


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