Configure multiple FoundationDB client threads

This commit is contained in:
Ameya Lokare committed 2026-09-10 10:29:37 -07:00
1 parent 5221b3fb84
commit 81f38c1db7
5 files changed
+56 -1

No files matched your search

@@ -111,6 +111,7 @@ import org.whispersystems.textsecuregcm.captcha.CaptchaClient;
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
import org.whispersystems.textsecuregcm.captcha.ShortCodeExpander;
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
import org.whispersystems.textsecuregcm.configuration.FoundationDbExternalClientConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretStore;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretsModule;
@@ -500,6 +501,17 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
// we'd like, but is the least bad option given current constraints.
fdb.disableShutdownHook();
final FoundationDbExternalClientConfiguration externalClientConfiguration = config.getFoundationDbMessagesConfiguration()
.externalClientConfiguration();
if (externalClientConfiguration != null) {
// If threadsPerClient is not specified, we default to the cluster size so that there is 1:1 correspondence between
// Database objects and threads.
final int clientThreadsPerVersion = externalClientConfiguration.threadsPerClient()
.orElseGet(() -> config.getFoundationDbMessagesConfiguration().clusters().size());
externalClientConfiguration.clientLibraryPaths().forEach(path -> fdb.options().setExternalClientLibrary(path));
fdb.options().setClientThreadsPerVersion(clientThreadsPerVersion);
}
final Map<Integer, List<FaultTolerantDatabase>> messageDatabasesByEpoch;
{
final Map<String, FaultTolerantDatabase> faultTolerantDatabasesByName =
@@ -0,0 +1,22 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.PositiveOrZero;
import java.util.List;
import java.util.Optional;
/// Configuration for FoundationDB external clients. Primarily exists because an external client must be specified to
/// make use of the multi-threaded client feature.
///
/// @param clientLibraryPaths a list of paths to external client libraries (generally .so files on Linux). This allows
/// us to run multiple FDB client versions concurrently if needed; however, during normal operation, we expect the list
/// to be a single path pointing to the current FDB client library
/// @param threadsPerClient the number of networking threads spawned per client
public record FoundationDbExternalClientConfiguration(@NotEmpty List<@NotBlank String> clientLibraryPaths,
Optional<@PositiveOrZero Integer> threadsPerClient) {}
@@ -35,7 +35,8 @@ public record FoundationDbMessagesConfiguration(@NotEmpty Map<String, @Valid Fou
@PositiveOrZero long transactionRetryLimit,
@NotNull Duration batchPriorityTransactionTimeout,
@PositiveOrZero long batchPriorityTransactionRetryLimit,
@Nullable String circuitBreakerConfigurationName) {
@Nullable String circuitBreakerConfigurationName,
@Nullable @Valid FoundationDbExternalClientConfiguration externalClientConfiguration) {
public static final long DEFAULT_MAX_WATCHES_PER_CLIENT = 10_000;
public static final Duration DEFAULT_TRANSACTION_TIMEOUT = Duration.ofSeconds(1);
@@ -37,6 +37,7 @@ import org.whispersystems.textsecuregcm.backup.BackupsDb;
import org.whispersystems.textsecuregcm.backup.Cdn3BackupCredentialGenerator;
import org.whispersystems.textsecuregcm.backup.Cdn3RemoteStorageManager;
import org.whispersystems.textsecuregcm.backup.SecureValueRecoveryBCredentialsGeneratorFactory;
import org.whispersystems.textsecuregcm.configuration.FoundationDbExternalClientConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
import org.whispersystems.textsecuregcm.controllers.SecureValueRecovery2Controller;
@@ -151,6 +152,17 @@ public record CommandDependencies(
// we'd like, but is the least bad option given current constraints.
fdb.disableShutdownHook();
final FoundationDbExternalClientConfiguration externalClientConfiguration = configuration.getFoundationDbMessagesConfiguration()
.externalClientConfiguration();
if (externalClientConfiguration != null) {
// If threadsPerClient is not specified, we default to the cluster size so that there is 1:1 correspondence between
// Database objects and threads.
final int clientThreadsPerVersion = externalClientConfiguration.threadsPerClient()
.orElseGet(() -> configuration.getFoundationDbMessagesConfiguration().clusters().size());
externalClientConfiguration.clientLibraryPaths().forEach(path -> fdb.options().setExternalClientLibrary(path));
fdb.options().setClientThreadsPerVersion(clientThreadsPerVersion);
}
final Map<Integer, List<FaultTolerantDatabase>> messageDatabasesByEpoch;
{
final Map<String, FaultTolerantDatabase> faultTolerantDatabasesByName =
@@ -29,6 +29,7 @@ class FoundationDbMessagesConfigurationTest {
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT,
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_TIMEOUT.multipliedBy(2),
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT * 2,
null,
null
).isEveryEpochClusterConfigured());
@@ -43,6 +44,7 @@ class FoundationDbMessagesConfigurationTest {
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT,
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_TIMEOUT.multipliedBy(2),
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT * 2,
null,
null
).isEveryEpochClusterConfigured());
}
@@ -60,6 +62,7 @@ class FoundationDbMessagesConfigurationTest {
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT,
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_TIMEOUT.multipliedBy(2),
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT * 2,
null,
null
).isEveryEpochFreeOfDuplicates());
@@ -74,6 +77,7 @@ class FoundationDbMessagesConfigurationTest {
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT,
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_TIMEOUT.multipliedBy(2),
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT * 2,
null,
null
).isEveryEpochFreeOfDuplicates());
}
@@ -91,6 +95,7 @@ class FoundationDbMessagesConfigurationTest {
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT,
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_TIMEOUT.multipliedBy(2),
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT * 2,
null,
null
).isActiveEpochConfigured());
@@ -105,6 +110,7 @@ class FoundationDbMessagesConfigurationTest {
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT,
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_TIMEOUT.multipliedBy(2),
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT * 2,
null,
null
).isActiveEpochConfigured());
}
@@ -122,6 +128,7 @@ class FoundationDbMessagesConfigurationTest {
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT,
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_TIMEOUT.multipliedBy(2),
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT * 2,
null,
null
).isCurrentVersionstampCipherKeyConfigured());
@@ -136,6 +143,7 @@ class FoundationDbMessagesConfigurationTest {
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT,
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_TIMEOUT.multipliedBy(2),
FoundationDbMessagesConfiguration.DEFAULT_TRANSACTION_RETRY_LIMIT * 2,
null,
null
).isCurrentVersionstampCipherKeyConfigured());
}