From 48e03e9d8a07d0e1febf058ec2940fb55924dedb Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 6 Sep 2026 21:57:10 +0900 Subject: [PATCH] =?UTF-8?q?perf(B04):=20=EC=9C=A0=EC=97=AD=20=EC=A4=80?= =?UTF-8?q?=EB=B9=84=EC=9D=98=20=EC=88=9C=EC=B0=A8=20=EC=A7=88=EC=9D=98=20?= =?UTF-8?q?3=EA=B1=B4=EC=9D=84=20=ED=95=A8=EA=BB=98=20=EB=B3=B4=EB=83=84?= =?UTF-8?q?=20+=20=ED=97=AC=ED=8D=BC=20=ED=95=9C=20=EA=B3=B3=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DB 가 원격이라 질의 하나가 곧 왕복 12ms 임. 서로 기다릴 이유가 없는 읽기를 한 커넥션에서 순차로 내면 그 왕복이 그대로 더해짐. - config_db.run_with_connection: 저장소 함수를 자기 커넥션으로 돌려 gather 로 묶을 수 있게 하는 공용 헬퍼. 정의를 한 곳에 둠(drainage_context 의 _query 는 이 함수를 가리키는 이름으로 정리). 머리에 경고 적음 — 순서가 필요한 쓰기는 이걸로 묶으면 트랜잭션이 깨짐. - B04_PreProcess_Router_Watershed._prepare: 저장경로·좌표계·지표면확정값 세 건을 gather 로. 약 24ms 절약. common_util_auth_repository.py:318(decide_join_request)은 FOR UPDATE + 순서 있는 UPDATE 라 묶지 않음 — 읽기 블록만 대상. 자체검증 — 관 드래그 왕복 305 -> 259ms(최소 193), 관 11 · 유역 11 로 값 동일. 전체 484 통과. Co-Authored-By: Claude Opus 5 (1M context) --- .../B04_PreProcess_Router_Watershed.py | 14 ++++++++------ common_util/common_util_drainage_context.py | 12 +++--------- config/config_db.py | 18 +++++++++++++++++- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/B04_PreProcess/B04_PreProcess_Router_Watershed.py b/B04_PreProcess/B04_PreProcess_Router_Watershed.py index 1ac02733..aec83a12 100644 --- a/B04_PreProcess/B04_PreProcess_Router_Watershed.py +++ b/B04_PreProcess/B04_PreProcess_Router_Watershed.py @@ -58,7 +58,7 @@ from common_util.common_util_wamis_station import ( build_station_rainfall_table, is_jeju, ) -from config.config_db import get_db_pool +from config.config_db import get_db_pool, run_with_connection from config.config_system import ( DRAINAGE_ARROW_SPACING_M, DRAINAGE_DESIGN_RETURN_PERIOD_YR, @@ -172,11 +172,13 @@ async def _prepare(project_id: UUID) -> dict[str, Any] | JSONResponse: 잘라 프로젝트 좌표계로 돌려준다. 원본을 그대로 쓰면 유역·관이 확정 노선 밖에도 찍히고 좌표계마저 갈린다(2026-09-01 실측: 관은 5179, 노선은 5176이었다). """ - pool = get_db_pool() - async with pool.acquire() as connection: - stored_path = await get_project_storage_relative_path(connection, project_id) - epsg = await get_surface_crs_epsg(connection, project_id, 0) - surface_params = await get_surface_confirmation_params(connection, str(project_id)) + # 셋은 서로 기다릴 이유가 없다 — DB 가 원격이라 순차로 내면 왕복 12ms 가 세 번 붙는다 + # (2026-09-06 실측). 커넥션을 갈라 같이 보낸다. + stored_path, epsg, surface_params = await asyncio.gather( + run_with_connection(get_project_storage_relative_path, project_id), + run_with_connection(get_surface_crs_epsg, project_id, 0), + run_with_connection(get_surface_confirmation_params, str(project_id)), + ) project_root = Path(resolve_stored_project_path(stored_path)) route_file = find_planned_route_file(_route_input_dir(stored_path)) diff --git a/common_util/common_util_drainage_context.py b/common_util/common_util_drainage_context.py index a9bda0c5..662be1ed 100644 --- a/common_util/common_util_drainage_context.py +++ b/common_util/common_util_drainage_context.py @@ -37,7 +37,7 @@ from common_util.common_util_route_profile import Z_SOURCE_CSV, resolve_route_pr from common_util.common_util_storage import resolve_stored_project_path from common_util.common_util_surface_confirmation import get_surface_confirmation_params from common_util.common_util_surface_sampler import build_surface_sampler -from config.config_db import get_db_pool +from config.config_db import run_with_connection logger = logging.getLogger(__name__) @@ -63,14 +63,8 @@ class DrainageContext: surface_params: dict[str, Any] = field(default_factory=dict) -async def _query(repository_call: Callable[..., Any], *args: Any) -> Any: - """저장소 함수 하나를 **자기 커넥션**으로 실행한다 — 같이 보내려면 커넥션이 갈려야 한다. - - 풀 최대치가 20이라 여기서 서너 개를 동시에 잡아도 여유가 있다(`config_system.DB_POOL_MAX`). - """ - pool = get_db_pool() - async with pool.acquire() as connection: - return await repository_call(connection, *args) +# 「자기 커넥션으로 하나씩 돌려 `gather` 로 묶는다」는 정의는 `config_db` 한 곳에 둔다. +_query = run_with_connection async def load_drainage_context(project_id: UUID) -> tuple[DrainageContext | None, str]: diff --git a/config/config_db.py b/config/config_db.py index 1fad1da8..f570c4da 100644 --- a/config/config_db.py +++ b/config/config_db.py @@ -5,7 +5,8 @@ config_db.py 비동기 연결 풀 생성 및 관리. """ -from typing import Optional +from collections.abc import Callable +from typing import Any, Optional import aiomysql @@ -58,3 +59,18 @@ def get_db_pool() -> aiomysql.Pool: if not db_pool: raise RuntimeError("DB pool not initialized. Call init_db_pool() first.") return db_pool + + +async def run_with_connection(repository_call: Callable[..., Any], *args: Any) -> Any: + """저장소 함수 하나를 **자기 커넥션**으로 실행한다 — `asyncio.gather` 로 묶기 위한 것. + + DB 가 원격이라 질의 하나가 곧 왕복 약 12ms 다(2026-09-06 실측). 서로 기다릴 이유가 없는 + 읽기를 한 커넥션에서 순차로 내면 그 왕복이 그대로 더해진다. 커넥션을 갈라 같이 보내면 + 가장 느린 하나의 시간만 든다. 풀 최대치는 `DB_POOL_MAX`(기본 20). + + ⚠ **읽기에만 쓸 것.** 순서가 필요한 쓰기(한 트랜잭션 안의 UPDATE 들)를 이걸로 묶으면 + 커넥션이 갈려 트랜잭션이 깨진다. + """ + pool = get_db_pool() + async with pool.acquire() as connection: + return await repository_call(connection, *args)