diff --git a/config.yaml b/config.yaml index b3f31f247c..0da20fda2f 100644 --- a/config.yaml +++ b/config.yaml @@ -26,3 +26,11 @@ options: default: false type: boolean description: Enable unaccent extension + profile: + description: | + Profile representing the scope of deployment, and used to tune resource allocation. + Allowed values are: “production” and “testing”. + Production will tune postgresql for maximum performance while testing will tune for + minimal running performance. + type: string + default: production diff --git a/lib/charms/postgresql_k8s/v0/postgresql.py b/lib/charms/postgresql_k8s/v0/postgresql.py index 918ed24100..03aaded346 100644 --- a/lib/charms/postgresql_k8s/v0/postgresql.py +++ b/lib/charms/postgresql_k8s/v0/postgresql.py @@ -19,7 +19,7 @@ Any charm using this library should import the `psycopg2` or `psycopg2-binary` dependency. """ import logging -from typing import List, Set, Tuple +from typing import List, Optional, Set, Tuple import psycopg2 from psycopg2 import sql @@ -32,7 +32,7 @@ # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 12 +LIBPATCH = 13 INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE = "invalid role(s) for extra user roles" @@ -419,3 +419,20 @@ def update_user_password(self, username: str, password: str) -> None: finally: if connection is not None: connection.close() + + @staticmethod + def build_postgresql_parameters( + profile: str, available_memory: int + ) -> Optional[dict[str, str]]: + """Builds the PostgreSQL parameters. + + Args: + profile: the profile to use. + available_memory: available memory to use in calculation + + Returns: + Dictionary with the PostgreSQL parameters. + """ + logger.debug(f"Building PostgreSQL parameters for {profile=} and {available_memory=}") + if profile == "production": + return {"shared_buffers": "987MB", "effective_cache_size": "2958MB"} diff --git a/src/charm.py b/src/charm.py index 59b9e43e5a..85d28491ef 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1368,6 +1368,7 @@ def update_config(self, is_creating_backup: bool = False) -> bool: backup_id=self.app_peer_data.get("restoring-backup"), stanza=self.app_peer_data.get("stanza"), restore_stanza=self.app_peer_data.get("restore-stanza"), + parameters=self.postgresql.build_postgresql_parameters(self.config["profile"], 0), ) if not self._is_workload_running: # If Patroni/PostgreSQL has not started yet and TLS relations was initialised, diff --git a/src/cluster.py b/src/cluster.py index c025e6d4e7..ad7cd9c0b6 100644 --- a/src/cluster.py +++ b/src/cluster.py @@ -62,8 +62,6 @@ class UpdateSyncNodeCountError(Exception): class Patroni: """This class handles the bootstrap of a PostgreSQL database through Patroni.""" - pass - def __init__( self, unit_ip: str, @@ -447,6 +445,7 @@ def render_patroni_yml_file( stanza: str = None, restore_stanza: Optional[str] = None, backup_id: Optional[str] = None, + parameters: Optional[dict[str, str]] = None, ) -> None: """Render the Patroni configuration file. @@ -457,6 +456,7 @@ def render_patroni_yml_file( stanza: name of the stanza created by pgBackRest. restore_stanza: name of the stanza used when restoring a backup. backup_id: id of the backup that is being restored. + parameters: PostgreSQL parameters to be added to the postgresql.conf file. """ # Open the template patroni.yml file. with open("templates/patroni.yml.j2", "r") as file: @@ -486,6 +486,7 @@ def render_patroni_yml_file( restore_stanza=restore_stanza, version=self.get_postgresql_version().split(".")[0], minority_count=self.planned_units // 2, + pg_parameters=parameters, ) self.render_file(f"{PATRONI_CONF_PATH}/patroni.yaml", rendered, 0o600) diff --git a/templates/patroni.yml.j2 b/templates/patroni.yml.j2 index 69469a263b..b9248e372b 100644 --- a/templates/patroni.yml.j2 +++ b/templates/patroni.yml.j2 @@ -64,6 +64,11 @@ bootstrap: {%- endif %} archive_mode: on wal_level: logical + {%- if pg_parameters %} + {%- for key, value in pg_parameters.items() %} + {{key}}: {{value}} + {%- endfor -%} + {% endif %} {%- if restoring_backup %} method: pgbackrest diff --git a/tests/integration/ha_tests/test_replication.py b/tests/integration/ha_tests/test_replication.py index 295e07f588..a579aad58b 100644 --- a/tests/integration/ha_tests/test_replication.py +++ b/tests/integration/ha_tests/test_replication.py @@ -29,7 +29,12 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: wait_for_apps = True charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): - await ops_test.model.deploy(charm, num_units=3, series=CHARM_SERIES) + await ops_test.model.deploy( + charm, + num_units=3, + series=CHARM_SERIES, + config={"profile": "testing"}, + ) # Deploy the continuous writes application charm if it wasn't already deployed. if not await app_name(ops_test, APPLICATION_NAME): wait_for_apps = True @@ -111,7 +116,11 @@ async def test_no_data_replicated_between_clusters(ops_test: OpsTest, continuous charm = await ops_test.build_charm(".") async with ops_test.fast_forward(): await ops_test.model.deploy( - charm, application_name=new_cluster_app, num_units=2, series=CHARM_SERIES + charm, + application_name=new_cluster_app, + num_units=2, + series=CHARM_SERIES, + config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(apps=[new_cluster_app], status="active") diff --git a/tests/integration/ha_tests/test_restore_cluster.py b/tests/integration/ha_tests/test_restore_cluster.py index 26ab556b9c..ce9fd99f86 100644 --- a/tests/integration/ha_tests/test_restore_cluster.py +++ b/tests/integration/ha_tests/test_restore_cluster.py @@ -42,11 +42,16 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: num_units=3, series=CHARM_SERIES, storage={"pgdata": {"pool": "lxd-btrfs", "size": 2048}}, + config={"profile": "testing"}, ) # Deploy the second cluster await ops_test.model.deploy( - charm, application_name=SECOND_APPLICATION, num_units=1, series=CHARM_SERIES + charm, + application_name=SECOND_APPLICATION, + num_units=1, + series=CHARM_SERIES, + config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(status="active", timeout=1000) diff --git a/tests/integration/ha_tests/test_self_healing.py b/tests/integration/ha_tests/test_self_healing.py index 3fac0c9218..ace65f739e 100644 --- a/tests/integration/ha_tests/test_self_healing.py +++ b/tests/integration/ha_tests/test_self_healing.py @@ -75,6 +75,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: num_units=3, series=CHARM_SERIES, storage={"pgdata": {"pool": "lxd-btrfs", "size": 2048}}, + config={"profile": "testing"}, ) # Deploy the continuous writes application charm if it wasn't already deployed. if not await app_name(ops_test, APPLICATION_NAME): diff --git a/tests/integration/test_db.py b/tests/integration/test_db.py index 7ae92f2bd1..70acc1c268 100644 --- a/tests/integration/test_db.py +++ b/tests/integration/test_db.py @@ -40,6 +40,7 @@ async def test_mailman3_core_db(ops_test: OpsTest, charm: str) -> None: application_name=DATABASE_APP_NAME, num_units=DATABASE_UNITS, series=CHARM_SERIES, + config={"profile": "testing"}, ) # Wait until the PostgreSQL charm is successfully deployed. diff --git a/tests/integration/test_password_rotation.py b/tests/integration/test_password_rotation.py index dfc4eb79e7..cada825418 100644 --- a/tests/integration/test_password_rotation.py +++ b/tests/integration/test_password_rotation.py @@ -29,6 +29,7 @@ async def test_deploy_active(ops_test: OpsTest): application_name=APP_NAME, num_units=3, series=CHARM_SERIES, + config={"profile": "testing"}, ) await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", timeout=1000) diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index 5778380be0..310f45239c 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -42,6 +42,7 @@ async def test_deploy_active(ops_test: OpsTest): application_name=APP_NAME, num_units=3, series=CHARM_SERIES, + config={"profile": "testing"}, ) # No wait between deploying charms, since we can't guarantee users will wait. Furthermore, # bundles don't wait between deploying charms. diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index e795a489a6..f5b64d6d83 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -1053,6 +1053,7 @@ def test_update_config( postgresql_mock.is_tls_enabled = PropertyMock(side_effect=[False, False, False, False]) _is_workload_running.side_effect = [True, True, False, True] _member_started.side_effect = [True, True, False] + postgresql_mock.build_postgresql_parameters.return_value = {"test": "test"} # Test without TLS files available. self.harness.update_relation_data( @@ -1067,6 +1068,7 @@ def test_update_config( backup_id=None, stanza=None, restore_stanza=None, + parameters={"test": "test"}, ) _reload_patroni_configuration.assert_called_once() _restart.assert_not_called() @@ -1089,6 +1091,7 @@ def test_update_config( backup_id=None, stanza=None, restore_stanza=None, + parameters={"test": "test"}, ) _reload_patroni_configuration.assert_called_once() _restart.assert_called_once()