Skip to content

Commit c347ef7

Browse files
committed
iops split
1 parent 0386ea5 commit c347ef7

2 files changed

Lines changed: 79 additions & 7 deletions

File tree

src/api/organization/project/branch/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .....deployment import (
2727
AUTOSCALER_PVC_SUFFIX,
2828
STORAGE_PVC_SUFFIX,
29+
WAL_IOPS_FRACTION,
2930
DeploymentParameters,
3031
ResizeParameters,
3132
branch_api_domain,
@@ -869,7 +870,8 @@ async def _clone_branch_environment_task(
869870
storage_class_name: str | None = None
870871
if copy_data:
871872
try:
872-
storage_class_name = await ensure_branch_storage_class(branch_id, iops=parameters.iops)
873+
data_iops = max(1, round(parameters.iops * (1 - WAL_IOPS_FRACTION)))
874+
storage_class_name = await ensure_branch_storage_class(branch_id, iops=data_iops)
873875
await clone_branch_database_volume(
874876
source_branch_id=source_branch_id,
875877
target_branch_id=branch_id,
@@ -942,7 +944,8 @@ async def _restore_branch_environment_task(
942944
await _persist_branch_status(branch_id, BranchServiceStatus.CREATING)
943945
storage_class_name: str | None = None
944946
try:
945-
storage_class_name = await ensure_branch_storage_class(branch_id, iops=parameters.iops)
947+
data_iops = max(1, round(parameters.iops * (1 - WAL_IOPS_FRACTION)))
948+
storage_class_name = await ensure_branch_storage_class(branch_id, iops=data_iops)
946949
await restore_branch_database_volume_from_snapshot(
947950
source_branch_id=source_branch_id,
948951
target_branch_id=branch_id,
@@ -1022,7 +1025,8 @@ async def _restore_branch_environment_in_place_task(
10221025
return
10231026

10241027
try:
1025-
storage_class_name = await ensure_branch_storage_class(branch_id, iops=parameters.iops)
1028+
data_iops = max(1, round(parameters.iops * (1 - WAL_IOPS_FRACTION)))
1029+
storage_class_name = await ensure_branch_storage_class(branch_id, iops=data_iops)
10261030
await restore_branch_database_volume_from_snapshot(
10271031
source_branch_id=source_branch_id,
10281032
target_branch_id=branch_id,

src/deployment/__init__.py

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
AUTOSCALER_PVC_SUFFIX = "-block-data"
8080
AUTOSCALER_WAL_PVC_SUFFIX = "-pg-wal"
8181
PITR_WAL_PVC_SIZE = "100Gi"
82+
WAL_IOPS_FRACTION = 0.25
8283
_LOAD_BALANCER_TIMEOUT_SECONDS = float(600)
8384
_LOAD_BALANCER_POLL_INTERVAL_SECONDS = float(2)
8485
_OVERLAY_IP_TIMEOUT_SECONDS = float(300)
@@ -98,6 +99,10 @@ def branch_storage_class_name(branch_id: Identifier) -> str:
9899
return f"sc-{str(branch_id).lower()}"
99100

100101

102+
def branch_wal_storage_class_name(branch_id: Identifier) -> str:
103+
return f"sc-{str(branch_id).lower()}-wal"
104+
105+
101106
def deployment_branch(namespace: str) -> ULID:
102107
"""Return the branch ULID for a given deployment namespace."""
103108

@@ -355,14 +360,28 @@ async def resolve_branch_database_volume_size(branch_id: Identifier) -> int:
355360
async def update_branch_volume_iops(branch_id: Identifier, iops: int) -> None:
356361
namespace = deployment_namespace(branch_id)
357362

363+
data_iops = max(1, round(iops * (1 - WAL_IOPS_FRACTION)))
364+
wal_iops = max(1, round(iops * WAL_IOPS_FRACTION))
365+
358366
volume, _ = await resolve_autoscaler_volume_identifiers(namespace)
359367
try:
360368
async with create_simplyblock_api() as sb_api:
361-
await sb_api.update_volume(volume=volume, payload={"max_rw_iops": iops})
369+
await sb_api.update_volume(volume=volume, payload={"max_rw_iops": data_iops})
362370
except VelaSimplyblockAPIError as exc:
363371
raise VelaDeploymentError("Failed to update volume") from exc
364372

365-
logger.info("Updated Simplyblock volume %s IOPS to %s", volume, iops)
373+
logger.info("Updated Simplyblock data volume %s IOPS to %s", volume, data_iops)
374+
375+
try:
376+
wal_volume, _ = await resolve_autoscaler_wal_volume_identifiers(namespace)
377+
try:
378+
async with create_simplyblock_api() as sb_api:
379+
await sb_api.update_volume(volume=wal_volume, payload={"max_rw_iops": wal_iops})
380+
except VelaSimplyblockAPIError as exc:
381+
raise VelaDeploymentError("Failed to update WAL volume") from exc
382+
logger.info("Updated Simplyblock WAL volume %s IOPS to %s", wal_volume, wal_iops)
383+
except VelaDeploymentError as exc:
384+
logger.info("WAL volume not found for branch %s; skipping WAL IOPS update: %s", branch_id, exc)
366385

367386

368387
async def ensure_branch_storage_class(branch_id: Identifier, *, iops: int) -> str:
@@ -384,6 +403,43 @@ async def ensure_branch_storage_class(branch_id: Identifier, *, iops: int) -> st
384403
return storage_class_name
385404

386405

406+
async def ensure_branch_wal_storage_class(branch_id: Identifier, *, iops: int) -> str:
407+
wal_sc_name = branch_wal_storage_class_name(branch_id)
408+
try:
409+
await kube_service.get_storage_class(wal_sc_name)
410+
logger.info("WAL StorageClass %s already exists; reusing", wal_sc_name)
411+
return wal_sc_name
412+
except VelaKubernetesError:
413+
pass
414+
415+
base_storage_class = await kube_service.get_storage_class(SIMPLYBLOCK_CSI_STORAGE_CLASS)
416+
wal_iops = max(1, round(iops * WAL_IOPS_FRACTION))
417+
storage_class_manifest = _build_storage_class_manifest(
418+
storage_class_name=wal_sc_name,
419+
iops=wal_iops,
420+
base_storage_class=base_storage_class,
421+
)
422+
await kube_service.apply_storage_class(storage_class_manifest)
423+
return wal_sc_name
424+
425+
426+
def _load_compose_manifest() -> dict[str, Any]:
427+
compose_resource = resources.files(__package__).joinpath("compose.yml")
428+
compose_content = yaml.safe_load(compose_resource.read_text())
429+
if not isinstance(compose_content, dict):
430+
raise VelaDeploymentError("docker-compose manifest must be a mapping")
431+
return compose_content
432+
433+
434+
def _configure_compose_storage(compose: dict[str, Any], *, enable_file_storage: bool) -> dict[str, Any]:
435+
services = compose.get("services")
436+
if not isinstance(services, dict):
437+
raise VelaDeploymentError("docker-compose manifest missing 'services' mapping")
438+
if not enable_file_storage:
439+
services.pop("storage", None)
440+
return compose
441+
442+
387443
def _load_chart_values(chart_root: Any) -> dict[str, Any]:
388444
values_content = yaml.safe_load((chart_root / "values.yaml").read_text())
389445
if not isinstance(values_content, dict):
@@ -399,6 +455,7 @@ def _configure_vela_values(
399455
database_admin_password: str,
400456
pgbouncer_admin_password: str,
401457
storage_class_name: str,
458+
wal_storage_class_name: str,
402459
use_existing_db_pvc: bool,
403460
pgbouncer_config: Mapping[str, int] | None,
404461
enable_file_storage: bool,
@@ -453,7 +510,7 @@ def _configure_vela_values(
453510
wal_persistence = pg_wal_spec.setdefault("persistence", {})
454511
wal_persistence["create"] = not use_existing_db_pvc
455512
wal_persistence["size"] = PITR_WAL_PVC_SIZE
456-
wal_persistence["storageClassName"] = storage_class_name
513+
wal_persistence["storageClassName"] = wal_storage_class_name
457514
wal_persistence["claimName"] = wal_persistence.get("claimName") or (
458515
f"{_autoscaler_vm_name()}{AUTOSCALER_WAL_PVC_SUFFIX}"
459516
)
@@ -521,14 +578,17 @@ async def create_vela_config(
521578
postgresql_resource = resources.files(__package__).joinpath("postgresql.conf")
522579
values_content = _load_chart_values(chart)
523580

524-
storage_class_name = await ensure_branch_storage_class(branch_id, iops=parameters.iops)
581+
data_iops = max(1, round(parameters.iops * (1 - WAL_IOPS_FRACTION)))
582+
storage_class_name = await ensure_branch_storage_class(branch_id, iops=data_iops)
583+
wal_storage_class_name = await ensure_branch_wal_storage_class(branch_id, iops=parameters.iops)
525584
values_content = _configure_vela_values(
526585
values_content,
527586
parameters=parameters,
528587
jwt_secret=jwt_secret,
529588
database_admin_password=database_admin_password,
530589
pgbouncer_admin_password=pgbouncer_admin_password,
531590
storage_class_name=storage_class_name,
591+
wal_storage_class_name=wal_storage_class_name,
532592
use_existing_db_pvc=use_existing_db_pvc,
533593
pgbouncer_config=pgbouncer_config,
534594
enable_file_storage=parameters.enable_file_storage,
@@ -608,6 +668,7 @@ async def _delete_autoscaler_vm(namespace: str) -> None:
608668
async def delete_deployment(branch_id: Identifier) -> None:
609669
namespace, _ = get_autoscaler_vm_identity(branch_id)
610670
storage_class_name = branch_storage_class_name(branch_id)
671+
wal_sc_name = branch_wal_storage_class_name(branch_id)
611672
await cleanup_branch_dns(branch_id)
612673
await _delete_autoscaler_vm(namespace)
613674
try:
@@ -628,6 +689,13 @@ async def delete_deployment(branch_id: Identifier) -> None:
628689
logger.info("StorageClass %s not found", storage_class_name)
629690
else:
630691
raise
692+
try:
693+
await kube_service.delete_storage_class(wal_sc_name)
694+
except ApiException as exc:
695+
if exc.status == 404:
696+
logger.info("WAL StorageClass %s not found", wal_sc_name)
697+
else:
698+
raise
631699

632700

633701
def get_autoscaler_vm_identity(branch_id: Identifier) -> tuple[str, str]:

0 commit comments

Comments
 (0)