Added implementation for setting a default connection timeout for all databases types

Closes #46809

Signed-off-by: Ruchika <ruchika.jha1@ibm.com>
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
Co-authored-by: Alexander Schwartz <alexander.schwartz@ibm.com>
This commit is contained in:
Ruchika Jha 2026-03-12 13:45:38 +00:00 committed by GitHub
parent be0da0392b
commit efa2df641c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 243 additions and 4 deletions

View file

@ -171,7 +171,8 @@ This behavior is enabled by default and can be controlled with the server option
* `--truststore-kubernetes-enabled=true|false` (default: `true`)
No changes are required for most deployments. If you previously relied on the Operator to manage these truststore entries, the server now performs the same function directly.
No changes are required for most deployments.
If you previously relied on the Operator to manage these truststore entries, the server now performs the same function directly.
WARNING: when `https-client-auth` is set to `required` or `request` without an explicit `https-trust-store-file`, mTLS client certificate validation falls back to the system truststore. With `truststore-kubernetes-enabled=true`, this means certificates signed by the Kubernetes cluster CA will be accepted as valid client certificates. If this is not desired, either set `https-trust-store-file` explicitly or disable `truststore-kubernetes-enabled`.
@ -259,6 +260,11 @@ As part of this change, the database configuration documentation has been update
The previously documented `utf8mb3` (or `utf8`) character set has been removed from the documentation due to its limitations in storing certain Unicode characters.
* Previously recommended JDBC driver settings for Oracle, MySQL, and MariaDB have been removed from the documentation, as current versions of these databases use appropriate default values.
=== Automatic database connection timeout defaults
To improve failover behavior and startup resilience during network issues, {project_name} now sets a default database connection timeout of 10 seconds.
See https://www.keycloak.org/server/db[Configuring the database] for the list of databases, and on how to change this default.
// ------------------------ Deprecated features ------------------------ //
== Deprecated features

View file

@ -281,6 +281,78 @@ For example:
<@kc.start parameters="--db mssql --db-url-properties=';sendStringParametersAsUnicode=true'"/>
== Automatic database connection timeout
When {project_name} connects to the database, network problems can occur, especially during failovers or switchovers.
To improve resilience and ensure faster recovery, {project_name} automatically sets a default connection timeout of 10 seconds for selected database vendors when using the standard JDBC driver.
The following table lists the affected vendors, the JDBC driver property used, and the default value applied by {project_name}:
[%autowidth]
|===
|Database |JDBC driver property |Default value |Unit
|MySQL
|`connectTimeout`
|`10000`
|milliseconds
|MariaDB
|`connectTimeout`
|`10000`
|milliseconds
|PostgreSQL
|`connectTimeout`
|`10`
|seconds
|Oracle Database
|`oracle.net.CONNECT_TIMEOUT`
|`10000`
|milliseconds
|Microsoft SQL Server
|`loginTimeout`
|`10`
|seconds
|===
{project_name} applies these defaults automatically, but only when all of the following conditions are met:
* The database vendor is configured via `--db`.
* {project_name} is using the standard JDBC driver for that vendor.
* The timeout property has not already been set explicitly by the user in `db-url` or `db-url-properties`.
=== Overriding the default connection timeout
To use a different connection timeout, set the relevant JDBC driver property explicitly via `db-url` or `db-url-properties`.
For MySQL:
<@kc.start parameters="--db mysql --db-url-properties='?connectTimeout=30000'"/>
For MariaDB:
<@kc.start parameters="--db mariadb --db-url-properties='?connectTimeout=30000'"/>
For Microsoft SQL Server:
<@kc.start parameters="--db mssql --db-url-properties=';loginTimeout=20'"/>
For PostgreSQL:
<@kc.start parameters="--db postgres --db-url-properties='?connectTimeout=30'"/>
[NOTE]
====
When using `db-url-properties`, prepend the correct delimiter for your vendor's JDBC URL format:
* PostgreSQL, MySQL, and MariaDB: use `?` as the first property delimiter, or `&` for subsequent properties.
* Microsoft SQL Server: use `;` as the property delimiter.
====
== Preparing for PostgreSQL
=== Writer and reader instances

