SQL databases: Database

A pooled JDBC DataSource (HikariCP) whose connections log in with the cloud's identity instead of a stored password, on Amazon RDS and Aurora, Azure Database for PostgreSQL and MySQL and Azure SQL, and Google Cloud SQL. Passwords work everywhere too, including any self-hosted PostgreSQL, MySQL or SQL Server.

A pool with IAM login#

Java
Database orders = halyx.provider("aws").database(SqlConfig.builder(SqlEngine.POSTGRESQL)
        .host("orders.cluster-abc.eu-west-1.rds.amazonaws.com")
        .database("orders")
        .auth(SqlAuth.iam("orders_app"))
        .pool(new PoolSettings(20, 5, Duration.ofSeconds(30), Duration.ofMinutes(10), Duration.ofMinutes(30)))
        .build());

DataSource dataSource = orders.dataSource();   // to JDBC, JPA, jOOQ, Flyway, Liquibase
boolean ok = orders.isHealthy(2);               // borrows a connection and checks it within 2 seconds

database(config) opens a new pool on every call, so create the Database once and keep it. Closing it, or closing Halyx, closes the pool. The pool starts on first use, so the application can start while the database is unreachable.

PoolSettings(maxSize, minIdle, connectionTimeout, idleTimeout, maxLifetime) sizes the pool. Without one, PoolSettings.DEFAULTS applies: 10 connections, all kept idle, a 30-second connection timeout, a 10-minute idle timeout and a 30-minute maximum lifetime.

How token login works#

Tokens are fetched when a new physical connection opens, and cached until shortly before they expire. Established connections stay logged in, so token lifetimes never interrupt queries. IAM logins turn on TLS (sslmode=require, sslMode=REQUIRED or encrypt=true).

Provider SqlAuth.iam(...) Engines The username is
AWS RDS IAM token (SigV4, signed locally, 15 minutes) PostgreSQL, MySQL the database user granted rds_iam
Azure Microsoft Entra ID access token PostgreSQL, MySQL, Azure SQL the identity's database role; not used for Azure SQL (SqlAuth.iam()), where the token is the driver's accessToken
Google Cloud OAuth token with the sqlservice.login scope PostgreSQL, MySQL derived from the service account if omitted (app@project.iam on PostgreSQL, app on MySQL)

Important

IAM database users on MySQL (RDS and Cloud SQL) start with no privileges. Until they're granted some, for example GRANT SELECT ON app.* TO 'app'@'%', even selecting the database in the JDBC URL is refused. PostgreSQL lets every user connect.

Passwords and rotated secrets#

SqlAuth.password(user, supplier) asks the supplier for every new connection, so a rotated secret takes effect as connections are replaced (maxLifetime, 30 minutes by default) without a restart:

Java
ParameterStore secrets = halyx.provider("aws").parameterStore(
        ParameterStoreConfig.secrets().cacheTtl(Duration.ofMinutes(5)).build());

Database reporting = halyx.provider("aws").database(SqlConfig.builder(SqlEngine.MYSQL)
        .host("reporting.abc.eu-west-1.rds.amazonaws.com")
        .database("reporting")
        .auth(SqlAuth.password("app", () -> secrets.require("reporting/db", "password")))
        .build());

Cloud specifics#

RDS Proxy works as is: use the proxy endpoint as host, with SqlAuth.iam(...) if the proxy requires IAM authentication.

Cloud SQL Java connector. On Google Cloud, set the instance connection name instead of a host. The connector handles TLS and IAM itself and needs no authorized networks:

Java
Database db = halyx.provider("gcp").database(SqlConfig.builder(SqlEngine.POSTGRESQL)
        .property(GcpProperties.SQL_INSTANCE, "acme-prod:europe-west2:orders")
        .database("orders")
        .auth(SqlAuth.iam())
        .build());

Add com.google.cloud.sql:postgres-socket-factory or com.google.cloud.sql:mysql-socket-factory-connector-j-8 to use it.

TLS verification. To verify the server certificate instead of only encrypting, override the TLS property with jdbcProperty(...), for example jdbcProperty("sslmode", "verify-full") with the cloud's CA bundle.

Driver. The JDBC driver is your dependency: org.postgresql:postgresql, com.mysql:mysql-connector-j or com.microsoft.sqlserver:mssql-jdbc.

Login latency#

A token login costs more than a password login, because the database checks every token. Measured from the same client:

Password Token, warm First token login on a new server
Amazon RDS about 0.35 s 0.5 to 1.1 s 5 to 6 s
Cloud SQL (PostgreSQL) about 0.34 s about 0.47 s about 1.4 s
Azure Database for PostgreSQL about 0.35 s about 4.4 s
Azure SQL about 0.31 s about 1.0 s

Pooled connections pay this only when they are opened. To keep it off the first request, set minIdle above zero so the pool opens connections in the background, and keep maxLifetime long. RDS also limits how fast new IAM connections can be opened, so pool rather than connect per request. The full measurements are in Cloud testing.

Reactive: R2DBC#

halyx-r2dbc gives reactive code the same logins through R2DBC: a pooled ConnectionFactory (r2dbc-pool) whose new connections log in with a password, looked up each time, or with the cloud's IAM token. The token is fetched on a scheduler for blocking work, never on the event loop, and reused until shortly before it expires; established connections stay logged in.

Java
ConnectionPool orders = R2dbcDatabases.connectionFactory(halyx.provider("aws"),
        SqlConfig.builder(SqlEngine.POSTGRESQL)
                .host("orders.cluster-abc.eu-west-1.rds.amazonaws.com")
                .database("orders")
                .auth(SqlAuth.iam("orders_app"))
                .build());
DatabaseClient client = DatabaseClient.create(orders);     // Spring's, or any R2DBC client
Engine Driver (your dependency) IAM login
PostgreSQL org.postgresql:r2dbc-postgresql RDS IAM, Entra ID, Cloud SQL IAM
MySQL io.asyncer:r2dbc-mysql RDS IAM, Cloud SQL IAM
SQL Server io.r2dbc:r2dbc-mssql no: the driver takes passwords only, so use password auth, or JDBC for Entra ID

The pool's size and timeouts come from the config's PoolSettings. Close it with dispose(); it doesn't close with the Halyx context. R2DBC connects to a host, so on Google Cloud use the instance's IP address, not the Cloud SQL connector (gcp.sql.instance). Other clients can get the same endpoint and login from CloudContext.sqlEndpoint(config).

Reactor Netty versions. The R2DBC drivers depend on reactor-netty-core, and the Azure SDK on reactor-netty-http. The two must be the same version, or the Azure SDK fails with IncompatibleClassChangeError. Spring Boot manages both. Without Boot, import io.projectreactor:reactor-bom in your build, or pin both modules to one version.

Spring Boot#

Every halyx.databases.<name> entry becomes a DataSource bean, covered by Spring Boot's JDBC health check and Hikari metrics. Spring Data JPA and JDBC, JdbcTemplate, Flyway and Liquibase use the primary Halyx DataSource unchanged. See Spring Boot.