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 4f95fb4ecf [PERF] Precompute ResultSet indexes on large IMAP
processings (#2974)
4f95fb4ecf is described below
commit 4f95fb4ecf915ecaa9fbfe42957da657d4a763fd
Author: Benoit TELLIER <[email protected]>
AuthorDate: Fri Mar 13 15:22:41 2026 +0100
[PERF] Precompute ResultSet indexes on large IMAP processings (#2974)
Outlook and other IMAP clients relies heavily on
C: UID FETCH 1:* (FLAGS)
For their resync and we spend ~50% of the compute of driver deserialization
recomputing the same integer indexes over, and over again on the DB driver
threads.
This patch proposes precomputation on the first row of those indexes for
reuse, saving
easily this precious time.
---
.../cassandra/mail/CassandraMessageIdDAO.java | 18 +++--
.../mailbox/cassandra/mail/FlagsExtractor.java | 86 ++++++++++++++++++++++
2 files changed, 99 insertions(+), 5 deletions(-)
diff --git
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
index f0ddb2eb2d..68c854b96c 100644
---
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
+++
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
@@ -465,20 +465,24 @@ public class CassandraMessageIdDAO {
}
public Flux<ComposedMessageIdWithMetaData>
listMessagesMetadata(CassandraId mailboxId, MessageRange range) {
+ MemoizedSupplier<FlagsExtractor.Optimized> memoizedReader = new
MemoizedSupplier<>();
+
return cassandraAsyncExecutor.executeRows(selectMetadataRange.bind()
.set(MAILBOX_ID, mailboxId.asUuid(), TypeCodecs.TIMEUUID)
.setLong(IMAP_UID_GTE, range.getUidFrom().asLong())
.setLong(IMAP_UID_LTE, range.getUidTo().asLong())
.setExecutionProfile(readProfile))
.map(row -> {
- CassandraMessageId messageId =
CassandraMessageId.Factory.of(row.get(MESSAGE_ID, TypeCodecs.TIMEUUID));
+ FlagsExtractor.Optimized reader = memoizedReader.get(() ->
FlagsExtractor.Optimized.of(row));
+ CassandraMessageId messageId =
CassandraMessageId.Factory.of(reader.getMessageId(row));
+
return ComposedMessageIdWithMetaData.builder()
-
.modSeq(ModSeq.of(TypeCodecs.BIGINT.decodePrimitive(row.getBytesUnsafe(MOD_SEQ),
protocolVersion)))
- .threadId(getThreadIdFromRow(row, messageId))
- .flags(FlagsExtractor.getFlags(row))
+ .modSeq(ModSeq.of(reader.getModSeq(row)))
+ .threadId(asThreadId(messageId, reader.getThreadId(row)))
+ .flags(reader.getFlags(row))
.composedMessageId(new ComposedMessageId(mailboxId,
messageId,
-
MessageUid.of(TypeCodecs.BIGINT.decodePrimitive(row.getBytesUnsafe(IMAP_UID),
protocolVersion))))
+ MessageUid.of(reader.getImapUid(row))))
.build();
});
}
@@ -609,6 +613,10 @@ public class CassandraMessageIdDAO {
private ThreadId getThreadIdFromRow(Row row, MessageId messageId) {
UUID threadIdUUID = row.get(THREAD_ID, TypeCodecs.TIMEUUID);
+ return asThreadId(messageId, threadIdUUID);
+ }
+
+ private static ThreadId asThreadId(MessageId messageId, UUID threadIdUUID)
{
if (threadIdUUID == null) {
return ThreadId.fromBaseMessageId(messageId);
}
diff --git
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/FlagsExtractor.java
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/FlagsExtractor.java
index b812ada7ab..dc883a0e3e 100644
---
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/FlagsExtractor.java
+++
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/FlagsExtractor.java
@@ -21,15 +21,21 @@ package org.apache.james.mailbox.cassandra.mail;
import static com.datastax.oss.driver.api.core.type.DataTypes.TEXT;
import static com.datastax.oss.driver.api.core.type.DataTypes.setOf;
+import java.time.Instant;
import java.util.Set;
+import java.util.UUID;
import jakarta.mail.Flags;
+import org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable;
+import org.apache.james.mailbox.cassandra.table.CassandraMessageIds;
+import org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table;
import org.apache.james.mailbox.cassandra.table.Flag;
import org.apache.james.mailbox.store.StoreMessageManager;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.type.codec.TypeCodec;
import com.datastax.oss.driver.api.core.type.codec.TypeCodecs;
@@ -38,6 +44,86 @@ import
com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
public class FlagsExtractor {
public static final TypeCodec<Set<String>> SET_OF_STRINGS_CODEC =
CodecRegistry.DEFAULT.codecFor(setOf(TEXT));
+ /**
+ * Immutable row reader whose column indices are pre-computed once from the
+ * {@link ColumnDefinitions} of the first row in a ResultSet. All
subsequent
+ * rows of the same ResultSet reuse those indices, eliminating the per-row
+ * {@code ColumnDefinitions.firstIndexOf} scan which takes 50% of the
Cassandra
+ * CPU post treatment time where in use.
+ */
+ public static class Optimized {
+ private final int[] flagIndices;
+ private final int userFlagsIndex;
+ private final int messageIdIndex;
+ private final int mailboxIdIndex;
+ private final int imapUidIndex;
+ private final int modSeqIndex;
+ private final int threadIdIndex;
+ private final int internalDateIndex;
+
+ private final ProtocolVersion protocolVersion;
+
+ public static Optimized of(Row row) {
+ return new Optimized(row.getColumnDefinitions(),
row.protocolVersion());
+ }
+
+ private Optimized(ColumnDefinitions defs, ProtocolVersion
protocolVersion) {
+ this.protocolVersion = protocolVersion;
+
+ this.flagIndices = new int[Flag.ALL_LOWERCASE.length];
+ for (int i = 0; i < Flag.ALL_LOWERCASE.length; i++) {
+ this.flagIndices[i] = defs.firstIndexOf(Flag.ALL_LOWERCASE[i]);
+ }
+ this.userFlagsIndex = defs.firstIndexOf(Flag.USER_FLAGS);
+
+ this.messageIdIndex =
defs.firstIndexOf(CassandraMessageIds.MESSAGE_ID);
+ this.mailboxIdIndex =
defs.firstIndexOf(CassandraMessageIds.MAILBOX_ID);
+ this.imapUidIndex =
defs.firstIndexOf(CassandraMessageIds.IMAP_UID);
+ this.modSeqIndex =
defs.firstIndexOf(CassandraMessageIdTable.MOD_SEQ);
+ this.threadIdIndex =
defs.firstIndexOf(CassandraMessageIdTable.THREAD_ID);
+ this.internalDateIndex =
defs.firstIndexOf(CassandraMessageV3Table.INTERNAL_DATE);
+ }
+
+ public Flags getFlags(Row row) {
+ Flags flags = new Flags();
+ for (int i = 0; i < Flag.ALL_LOWERCASE.length; i++) {
+ CqlIdentifier cqlId = Flag.ALL_LOWERCASE[i];
+ if (!StoreMessageManager.HANDLE_RECENT &&
cqlId.equals(Flag.RECENT)) {
+ continue;
+ }
+ if
(TypeCodecs.BOOLEAN.decodePrimitive(row.getBytesUnsafe(flagIndices[i]),
protocolVersion)) {
+ flags.add(Flag.JAVAX_MAIL_FLAG.get(cqlId));
+ }
+ }
+ row.get(userFlagsIndex, SET_OF_STRINGS_CODEC).forEach(flags::add);
+ return flags;
+ }
+
+ public UUID getMessageId(Row row) {
+ return
TypeCodecs.TIMEUUID.decode(row.getBytesUnsafe(messageIdIndex), protocolVersion);
+ }
+
+ public UUID getMailboxId(Row row) {
+ return
TypeCodecs.TIMEUUID.decode(row.getBytesUnsafe(mailboxIdIndex), protocolVersion);
+ }
+
+ public long getImapUid(Row row) {
+ return
TypeCodecs.BIGINT.decodePrimitive(row.getBytesUnsafe(imapUidIndex),
protocolVersion);
+ }
+
+ public long getModSeq(Row row) {
+ return
TypeCodecs.BIGINT.decodePrimitive(row.getBytesUnsafe(modSeqIndex),
protocolVersion);
+ }
+
+ public UUID getThreadId(Row row) {
+ return
TypeCodecs.TIMEUUID.decode(row.getBytesUnsafe(threadIdIndex), protocolVersion);
+ }
+
+ public Instant getInternalDate(Row row) {
+ return
TypeCodecs.TIMESTAMP.decode(row.getBytesUnsafe(internalDateIndex),
protocolVersion);
+ }
+ }
+
public static Flags getFlags(Row row) {
return getFlags(row, row.protocolVersion());
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]