From 59ccbb42ce4cf5acb381c08c8fbc125b7740cb6f Mon Sep 17 00:00:00 2001 From: Paulo Machado Date: Fri, 7 Jun 2024 16:58:49 -0300 Subject: [PATCH] port experimental max_connections (#425) --- config.yaml | 8 ++++++++ lib/charms/mysql/v0/mysql.py | 33 +++++++++++++++++++++++++++++---- src/charm.py | 8 +++++++- src/config.py | 13 +++++++++++++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/config.yaml b/config.yaml index d91194416..e682d2f7e 100644 --- a/config.yaml +++ b/config.yaml @@ -36,3 +36,11 @@ options: mysql-root-interface-database: description: "The database name for the legacy 'mysql' interface (root level access)" type: "string" +# Experimental features + experimental-max-connections: + type: int + description: | + Maximum number of connections allowed to the MySQL server. + When set max-connections value take precedence over the memory utilizations + againts innodb_buffer_pool_size. + This is an experimental feature and may be removed in future releases. diff --git a/lib/charms/mysql/v0/mysql.py b/lib/charms/mysql/v0/mysql.py index 833f6a912..dba87decf 100644 --- a/lib/charms/mysql/v0/mysql.py +++ b/lib/charms/mysql/v0/mysql.py @@ -130,7 +130,7 @@ def wait_until_mysql_connection(self) -> None: # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 59 +LIBPATCH = 60 UNIT_TEARDOWN_LOCKNAME = "unit-teardown" UNIT_ADD_LOCKNAME = "unit-add" @@ -141,7 +141,8 @@ def wait_until_mysql_connection(self) -> None: BYTES_1MiB = 1048576 # 1 mebibyte RECOVERY_CHECK_TIME = 10 # seconds GET_MEMBER_STATE_TIME = 10 # seconds -MIN_MAX_CONNECTIONS = 100 +MAX_CONNECTIONS_FLOOR = 10 +MIM_MEM_BUFFERS = 200 * BYTES_1MiB SECRET_INTERNAL_LABEL = "secret-id" SECRET_DELETED_LABEL = "None" @@ -868,6 +869,7 @@ def render_mysqld_configuration( *, profile: str, memory_limit: Optional[int] = None, + experimental_max_connections: Optional[int] = None, snap_common: str = "", ) -> tuple[str, dict]: """Render mysqld ini configuration file. @@ -875,16 +877,18 @@ def render_mysqld_configuration( Args: profile: profile to use for the configuration (testing, production) memory_limit: memory limit to use for the configuration in bytes + experimental_max_connections: explicit max connections to use for the configuration snap_common: snap common directory (for log files locations in vm) Returns: a tuple with mysqld ini file string content and a the config dict """ + max_connections = None performance_schema_instrument = "" if profile == "testing": innodb_buffer_pool_size = 20 * BYTES_1MiB innodb_buffer_pool_chunk_size = 1 * BYTES_1MiB group_replication_message_cache_size = 128 * BYTES_1MiB - max_connections = MIN_MAX_CONNECTIONS + max_connections = 100 performance_schema_instrument = "'memory/%=OFF'" else: available_memory = self.get_available_memory() @@ -892,12 +896,33 @@ def render_mysqld_configuration( # when memory limit is set, we need to use the minimum # between the available memory and the limit available_memory = min(available_memory, memory_limit) + + if experimental_max_connections: + # when set, we use the experimental max connections + # and it takes precedence over buffers usage + max_connections = experimental_max_connections + # we reserve 200MiB for memory buffers + # even when there's some overcommittment + available_memory = max( + available_memory - max_connections * 12 * BYTES_1MiB, 200 * BYTES_1MiB + ) + ( innodb_buffer_pool_size, innodb_buffer_pool_chunk_size, group_replication_message_cache_size, ) = self.get_innodb_buffer_pool_parameters(available_memory) - max_connections = max(self.get_max_connections(available_memory), MIN_MAX_CONNECTIONS) + + # constrain max_connections based on the available memory + # after innodb_buffer_pool_size calculation + available_memory -= innodb_buffer_pool_size + ( + group_replication_message_cache_size or 0 + ) + if not max_connections: + max_connections = max( + self.get_max_connections(available_memory), MAX_CONNECTIONS_FLOOR + ) + if available_memory < 2 * BYTES_1GiB: # disable memory instruments if we have less than 2GiB of RAM performance_schema_instrument = "'memory/%=OFF'" diff --git a/src/charm.py b/src/charm.py index 2fd207839..71e0602e8 100755 --- a/src/charm.py +++ b/src/charm.py @@ -373,7 +373,9 @@ def _restart(self, event: EventBase) -> None: restart_states = { self.restart_peers.data[unit].get("state", "unset") for unit in self.peers.units } - if restart_states != {"release"}: + if restart_states == {"unset"}: + logger.info("Restarting primary") + elif restart_states != {"release"}: # Wait other units restart first to minimize primary switchover message = "Primary restart deferred after other units" logger.info(message) @@ -384,6 +386,8 @@ def _restart(self, event: EventBase) -> None: container = self.unit.get_container(CONTAINER_NAME) if container.can_connect(): container.restart(MYSQLD_SAFE_SERVICE) + sleep(10) + self._on_update_status(None) # ========================================================================= # Charm event handlers @@ -440,6 +444,7 @@ def _on_config_changed(self, event: EventBase) -> None: new_config_content, new_config_dict = self._mysql.render_mysqld_configuration( profile=self.config.profile, memory_limit=memory_limit_bytes, + experimental_max_connections=self.config.experimental_max_connections, ) changed_config = compare_dictionaries(previous_config_dict, new_config_dict) @@ -506,6 +511,7 @@ def _write_mysqld_configuration(self): new_config_content, _ = self._mysql.render_mysqld_configuration( profile=self.config.profile, memory_limit=memory_limit_bytes, + experimental_max_connections=self.config.experimental_max_connections, ) self._mysql.write_content_to_file(path=MYSQLD_CONFIG_FILE, content=new_config_content) diff --git a/src/config.py b/src/config.py index c4ec5d029..2bc8250a8 100644 --- a/src/config.py +++ b/src/config.py @@ -9,6 +9,7 @@ from typing import Optional from charms.data_platform_libs.v0.data_models import BaseConfigModel +from charms.mysql.v0.mysql import MAX_CONNECTIONS_FLOOR from pydantic import validator logger = logging.getLogger(__name__) @@ -53,6 +54,7 @@ class CharmConfig(BaseConfigModel): mysql_interface_database: Optional[str] mysql_root_interface_user: Optional[str] mysql_root_interface_database: Optional[str] + experimental_max_connections: Optional[int] @validator("profile") @classmethod @@ -116,3 +118,14 @@ def database_name_validator(cls, value: str) -> Optional[str]: ) return value + + @validator("experimental_max_connections") + @classmethod + def experimental_max_connections_validator(cls, value: int) -> Optional[int]: + """Check experimental max connections.""" + if value < MAX_CONNECTIONS_FLOOR: + raise ValueError( + f"experimental-max-connections must be greater than {MAX_CONNECTIONS_FLOOR}" + ) + + return value