Skip to content

Commit

Permalink
port experimental max_connections (#425)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulomach authored Jun 7, 2024
1 parent 38d5709 commit 59ccbb4
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 5 deletions.
8 changes: 8 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
33 changes: 29 additions & 4 deletions lib/charms/mysql/v0/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -868,36 +869,60 @@ 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.
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()
if memory_limit:
# 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'"
Expand Down
8 changes: 7 additions & 1 deletion src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
13 changes: 13 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

0 comments on commit 59ccbb4

Please sign in to comment.