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 2023/04/27 10:55:16 UTC

[james-project] 02/03: [PERF] Optimize IMAP ESEARCH options

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 f812548421384d44f0d2f0a580c249a2db957b30
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Apr 26 17:34:31 2023 +0700

    [PERF] Optimize IMAP ESEARCH options
    
     - Compute UID ranges only if needed
     - Compute the idSet directly from the LongList to work in-place and drop complexity
---
 .../james/imap/processor/SearchProcessor.java      | 69 ++++++++++++++++------
 1 file changed, 50 insertions(+), 19 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/SearchProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/SearchProcessor.java
index 6d506900c3..499047dd94 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/SearchProcessor.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/SearchProcessor.java
@@ -22,6 +22,7 @@ package org.apache.james.imap.processor;
 import static org.apache.james.mailbox.MessageManager.MailboxMetaData.RecentMode.IGNORE;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -74,6 +75,8 @@ import com.github.fge.lambdas.Throwing;
 import com.google.common.collect.ImmutableList;
 
 import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongComparators;
+import it.unimi.dsi.fastutil.longs.LongConsumer;
 import it.unimi.dsi.fastutil.longs.LongList;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
@@ -164,22 +167,8 @@ public class SearchProcessor extends AbstractMailboxProcessor<SearchRequest> imp
 
     private ImapResponseMessage handleResultOptions(SearchRequest request, ImapSession session, Collection<MessageUid> uids, ModSeq highestModSeq, LongList ids) {
         List<SearchResultOption> resultOptions = request.getSearchOperation().getResultOptions();
-        List<Long> idList = new ArrayList<>(ids.size());
-        for (long id : ids) {
-            idList.add(id);
-        }
 
-        List<IdRange> idsAsRanges = new ArrayList<>();
-        for (Long id: idList) {
-            idsAsRanges.add(new IdRange(id));
-        }
-        IdRange[] idRanges = IdRange.mergeRanges(idsAsRanges).toArray(IdRange[]::new);
-
-        List<UidRange> uidsAsRanges = new ArrayList<>();
-        for (MessageUid uid: uids) {
-            uidsAsRanges.add(new UidRange(uid));
-        }
-        UidRange[] uidRanges = UidRange.mergeRanges(uidsAsRanges).toArray(UidRange[]::new);
+        IdRange[] idRanges = asRanges(ids);
 
         boolean esearch = false;
         for (SearchResultOption resultOption : resultOptions) {
@@ -194,12 +183,11 @@ public class SearchProcessor extends AbstractMailboxProcessor<SearchRequest> imp
             long max = -1;
             long count = ids.size();
 
-            if (ids.size() > 0) {
+            if (!ids.isEmpty()) {
                 min = ids.getLong(0);
                 max = ids.getLong(ids.size() - 1);
             }
 
-
             // Save the sequence-set for later usage. This is part of SEARCHRES
             if (resultOptions.contains(SearchResultOption.SAVE)) {
                 if (resultOptions.contains(SearchResultOption.ALL) || resultOptions.contains(SearchResultOption.COUNT)) {
@@ -207,17 +195,18 @@ public class SearchProcessor extends AbstractMailboxProcessor<SearchRequest> imp
                     SearchResUtil.saveSequenceSet(session, idRanges);
                 } else {
                     List<IdRange> savedRanges = new ArrayList<>();
-                    if (resultOptions.contains(SearchResultOption.MIN)) {
+                    if (resultOptions.contains(SearchResultOption.MIN) && min > 0) {
                         // Store the MIN
                         savedRanges.add(new IdRange(min));
                     }
-                    if (resultOptions.contains(SearchResultOption.MAX)) {
+                    if (resultOptions.contains(SearchResultOption.MAX) && max > 0) {
                         // Store the MAX
                         savedRanges.add(new IdRange(max));
                     }
                     SearchResUtil.saveSequenceSet(session, savedRanges.toArray(IdRange[]::new));
                 }
             }
+            UidRange[] uidRanges = uidRangeIfNeeded(request, resultOptions, uids);
             return new ESearchResponse(min, max, count, idRanges, uidRanges, highestModSeq, request.getTag(), request.isUseUids(), resultOptions);
         } else {
             // Just save the returned sequence-set as this is not SEARCHRES + ESEARCH
@@ -226,6 +215,48 @@ public class SearchProcessor extends AbstractMailboxProcessor<SearchRequest> imp
         }
     }
 
+    private UidRange[] uidRangeIfNeeded(SearchRequest request, List<SearchResultOption> resultOptions, Collection<MessageUid> uids) {
+        if (resultOptions.contains(SearchResultOption.ALL) && request.isUseUids()) {
+            List<UidRange> uidsAsRanges = new ArrayList<>();
+            for (MessageUid uid: uids) {
+                uidsAsRanges.add(new UidRange(uid));
+            }
+            return UidRange.mergeRanges(uidsAsRanges).toArray(UidRange[]::new);
+        }
+        return null;
+    }
+
+    /**
+     * Optimization of IdRange.mergeRanges(idsAsRanges) for list of long
+     */
+    private IdRange[] asRanges(LongList ids) {
+        ids.sort(LongComparators.NATURAL_COMPARATOR);
+        List<IdRange> idsAsRanges = new ArrayList<>();
+        long lowBound = -1;
+        long highBound = -1;
+        for (int i = 0; i < ids.size(); i++) {
+            long id = ids.getLong(i);
+            // Initialize
+            if (lowBound == -1) {
+                lowBound = id;
+                highBound = id;
+                continue;
+            }
+            if (id == highBound + 1) {
+                highBound = id;
+                continue;
+            }
+            idsAsRanges.add(new IdRange(lowBound, highBound));
+            lowBound = id;
+            highBound = id;
+        }
+        if (lowBound != -1 && highBound != -1) {
+            idsAsRanges.add(new IdRange(lowBound, highBound));
+        }
+        IdRange[] result = new IdRange[idsAsRanges.size()];
+        return idsAsRanges.toArray(result);
+    }
+
     private LongList asResults(ImapSession session, boolean useUids, Collection<MessageUid> uids) {
         LongList result = new LongArrayList(uids.size());
         // Avoid using streams here as the overhead for large search responses is massive.


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