View file

@ -126,7 +126,36 @@ public class DatabaseOptions {
.defaultValue("false")
.hidden()
.build();
public static final Option<String> DB_MYSQL_CONNECT_TIMEOUT = new OptionBuilder<>("db-mysql-connect-timeout", String.class)
.category(OptionCategory.DATABASE)
.defaultValue("10000") // 10 seconds in milliseconds
.hidden()
.build();
public static final Option<String> DB_MARIADB_CONNECT_TIMEOUT = new OptionBuilder<>("db-mariadb-connect-timeout", String.class)
.category(OptionCategory.DATABASE)
.defaultValue("10000") // 10 seconds in milliseconds
.hidden()
.build();
public static final Option<String> DB_ORACLE_CONNECT_TIMEOUT = new OptionBuilder<>("db-oracle-connect-timeout", String.class)
.category(OptionCategory.DATABASE)
.defaultValue("10000") // 10 seconds in milliseconds
.hidden()
.build();
public static final Option<String> DB_MSSQL_CONNECT_TIMEOUT = new OptionBuilder<>("db-mssql-login-timeout", String.class)
.category(OptionCategory.DATABASE)
.defaultValue("10") // 10 seconds, unit is SECONDS
.hidden()
.build();
public static final Option<String> DB_POSTGRES_CONNECT_TIMEOUT = new OptionBuilder<>("db-postgres-connect-timeout", String.class)
.category(OptionCategory.DATABASE)
.defaultValue("10") // 10 seconds, unit is SECONDS
.hidden()
.build();
public static final Option<String> DB_TIDB_CONNECT_TIMEOUT = new OptionBuilder<>("db-tidb-connect-timeout", String.class)
.category(OptionCategory.DATABASE)
.defaultValue("10000") // 10 seconds in milliseconds
.hidden()
.build();
public static final class Datasources {
/**
* Options that have their sibling for a named datasource

View file

@ -38,6 +38,13 @@ import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.
public final class DatabasePropertyMappers implements PropertyMapperGrouping {
public static final String PG_TARGET_SERVER_TYPE = "quarkus.datasource.jdbc.additional-jdbc-properties.targetServerType";
public static final String MSSQL_SEND_STRING_PARAMETER_AS_UNICODE = "quarkus.datasource.jdbc.additional-jdbc-properties.sendStringParametersAsUnicode";
public static final String MYSQL_CONNECT_TIMEOUT = "quarkus.datasource.jdbc.additional-jdbc-properties.connectTimeout";
public static final String MARIADB_CONNECT_TIMEOUT = "quarkus.datasource.jdbc.additional-jdbc-properties.connectTimeout";
public static final String ORACLEDB_CONNECT_TIMEOUT = "quarkus.datasource.jdbc.additional-jdbc-properties.oracle.net.CONNECT_TIMEOUT";
public static final String MSSQL_CONNECT_TIMEOUT = "quarkus.datasource.jdbc.additional-jdbc-properties.loginTimeout";
private static final String POSTGRES_CONNECT_TIMEOUT = "quarkus.datasource.jdbc.additional-jdbc-properties.connectTimeout";
private static final String TIDB_CONNECT_TIMEOUT = "quarkus.datasource.jdbc.additional-jdbc-properties.connectTimeout";
private static final Logger log = Logger.getLogger(DatabasePropertyMappers.class);
/**
@ -76,6 +83,30 @@ public final class DatabasePropertyMappers implements PropertyMapperGrouping {
.to(MSSQL_SEND_STRING_PARAMETER_AS_UNICODE)
.isEnabled(() -> isMssqlSendStringParametersAsUnicode())
.build(),
fromOption(DatabaseOptions.DB_MYSQL_CONNECT_TIMEOUT)
.to(MYSQL_CONNECT_TIMEOUT)
.isEnabled(() -> isMysqlConnectTimeoutEnabled())
.build(),
fromOption(DatabaseOptions.DB_MARIADB_CONNECT_TIMEOUT)
.to(MARIADB_CONNECT_TIMEOUT)
.isEnabled(() -> isMariadbConnectTimeoutEnabled())
.build(),
fromOption(DatabaseOptions.DB_ORACLE_CONNECT_TIMEOUT)
.to(ORACLEDB_CONNECT_TIMEOUT)
.isEnabled(() -> isOracleConnectTimeoutEnabled())
.build(),
fromOption(DatabaseOptions.DB_MSSQL_CONNECT_TIMEOUT)
.to(MSSQL_CONNECT_TIMEOUT)
.isEnabled(() -> isMssqlLoginTimeoutEnabled())
.build(),
fromOption(DatabaseOptions.DB_POSTGRES_CONNECT_TIMEOUT)
.to(POSTGRES_CONNECT_TIMEOUT)
.isEnabled(() -> isPostgresConnectTimeoutEnabled())
.build(),
fromOption(DatabaseOptions.DB_TIDB_CONNECT_TIMEOUT)
.to(TIDB_CONNECT_TIMEOUT)
.isEnabled(() -> isTidbConnectTimeoutEnabled())
.build(),
fromOption(DatabaseOptions.DB_URL_HOST)
.paramLabel("hostname")
.build(),
@ -196,14 +227,58 @@ public final class DatabasePropertyMappers implements PropertyMapperGrouping {
if (!Objects.equals(Database.getDriver(db, true).orElse(null), dbDriver) &&
!Objects.equals(Database.getDriver(db, false).orElse(null), dbDriver)) {
// Custom JDBC-Driver, for example, AWS JDBC Wrapper.
return false;
}
// sendStringParametersAsUnicode already set by user in db-url or db-url-properties, ignore
return !dbUrl.contains("sendStringParametersAsUnicode") &&
!dbUrlProperties.contains("sendStringParametersAsUnicode");
}
public static boolean isMysqlConnectTimeoutEnabled() {
return isConnectTimeoutEnabled(Database.Vendor.MYSQL, "connectTimeout");
}
public static boolean isMariadbConnectTimeoutEnabled() {
return isConnectTimeoutEnabled(Database.Vendor.MARIADB, "connectTimeout");
}
public static boolean isOracleConnectTimeoutEnabled() {
return isConnectTimeoutEnabled(Database.Vendor.ORACLE, "oracle.net.CONNECT_TIMEOUT");
}
public static boolean isMssqlLoginTimeoutEnabled() {
return isConnectTimeoutEnabled(Database.Vendor.MSSQL, "loginTimeout");
}
public static boolean isPostgresConnectTimeoutEnabled() {
return isConnectTimeoutEnabled(Database.Vendor.POSTGRES, "connectTimeout");
}
public static boolean isTidbConnectTimeoutEnabled() {
return isConnectTimeoutEnabled(Database.Vendor.TIDB, "connectTimeout");
}
private static boolean isConnectTimeoutEnabled(Database.Vendor expectedVendor, String timeoutProperty) {
String db = Configuration.getConfigValue(DB).getValue();
Database.Vendor vendor = Database.getVendor(db).orElse(null);
if (vendor != expectedVendor) {
return false;
}
String dbDriver = Configuration.getConfigValue(DatabaseOptions.DB_DRIVER).getValue();
if (!Objects.equals(Database.getDriver(db, true).orElse(null), dbDriver) &&
!Objects.equals(Database.getDriver(db, false).orElse(null), dbDriver)) {
// Custom JDBC driver (e.g. AWS JDBC Wrapper) do not inject defaults
return false;
}
String dbUrl = Configuration.getConfigValue(DatabaseOptions.DB_URL).getValueOrDefault("");
String dbUrlProperties = Configuration.getKcConfigValue(DatabaseOptions.DB_URL_PROPERTIES.getKey()).getValueOrDefault("");
// Property already set explicitly by the user do not override
return !dbUrl.contains(timeoutProperty) && !dbUrlProperties.contains(timeoutProperty);
}
/**
* Starting with H2 version 2.x, marking "VALUE" as a non-keyword is necessary as some columns are named "VALUE" in the Keycloak schema.
* <p />

View file

@ -712,4 +712,61 @@ public class ConfigurationTest extends AbstractConfigurationTest {
private static Config.Scope cacheEmbeddedConfiguration() {
return initConfig(CacheEmbeddedConfigProviderSpi.SPI_NAME, DefaultCacheEmbeddedConfigProviderFactory.PROVIDER_ID);
}
@Test
public void testDefaultDatabaseConnectTimeouts() {
ConfigArgsConfigSource.setCliArgs("--db=mysql");
SmallRyeConfig config = createConfig();
assertTrue(DatabasePropertyMappers.isMysqlConnectTimeoutEnabled());
assertEquals("10000", config.getConfigValue(DatabasePropertyMappers.MYSQL_CONNECT_TIMEOUT).getValue());
ConfigArgsConfigSource.setCliArgs("--db=mysql", "--db-url-properties=?connectTimeout=5000");
config = createConfig();
assertFalse(DatabasePropertyMappers.isMysqlConnectTimeoutEnabled());
ConfigArgsConfigSource.setCliArgs("--db=mysql", "--db-url=jdbc:mysql://localhost:3306/keycloak?connectTimeout=5000");
config = createConfig();
assertFalse(DatabasePropertyMappers.isMysqlConnectTimeoutEnabled());
ConfigArgsConfigSource.setCliArgs("--db=mariadb");
config = createConfig();
assertTrue(DatabasePropertyMappers.isMariadbConnectTimeoutEnabled());
assertEquals("10000", config.getConfigValue(DatabasePropertyMappers.MARIADB_CONNECT_TIMEOUT).getValue());
ConfigArgsConfigSource.setCliArgs("--db=mariadb", "--db-url=jdbc:mariadb://localhost:3306/keycloak?connectTimeout=5000");
config = createConfig();
assertFalse(DatabasePropertyMappers.isMariadbConnectTimeoutEnabled());
// MariaDB: connectTimeout
ConfigArgsConfigSource.setCliArgs("--db=mariadb", "--db-url-properties=?connectTimeout=5000");
config = createConfig();
assertFalse(DatabasePropertyMappers.isMariadbConnectTimeoutEnabled());
ConfigArgsConfigSource.setCliArgs("--db=oracle");
config = createConfig();
assertTrue(DatabasePropertyMappers.isOracleConnectTimeoutEnabled());
assertEquals("10000", config.getConfigValue(DatabasePropertyMappers.ORACLEDB_CONNECT_TIMEOUT).getValue());
ConfigArgsConfigSource.setCliArgs("--db=oracle", "--db-url-properties=?oracle.net.CONNECT_TIMEOUT=5000");
config = createConfig();
assertFalse(DatabasePropertyMappers.isOracleConnectTimeoutEnabled());
ConfigArgsConfigSource.setCliArgs("--db=oracle", "--db-url=jdbc:oracle:thin:@//localhost:1521/keycloak?oracle.net.CONNECT_TIMEOUT=5000");
config = createConfig();
assertFalse(DatabasePropertyMappers.isOracleConnectTimeoutEnabled());
ConfigArgsConfigSource.setCliArgs("--db=mssql");
config = createConfig();
assertTrue(DatabasePropertyMappers.isMssqlLoginTimeoutEnabled());
assertEquals("10", config.getConfigValue(DatabasePropertyMappers.MSSQL_CONNECT_TIMEOUT).getValue());
ConfigArgsConfigSource.setCliArgs("--db=mssql", "--db-url-properties=;loginTimeout=20");
config = createConfig();
assertFalse(DatabasePropertyMappers.isMssqlLoginTimeoutEnabled());
ConfigArgsConfigSource.setCliArgs("--db=mssql", "--db-url=jdbc:sqlserver://localhost:1433;databaseName=keycloak;loginTimeout=20");
config = createConfig();
assertFalse(DatabasePropertyMappers.isMssqlLoginTimeoutEnabled());
}
}