Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public class PostgresConfiguration {
public static final String SSL_MODE_DEFAULT_VALUE = "allow";
public static final String JOOQ_REACTIVE_TIMEOUT = "jooq.reactive.timeout";
public static final Duration JOOQ_REACTIVE_TIMEOUT_DEFAULT_VALUE = Duration.ofSeconds(10);
public static final String ATTACHMENT_STORAGE_ENABLED = "attachment.storage.enabled";
public static final boolean ATTACHMENT_STORAGE_ENABLED_DEFAULT_VALUE = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For JMAP users this makes sens to have this true

Maybe we can keep the existing behaviour given we provide an easy option to opt out?


public static class Credential {
private final String username;
Expand Down Expand Up @@ -95,6 +97,7 @@ public static class Builder {
private Optional<Integer> byPassRLSPoolMaxSize = Optional.empty();
private Optional<String> sslMode = Optional.empty();
private Optional<Duration> jooqReactiveTimeout = Optional.empty();
private Optional<Boolean> attachmentStorageEnabled = Optional.empty();

public Builder databaseName(String databaseName) {
this.databaseName = Optional.of(databaseName);
Expand Down Expand Up @@ -241,6 +244,16 @@ public Builder jooqReactiveTimeout(Optional<Duration> jooqReactiveTimeout) {
return this;
}

public Builder attachmentStorageEnabled(Optional<Boolean> attachmentStorageEnabled) {
this.attachmentStorageEnabled = attachmentStorageEnabled;
return this;
}

public Builder attachmentStorageEnabled(Boolean attachmentStorageEnabled) {
this.attachmentStorageEnabled = Optional.of(attachmentStorageEnabled);
return this;
}

public PostgresConfiguration build() {
Preconditions.checkArgument(username.isPresent() && !username.get().isBlank(), "You need to specify username");
Preconditions.checkArgument(password.isPresent() && !password.get().isBlank(), "You need to specify password");
Expand All @@ -262,7 +275,8 @@ public PostgresConfiguration build() {
byPassRLSPoolInitialSize.orElse(BY_PASS_RLS_POOL_INITIAL_SIZE_DEFAULT_VALUE),
byPassRLSPoolMaxSize.orElse(BY_PASS_RLS_POOL_MAX_SIZE_DEFAULT_VALUE),
SSLMode.fromValue(sslMode.orElse(SSL_MODE_DEFAULT_VALUE)),
jooqReactiveTimeout.orElse(JOOQ_REACTIVE_TIMEOUT_DEFAULT_VALUE));
jooqReactiveTimeout.orElse(JOOQ_REACTIVE_TIMEOUT_DEFAULT_VALUE),
attachmentStorageEnabled.orElse(ATTACHMENT_STORAGE_ENABLED_DEFAULT_VALUE));
}
}

Expand All @@ -288,6 +302,7 @@ public static PostgresConfiguration from(Configuration propertiesConfiguration)
.sslMode(Optional.ofNullable(propertiesConfiguration.getString(SSL_MODE)))
.jooqReactiveTimeout(Optional.ofNullable(propertiesConfiguration.getString(JOOQ_REACTIVE_TIMEOUT))
.map(value -> DurationParser.parse(value, ChronoUnit.SECONDS)))
.attachmentStorageEnabled(propertiesConfiguration.getBoolean(ATTACHMENT_STORAGE_ENABLED, ATTACHMENT_STORAGE_ENABLED_DEFAULT_VALUE))
.build();
}

Expand All @@ -304,12 +319,13 @@ public static PostgresConfiguration from(Configuration propertiesConfiguration)
private final Integer byPassRLSPoolMaxSize;
private final SSLMode sslMode;
private final Duration jooqReactiveTimeout;
private final boolean attachmentStorageEnabled;

private PostgresConfiguration(String host, int port, String databaseName, String databaseSchema,
Credential defaultCredential, Credential byPassRLSCredential, RowLevelSecurity rowLevelSecurity,
Integer poolInitialSize, Integer poolMaxSize,
Integer byPassRLSPoolInitialSize, Integer byPassRLSPoolMaxSize,
SSLMode sslMode, Duration jooqReactiveTimeout) {
SSLMode sslMode, Duration jooqReactiveTimeout, boolean attachmentStorageEnabled) {
this.host = host;
this.port = port;
this.databaseName = databaseName;
Expand All @@ -323,6 +339,7 @@ private PostgresConfiguration(String host, int port, String databaseName, String
this.byPassRLSPoolMaxSize = byPassRLSPoolMaxSize;
this.sslMode = sslMode;
this.jooqReactiveTimeout = jooqReactiveTimeout;
this.attachmentStorageEnabled = attachmentStorageEnabled;
}

public String getHost() {
Expand Down Expand Up @@ -377,9 +394,13 @@ public Duration getJooqReactiveTimeout() {
return jooqReactiveTimeout;
}

public boolean isAttachmentStorageEnabled() {
return attachmentStorageEnabled;
}

@Override
public final int hashCode() {
return Objects.hash(host, port, databaseName, databaseSchema, defaultCredential, byPassRLSCredential, rowLevelSecurity, poolInitialSize, poolMaxSize, sslMode, jooqReactiveTimeout);
return Objects.hash(host, port, databaseName, databaseSchema, defaultCredential, byPassRLSCredential, rowLevelSecurity, poolInitialSize, poolMaxSize, sslMode, jooqReactiveTimeout, attachmentStorageEnabled);
}

@Override
Expand All @@ -397,7 +418,8 @@ public final boolean equals(Object o) {
&& Objects.equals(this.poolInitialSize, that.poolInitialSize)
&& Objects.equals(this.poolMaxSize, that.poolMaxSize)
&& Objects.equals(this.sslMode, that.sslMode)
&& Objects.equals(this.jooqReactiveTimeout, that.jooqReactiveTimeout);
&& Objects.equals(this.jooqReactiveTimeout, that.jooqReactiveTimeout)
&& Objects.equals(this.attachmentStorageEnabled, that.attachmentStorageEnabled);
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class PostgresMailboxSessionMapperFactory extends MailboxSessionMapperFac
private final BlobId.Factory blobIdFactory;
private final Clock clock;
private final RowLevelSecurity rowLevelSecurity;
private final boolean attachmentStorageEnabled;

@Inject
public PostgresMailboxSessionMapperFactory(PostgresExecutor.Factory executorFactory,
Expand All @@ -74,6 +75,7 @@ public PostgresMailboxSessionMapperFactory(PostgresExecutor.Factory executorFact
this.blobIdFactory = blobIdFactory;
this.clock = clock;
this.rowLevelSecurity = postgresConfiguration.getRowLevelSecurity();
this.attachmentStorageEnabled = postgresConfiguration.isAttachmentStorageEnabled();
}

@Override
Expand Down Expand Up @@ -140,6 +142,10 @@ public PostgresAttachmentMapper getAttachmentMapper(MailboxSession session) {
return createAttachmentMapper(session);
}

public boolean isAttachmentStorageEnabled() {
return attachmentStorageEnabled;
}

protected DeleteMessageListener deleteMessageListener() {
PostgresMessageDAO.Factory postgresMessageDAOFactory = new PostgresMessageDAO.Factory(blobIdFactory, executorFactory);
PostgresMailboxMessageDAO.Factory postgresMailboxMessageDAOFactory = new PostgresMailboxMessageDAO.Factory(executorFactory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,24 @@ public PostgresMessageManager(PostgresMailboxSessionMapperFactory mapperFactory,
Clock clock, PreDeletionHooks preDeletionHooks) {
super(StoreMailboxManager.DEFAULT_NO_MESSAGE_CAPABILITIES, mapperFactory, index, eventBus, locker, mailbox,
quotaManager, quotaRootResolver, batchSizes, storeRightManager, preDeletionHooks,
new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), mapperFactory, messageParser, threadIdGuessingAlgorithm, clock));
createMessageStorer(mapperFactory, messageIdFactory, messageParser, threadIdGuessingAlgorithm, clock));
this.storeRightManager = storeRightManager;
this.mapperFactory = mapperFactory;
this.mailbox = mailbox;
}

private static MessageStorer createMessageStorer(PostgresMailboxSessionMapperFactory mapperFactory,
MessageId.Factory messageIdFactory,
MessageParser messageParser,
ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm,
Clock clock) {
if (mapperFactory.isAttachmentStorageEnabled()) {
return new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), mapperFactory, messageParser, threadIdGuessingAlgorithm, clock);
} else {
return new MessageStorer.WithoutAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), threadIdGuessingAlgorithm, clock);
}
}


@Override
public Flags getPermanentFlags(MailboxSession session) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@ by-pass-rls.pool.max.size=10
ssl.mode=allow

## Duration. Optional, defaults to 10 second. jOOQ reactive timeout when executing Postgres query. This setting prevent jooq reactive bug from causing hanging issue.
#jooq.reactive.timeout=10second
#jooq.reactive.timeout=10second

# Boolean. Optional, default to false. Whether to enable attachment storage.
# When enabled, attachments are stored separately in blob storage and attachment table for faster JMAP attachment download.
# When disabled, attachments are only stored within the message body blob, reducing storage duplication but requiring
# message parsing for attachment access. Virtual part blobs (messageId_partId) will still work for JMAP downloads.
# Disabling this can significantly reduce storage usage for messages with many attachments.
#attachment.storage.enabled=false