From 805cc838f70b158927a70d07fc9fa1795988f327 Mon Sep 17 00:00:00 2001 From: Titouan Chary Date: Tue, 3 Mar 2026 10:59:11 +0100 Subject: [PATCH 1/3] perf(postgres): add configurable JDBC and JOOQ tuning settings Add configurable settings for PostgreSQL JDBC driver and JOOQ with performance-optimized defaults. Combined these reduce PostgreSQL/JOOQ CPU overhead by ~18%. JDBC driver settings (inkless.control.plane.jdbc.*): - prepare.threshold (default: 5) - executions before server-prepared - prepared.statement.cache.queries (default: 256) - cached queries - prepared.statement.cache.size.mib (default: 5) - cache size limit - default.row.fetch.size (default: 100) - batch row fetching - tcp.keep.alive (default: true) - connection keepalive - binary.transfer (default: true) - binary serialization JOOQ settings (inkless.control.plane.jooq.*): - execute.logging (default: false) - query logging - render.catalog (default: false) - catalog prefix in SQL - render.schema (default: false) - schema prefix in SQL - reflection.caching (default: true) - reflection caching - cache.record.mappers (default: true) - mapper caching - in.list.padding (default: true) - IN list padding Co-Authored-By: Claude Opus 4.5 --- docs/inkless/configs.rst | 264 ++++++++++++++++++ .../postgres/PostgresConnectionConfig.java | 190 +++++++++++++ .../postgres/PostgresControlPlane.java | 33 ++- 3 files changed, 481 insertions(+), 6 deletions(-) diff --git a/docs/inkless/configs.rst b/docs/inkless/configs.rst index 5c5e9b487c8..7845fe4e0c1 100644 --- a/docs/inkless/configs.rst +++ b/docs/inkless/configs.rst @@ -295,6 +295,94 @@ Under ``inkless.control.plane.`` * Valid Values: [1,...] * Importance: medium +``jdbc.binary.transfer`` + Enable binary transfer for better serialization performance + + * Type: boolean + * Default: true + * Importance: low + +``jdbc.default.row.fetch.size`` + Number of rows to fetch at once from the database + + * Type: int + * Default: 100 + * Valid Values: [0,...] + * Importance: low + +``jdbc.prepare.threshold`` + Number of executions before a statement is server-prepared + + * Type: int + * Default: 5 + * Valid Values: [0,...] + * Importance: low + +``jdbc.prepared.statement.cache.queries`` + Maximum number of queries to cache per connection + + * Type: int + * Default: 256 + * Valid Values: [0,...] + * Importance: low + +``jdbc.prepared.statement.cache.size.mib`` + Maximum size of the prepared statement cache in MiB + + * Type: int + * Default: 5 + * Valid Values: [0,...] + * Importance: low + +``jdbc.tcp.keep.alive`` + Enable TCP keepalive for long-lived connections + + * Type: boolean + * Default: true + * Importance: low + +``jooq.cache.record.mappers`` + Cache record mappers for faster result mapping + + * Type: boolean + * Default: true + * Importance: low + +``jooq.execute.logging`` + Enable JOOQ query logging + + * Type: boolean + * Default: false + * Importance: low + +``jooq.in.list.padding`` + Pad IN lists to powers of 2 for better query plan cache reuse + + * Type: boolean + * Default: true + * Importance: low + +``jooq.reflection.caching`` + Cache reflection operations for faster record mapping + + * Type: boolean + * Default: true + * Importance: low + +``jooq.render.catalog`` + Render catalog prefix in SQL + + * Type: boolean + * Default: false + * Importance: low + +``jooq.render.schema`` + Render schema prefix in SQL + + * Type: boolean + * Default: false + * Importance: low + ----------------- @@ -347,6 +435,94 @@ Under ``inkless.control.plane.read.`` * Valid Values: [1,...] * Importance: medium +``jdbc.binary.transfer`` + Enable binary transfer for better serialization performance + + * Type: boolean + * Default: true + * Importance: low + +``jdbc.default.row.fetch.size`` + Number of rows to fetch at once from the database + + * Type: int + * Default: 100 + * Valid Values: [0,...] + * Importance: low + +``jdbc.prepare.threshold`` + Number of executions before a statement is server-prepared + + * Type: int + * Default: 5 + * Valid Values: [0,...] + * Importance: low + +``jdbc.prepared.statement.cache.queries`` + Maximum number of queries to cache per connection + + * Type: int + * Default: 256 + * Valid Values: [0,...] + * Importance: low + +``jdbc.prepared.statement.cache.size.mib`` + Maximum size of the prepared statement cache in MiB + + * Type: int + * Default: 5 + * Valid Values: [0,...] + * Importance: low + +``jdbc.tcp.keep.alive`` + Enable TCP keepalive for long-lived connections + + * Type: boolean + * Default: true + * Importance: low + +``jooq.cache.record.mappers`` + Cache record mappers for faster result mapping + + * Type: boolean + * Default: true + * Importance: low + +``jooq.execute.logging`` + Enable JOOQ query logging + + * Type: boolean + * Default: false + * Importance: low + +``jooq.in.list.padding`` + Pad IN lists to powers of 2 for better query plan cache reuse + + * Type: boolean + * Default: true + * Importance: low + +``jooq.reflection.caching`` + Cache reflection operations for faster record mapping + + * Type: boolean + * Default: true + * Importance: low + +``jooq.render.catalog`` + Render catalog prefix in SQL + + * Type: boolean + * Default: false + * Importance: low + +``jooq.render.schema`` + Render schema prefix in SQL + + * Type: boolean + * Default: false + * Importance: low + ----------------- @@ -399,6 +575,94 @@ Under ``inkless.control.plane.write.`` * Valid Values: [1,...] * Importance: medium +``jdbc.binary.transfer`` + Enable binary transfer for better serialization performance + + * Type: boolean + * Default: true + * Importance: low + +``jdbc.default.row.fetch.size`` + Number of rows to fetch at once from the database + + * Type: int + * Default: 100 + * Valid Values: [0,...] + * Importance: low + +``jdbc.prepare.threshold`` + Number of executions before a statement is server-prepared + + * Type: int + * Default: 5 + * Valid Values: [0,...] + * Importance: low + +``jdbc.prepared.statement.cache.queries`` + Maximum number of queries to cache per connection + + * Type: int + * Default: 256 + * Valid Values: [0,...] + * Importance: low + +``jdbc.prepared.statement.cache.size.mib`` + Maximum size of the prepared statement cache in MiB + + * Type: int + * Default: 5 + * Valid Values: [0,...] + * Importance: low + +``jdbc.tcp.keep.alive`` + Enable TCP keepalive for long-lived connections + + * Type: boolean + * Default: true + * Importance: low + +``jooq.cache.record.mappers`` + Cache record mappers for faster result mapping + + * Type: boolean + * Default: true + * Importance: low + +``jooq.execute.logging`` + Enable JOOQ query logging + + * Type: boolean + * Default: false + * Importance: low + +``jooq.in.list.padding`` + Pad IN lists to powers of 2 for better query plan cache reuse + + * Type: boolean + * Default: true + * Importance: low + +``jooq.reflection.caching`` + Cache reflection operations for faster record mapping + + * Type: boolean + * Default: true + * Importance: low + +``jooq.render.catalog`` + Render catalog prefix in SQL + + * Type: boolean + * Default: false + * Importance: low + +``jooq.render.schema`` + Render schema prefix in SQL + + * Type: boolean + * Default: false + * Importance: low + ----------------- diff --git a/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresConnectionConfig.java b/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresConnectionConfig.java index 42a7fee6c95..5533ae52e58 100644 --- a/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresConnectionConfig.java +++ b/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresConnectionConfig.java @@ -37,6 +37,56 @@ public class PostgresConnectionConfig extends AbstractControlPlaneConfig { public static final String MAX_CONNECTIONS_CONFIG = "max.connections"; private static final String MAX_CONNECTIONS_DOC = "Maximum number of connections to the database"; + // JDBC driver tuning + public static final String JDBC_PREPARE_THRESHOLD_CONFIG = "jdbc.prepare.threshold"; + private static final String JDBC_PREPARE_THRESHOLD_DOC = + "Number of executions before a statement is server-prepared"; + + public static final String JDBC_PREPARED_STATEMENT_CACHE_QUERIES_CONFIG = "jdbc.prepared.statement.cache.queries"; + private static final String JDBC_PREPARED_STATEMENT_CACHE_QUERIES_DOC = + "Maximum number of queries to cache per connection"; + + public static final String JDBC_PREPARED_STATEMENT_CACHE_SIZE_MIB_CONFIG = "jdbc.prepared.statement.cache.size.mib"; + private static final String JDBC_PREPARED_STATEMENT_CACHE_SIZE_MIB_DOC = + "Maximum size of the prepared statement cache in MiB"; + + public static final String JDBC_DEFAULT_ROW_FETCH_SIZE_CONFIG = "jdbc.default.row.fetch.size"; + private static final String JDBC_DEFAULT_ROW_FETCH_SIZE_DOC = + "Number of rows to fetch at once from the database"; + + public static final String JDBC_TCP_KEEP_ALIVE_CONFIG = "jdbc.tcp.keep.alive"; + private static final String JDBC_TCP_KEEP_ALIVE_DOC = + "Enable TCP keepalive for long-lived connections"; + + public static final String JDBC_BINARY_TRANSFER_CONFIG = "jdbc.binary.transfer"; + private static final String JDBC_BINARY_TRANSFER_DOC = + "Enable binary transfer for better serialization performance"; + + // JOOQ settings + public static final String JOOQ_EXECUTE_LOGGING_CONFIG = "jooq.execute.logging"; + private static final String JOOQ_EXECUTE_LOGGING_DOC = + "Enable JOOQ query logging"; + + public static final String JOOQ_RENDER_CATALOG_CONFIG = "jooq.render.catalog"; + private static final String JOOQ_RENDER_CATALOG_DOC = + "Render catalog prefix in SQL"; + + public static final String JOOQ_RENDER_SCHEMA_CONFIG = "jooq.render.schema"; + private static final String JOOQ_RENDER_SCHEMA_DOC = + "Render schema prefix in SQL"; + + public static final String JOOQ_REFLECTION_CACHING_CONFIG = "jooq.reflection.caching"; + private static final String JOOQ_REFLECTION_CACHING_DOC = + "Cache reflection operations for faster record mapping"; + + public static final String JOOQ_CACHE_RECORD_MAPPERS_CONFIG = "jooq.cache.record.mappers"; + private static final String JOOQ_CACHE_RECORD_MAPPERS_DOC = + "Cache record mappers for faster result mapping"; + + public static final String JOOQ_IN_LIST_PADDING_CONFIG = "jooq.in.list.padding"; + private static final String JOOQ_IN_LIST_PADDING_DOC = + "Pad IN lists to powers of 2 for better query plan cache reuse"; + public static ConfigDef configDef() { return baseConfigDef() .define( @@ -70,6 +120,96 @@ public static ConfigDef configDef() { ConfigDef.Range.atLeast(1), ConfigDef.Importance.MEDIUM, MAX_CONNECTIONS_DOC + ) + // JDBC driver tuning + .define( + JDBC_PREPARE_THRESHOLD_CONFIG, + ConfigDef.Type.INT, + 5, + ConfigDef.Range.atLeast(0), + ConfigDef.Importance.LOW, + JDBC_PREPARE_THRESHOLD_DOC + ) + .define( + JDBC_PREPARED_STATEMENT_CACHE_QUERIES_CONFIG, + ConfigDef.Type.INT, + 256, + ConfigDef.Range.atLeast(0), + ConfigDef.Importance.LOW, + JDBC_PREPARED_STATEMENT_CACHE_QUERIES_DOC + ) + .define( + JDBC_PREPARED_STATEMENT_CACHE_SIZE_MIB_CONFIG, + ConfigDef.Type.INT, + 5, + ConfigDef.Range.atLeast(0), + ConfigDef.Importance.LOW, + JDBC_PREPARED_STATEMENT_CACHE_SIZE_MIB_DOC + ) + .define( + JDBC_DEFAULT_ROW_FETCH_SIZE_CONFIG, + ConfigDef.Type.INT, + 100, + ConfigDef.Range.atLeast(0), + ConfigDef.Importance.LOW, + JDBC_DEFAULT_ROW_FETCH_SIZE_DOC + ) + .define( + JDBC_TCP_KEEP_ALIVE_CONFIG, + ConfigDef.Type.BOOLEAN, + true, + ConfigDef.Importance.LOW, + JDBC_TCP_KEEP_ALIVE_DOC + ) + .define( + JDBC_BINARY_TRANSFER_CONFIG, + ConfigDef.Type.BOOLEAN, + true, + ConfigDef.Importance.LOW, + JDBC_BINARY_TRANSFER_DOC + ) + // JOOQ settings + .define( + JOOQ_EXECUTE_LOGGING_CONFIG, + ConfigDef.Type.BOOLEAN, + false, + ConfigDef.Importance.LOW, + JOOQ_EXECUTE_LOGGING_DOC + ) + .define( + JOOQ_RENDER_CATALOG_CONFIG, + ConfigDef.Type.BOOLEAN, + false, + ConfigDef.Importance.LOW, + JOOQ_RENDER_CATALOG_DOC + ) + .define( + JOOQ_RENDER_SCHEMA_CONFIG, + ConfigDef.Type.BOOLEAN, + false, + ConfigDef.Importance.LOW, + JOOQ_RENDER_SCHEMA_DOC + ) + .define( + JOOQ_REFLECTION_CACHING_CONFIG, + ConfigDef.Type.BOOLEAN, + true, + ConfigDef.Importance.LOW, + JOOQ_REFLECTION_CACHING_DOC + ) + .define( + JOOQ_CACHE_RECORD_MAPPERS_CONFIG, + ConfigDef.Type.BOOLEAN, + true, + ConfigDef.Importance.LOW, + JOOQ_CACHE_RECORD_MAPPERS_DOC + ) + .define( + JOOQ_IN_LIST_PADDING_CONFIG, + ConfigDef.Type.BOOLEAN, + true, + ConfigDef.Importance.LOW, + JOOQ_IN_LIST_PADDING_DOC ); } @@ -93,4 +233,54 @@ public String password() { public int maxConnections() { return getInt(MAX_CONNECTIONS_CONFIG); } + + // JDBC driver tuning getters + public int jdbcPrepareThreshold() { + return getInt(JDBC_PREPARE_THRESHOLD_CONFIG); + } + + public int jdbcPreparedStatementCacheQueries() { + return getInt(JDBC_PREPARED_STATEMENT_CACHE_QUERIES_CONFIG); + } + + public int jdbcPreparedStatementCacheSizeMib() { + return getInt(JDBC_PREPARED_STATEMENT_CACHE_SIZE_MIB_CONFIG); + } + + public int jdbcDefaultRowFetchSize() { + return getInt(JDBC_DEFAULT_ROW_FETCH_SIZE_CONFIG); + } + + public boolean jdbcTcpKeepAlive() { + return getBoolean(JDBC_TCP_KEEP_ALIVE_CONFIG); + } + + public boolean jdbcBinaryTransfer() { + return getBoolean(JDBC_BINARY_TRANSFER_CONFIG); + } + + // JOOQ settings getters + public boolean jooqExecuteLogging() { + return getBoolean(JOOQ_EXECUTE_LOGGING_CONFIG); + } + + public boolean jooqRenderCatalog() { + return getBoolean(JOOQ_RENDER_CATALOG_CONFIG); + } + + public boolean jooqRenderSchema() { + return getBoolean(JOOQ_RENDER_SCHEMA_CONFIG); + } + + public boolean jooqReflectionCaching() { + return getBoolean(JOOQ_REFLECTION_CACHING_CONFIG); + } + + public boolean jooqCacheRecordMappers() { + return getBoolean(JOOQ_CACHE_RECORD_MAPPERS_CONFIG); + } + + public boolean jooqInListPadding() { + return getBoolean(JOOQ_IN_LIST_PADDING_CONFIG); + } } diff --git a/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlane.java b/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlane.java index 0e7d3ad99b0..5f4224759b4 100644 --- a/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlane.java +++ b/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlane.java @@ -28,6 +28,7 @@ import org.jooq.DSLContext; import org.jooq.SQLDialect; +import org.jooq.conf.Settings; import org.jooq.generated.udt.records.CommitFileMergeWorkItemBatchV1Record; import org.jooq.generated.udt.records.CommitFileMergeWorkItemResponseV1Record; import org.jooq.impl.DSL; @@ -107,13 +108,14 @@ public void configure(final Map configs) { throw new RuntimeException(e); } - jobsJooqCtx = DSL.using(jobsDataSource, SQLDialect.POSTGRES); + final Settings jooqSettings = jooqSettings(controlPlaneConfig); + jobsJooqCtx = DSL.using(jobsDataSource, SQLDialect.POSTGRES, jooqSettings); // Set up read and write contexts if configured if (controlPlaneConfig.writeConfig() != null) { LOGGER.info("Using separate write configuration"); writeDataSource = new HikariDataSource(dataSourceConfig(metrics, POOL_NAME + "-write", controlPlaneConfig.writeConfig())); - writeJooqCtx = DSL.using(writeDataSource, SQLDialect.POSTGRES); + writeJooqCtx = DSL.using(writeDataSource, SQLDialect.POSTGRES, jooqSettings); } else { LOGGER.info("No separate write configuration found, using jobs context for writes"); writeJooqCtx = jobsJooqCtx; @@ -121,13 +123,26 @@ public void configure(final Map configs) { if (controlPlaneConfig.readConfig() != null) { LOGGER.info("Using separate read configuration"); readDataSource = new HikariDataSource(dataSourceConfig(metrics, POOL_NAME + "-read", controlPlaneConfig.readConfig())); - readJooqCtx = DSL.using(readDataSource, SQLDialect.POSTGRES); + readJooqCtx = DSL.using(readDataSource, SQLDialect.POSTGRES, jooqSettings); } else { LOGGER.info("No separate write configuration found, using jobs context for reads"); readJooqCtx = jobsJooqCtx; } } + /** + * Creates JOOQ Settings from configuration. + */ + private static Settings jooqSettings(final PostgresConnectionConfig config) { + return new Settings() + .withExecuteLogging(config.jooqExecuteLogging()) + .withRenderCatalog(config.jooqRenderCatalog()) + .withRenderSchema(config.jooqRenderSchema()) + .withReflectionCaching(config.jooqReflectionCaching()) + .withCacheRecordMappers(config.jooqCacheRecordMappers()) + .withInListPadding(config.jooqInListPadding()); + } + private static HikariConfig dataSourceConfig(final KafkaMetricsGroup metrics, final String name, final PostgresConnectionConfig connectionConfig) { final HikariConfig config = new HikariConfig(); config.setPoolName(name); @@ -136,11 +151,17 @@ private static HikariConfig dataSourceConfig(final KafkaMetricsGroup metrics, fi config.setPassword(connectionConfig.password()); config.setMetricsTrackerFactory((poolName, poolStats) -> new PostgresConnectionPoolMetrics(metrics, poolName, poolStats)); config.setTransactionIsolation(IsolationLevel.TRANSACTION_READ_COMMITTED.name()); - config.setMaximumPoolSize(connectionConfig.maxConnections()); - - // We're doing interactive transactions. config.setAutoCommit(false); + + // PostgreSQL JDBC driver tuning + config.addDataSourceProperty("prepareThreshold", connectionConfig.jdbcPrepareThreshold()); + config.addDataSourceProperty("preparedStatementCacheQueries", connectionConfig.jdbcPreparedStatementCacheQueries()); + config.addDataSourceProperty("preparedStatementCacheSizeMiB", connectionConfig.jdbcPreparedStatementCacheSizeMib()); + config.addDataSourceProperty("defaultRowFetchSize", connectionConfig.jdbcDefaultRowFetchSize()); + config.addDataSourceProperty("tcpKeepAlive", connectionConfig.jdbcTcpKeepAlive()); + config.addDataSourceProperty("binaryTransfer", connectionConfig.jdbcBinaryTransfer()); + return config; } From 61e7572a3e9ba5559726b43222256379b7f425b5 Mon Sep 17 00:00:00 2001 From: Titouan Chary Date: Thu, 5 Mar 2026 16:53:34 +0100 Subject: [PATCH 2/3] fix: address review comments - Fix log message typo: "No separate write configuration" -> "No separate read configuration" - Build separate jooqSettings for write/read contexts to respect per-context overrides Co-Authored-By: Claude Opus 4.5 --- .../control_plane/postgres/PostgresControlPlane.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlane.java b/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlane.java index 5f4224759b4..19f9b261810 100644 --- a/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlane.java +++ b/storage/inkless/src/main/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlane.java @@ -108,14 +108,15 @@ public void configure(final Map configs) { throw new RuntimeException(e); } - final Settings jooqSettings = jooqSettings(controlPlaneConfig); - jobsJooqCtx = DSL.using(jobsDataSource, SQLDialect.POSTGRES, jooqSettings); + final Settings jobsJooqSettings = jooqSettings(controlPlaneConfig); + jobsJooqCtx = DSL.using(jobsDataSource, SQLDialect.POSTGRES, jobsJooqSettings); // Set up read and write contexts if configured + // Each context uses its own jooqSettings to respect per-context overrides if (controlPlaneConfig.writeConfig() != null) { LOGGER.info("Using separate write configuration"); writeDataSource = new HikariDataSource(dataSourceConfig(metrics, POOL_NAME + "-write", controlPlaneConfig.writeConfig())); - writeJooqCtx = DSL.using(writeDataSource, SQLDialect.POSTGRES, jooqSettings); + writeJooqCtx = DSL.using(writeDataSource, SQLDialect.POSTGRES, jooqSettings(controlPlaneConfig.writeConfig())); } else { LOGGER.info("No separate write configuration found, using jobs context for writes"); writeJooqCtx = jobsJooqCtx; @@ -123,9 +124,9 @@ public void configure(final Map configs) { if (controlPlaneConfig.readConfig() != null) { LOGGER.info("Using separate read configuration"); readDataSource = new HikariDataSource(dataSourceConfig(metrics, POOL_NAME + "-read", controlPlaneConfig.readConfig())); - readJooqCtx = DSL.using(readDataSource, SQLDialect.POSTGRES, jooqSettings); + readJooqCtx = DSL.using(readDataSource, SQLDialect.POSTGRES, jooqSettings(controlPlaneConfig.readConfig())); } else { - LOGGER.info("No separate write configuration found, using jobs context for reads"); + LOGGER.info("No separate read configuration found, using jobs context for reads"); readJooqCtx = jobsJooqCtx; } } From 3f84bc8fcb5b8f73d303aeac1f892694e2f9b2a6 Mon Sep 17 00:00:00 2001 From: Titouan Chary Date: Thu, 5 Mar 2026 17:26:27 +0100 Subject: [PATCH 3/3] test(control-plane): add unit tests for JDBC/JOOQ config defaults Add tests to verify: - JDBC tuning config defaults (prepareThreshold, cache sizes, fetch size) - JOOQ settings defaults (execute logging, render catalog/schema, caching) - Override behavior for both direct and read/write-prefixed configs This locks in the intended performance tuning defaults so future refactors don't silently change behavior. Co-Authored-By: Claude Opus 4.5 --- .../PostgresControlPlaneConfigTest.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/storage/inkless/src/test/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlaneConfigTest.java b/storage/inkless/src/test/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlaneConfigTest.java index c21e194e706..5a4bfc99dc5 100644 --- a/storage/inkless/src/test/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlaneConfigTest.java +++ b/storage/inkless/src/test/java/io/aiven/inkless/control_plane/postgres/PostgresControlPlaneConfigTest.java @@ -176,4 +176,132 @@ void writeReadConfigs() { assertThat(config.writeConfig().username()).isEqualTo("username-w"); assertThat(config.writeConfig().password()).isEqualTo("password-w"); } + + @Test + void jdbcTuningDefaults() { + // Verify JDBC tuning config defaults + final var config = new PostgresControlPlaneConfig( + Map.of( + "connection.string", "jdbc:postgresql://127.0.0.1:5432/inkless", + "username", "username", + "password", "password" + ) + ); + + // JDBC driver tuning defaults + assertThat(config.jdbcPrepareThreshold()).isEqualTo(5); + assertThat(config.jdbcPreparedStatementCacheQueries()).isEqualTo(256); + assertThat(config.jdbcPreparedStatementCacheSizeMib()).isEqualTo(5); + assertThat(config.jdbcDefaultRowFetchSize()).isEqualTo(100); + assertThat(config.jdbcTcpKeepAlive()).isTrue(); + assertThat(config.jdbcBinaryTransfer()).isTrue(); + } + + @Test + void jooqSettingsDefaults() { + // Verify JOOQ settings defaults + final var config = new PostgresControlPlaneConfig( + Map.of( + "connection.string", "jdbc:postgresql://127.0.0.1:5432/inkless", + "username", "username", + "password", "password" + ) + ); + + // JOOQ settings defaults + assertThat(config.jooqExecuteLogging()).isFalse(); + assertThat(config.jooqRenderCatalog()).isFalse(); + assertThat(config.jooqRenderSchema()).isFalse(); + assertThat(config.jooqReflectionCaching()).isTrue(); + assertThat(config.jooqCacheRecordMappers()).isTrue(); + assertThat(config.jooqInListPadding()).isTrue(); + } + + @Test + void jdbcTuningOverrides() { + // Verify JDBC tuning configs can be overridden + final var config = new PostgresControlPlaneConfig( + Map.of( + "connection.string", "jdbc:postgresql://127.0.0.1:5432/inkless", + "username", "username", + "password", "password", + "jdbc.prepare.threshold", "10", + "jdbc.prepared.statement.cache.queries", "512", + "jdbc.prepared.statement.cache.size.mib", "10", + "jdbc.default.row.fetch.size", "500", + "jdbc.tcp.keep.alive", "false", + "jdbc.binary.transfer", "false" + ) + ); + + assertThat(config.jdbcPrepareThreshold()).isEqualTo(10); + assertThat(config.jdbcPreparedStatementCacheQueries()).isEqualTo(512); + assertThat(config.jdbcPreparedStatementCacheSizeMib()).isEqualTo(10); + assertThat(config.jdbcDefaultRowFetchSize()).isEqualTo(500); + assertThat(config.jdbcTcpKeepAlive()).isFalse(); + assertThat(config.jdbcBinaryTransfer()).isFalse(); + } + + @Test + void jooqSettingsOverrides() { + // Verify JOOQ settings can be overridden + final var config = new PostgresControlPlaneConfig( + Map.of( + "connection.string", "jdbc:postgresql://127.0.0.1:5432/inkless", + "username", "username", + "password", "password", + "jooq.execute.logging", "true", + "jooq.render.catalog", "true", + "jooq.render.schema", "true", + "jooq.reflection.caching", "false", + "jooq.cache.record.mappers", "false", + "jooq.in.list.padding", "false" + ) + ); + + assertThat(config.jooqExecuteLogging()).isTrue(); + assertThat(config.jooqRenderCatalog()).isTrue(); + assertThat(config.jooqRenderSchema()).isTrue(); + assertThat(config.jooqReflectionCaching()).isFalse(); + assertThat(config.jooqCacheRecordMappers()).isFalse(); + assertThat(config.jooqInListPadding()).isFalse(); + } + + @Test + void readWritePrefixedJdbcJooqOverrides() { + // Verify read/write prefixed JDBC/JOOQ configs are properly parsed + final Map configs = new HashMap<>(); + configs.put("connection.string", "jdbc:postgresql://127.0.0.1:5432/inkless"); + configs.put("username", "username"); + configs.put("password", "password"); + // Read-prefixed overrides + configs.put("read.connection.string", "jdbc:postgresql://127.0.0.1:5432/inkless-read"); + configs.put("read.username", "username-r"); + configs.put("read.password", "password-r"); + configs.put("read.jdbc.default.row.fetch.size", "1000"); // Higher for read replicas + configs.put("read.jooq.execute.logging", "true"); + // Write-prefixed overrides + configs.put("write.connection.string", "jdbc:postgresql://127.0.0.1:5432/inkless-write"); + configs.put("write.username", "username-w"); + configs.put("write.password", "password-w"); + configs.put("write.jdbc.default.row.fetch.size", "50"); // Lower for write primary + configs.put("write.jooq.execute.logging", "false"); + + final var config = new PostgresControlPlaneConfig(configs); + config.initializeReadWriteConfigs(); + + // Base config has defaults + assertThat(config.jdbcDefaultRowFetchSize()).isEqualTo(100); + assertThat(config.jooqExecuteLogging()).isFalse(); + + // Read config has overrides + assertThat(config.readConfig()).isNotNull(); + assertThat(config.readConfig().jdbcDefaultRowFetchSize()).isEqualTo(1000); + assertThat(config.readConfig().jooqExecuteLogging()).isTrue(); + + // Write config has overrides + assertThat(config.writeConfig()).isNotNull(); + assertThat(config.writeConfig().jdbcDefaultRowFetchSize()).isEqualTo(50); + assertThat(config.writeConfig().jooqExecuteLogging()).isFalse(); + } }