설정이 .env(compose 주입)·config.toml·코드 곳곳의 os.environ 직독 3계층에 흩어져 관리가 어려웠다. TOML 하나로 통합한다(협의 결정). - 신설 [WorkerConfig](동시성·폴백·프로필·데드라인·유예·Chrome·하트비트), [AlertConfig](웹훅·쿨다운·임계 10종). [WebServerConfig].api_keys(guard), [DecodoConfig].ip_request_budget/port_cooldown_sec 추가 — 흩어져 있던 LPS_* env 20여 개를 섹션으로 흡수. - server_configs 의 env override 계층(DB_*·시크릿·NAVER_KEYS 등) 삭제. 남는 env 는 APP_ENV(부트스트랩)·PROCESS_COUNT/WORKER_CONCURRENCY(실행 스크립트 대화형 입력 전용)·LPS_LIVE(테스트 옵트인)뿐. - Docker: env 주입 → config.docker.toml 마운트 + APP_ENV=docker. 이미지 무시크릿 유지, 마운트 누락 시 FileNotFoundError 즉시 실패. .env.example 삭제, config.docker.toml.example 신설. - negodata 호출부: guard 키를 env 직독에서 [WebServerConfig].lps_api_key (+기존 관례대로 env override)로 이동. - 실행 스크립트: 프로필·폴백·예산 프롬프트 제거(toml 소스 안내), 동시성/프로세스 수만 임시 override 로 유지. - docs 7종·example toml 의 env 표기를 toml 키로 일괄 갱신. - 전체 145 passed + APP_ENV=docker 로딩·API 기동 스모크 확인. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
69 lines
3.5 KiB
Python
69 lines
3.5 KiB
Python
import os
|
||
|
||
from config.config_loader import Configs
|
||
from config.config_models import (
|
||
WebServerConfig, LogConfig, MainDBConfig, NaverConfig, OpenAIConfig, DecodoConfig,
|
||
WorkerConfig, AlertConfig,
|
||
)
|
||
|
||
# ── 설정 소스는 TOML 하나다(2026-07-13 협의 — env/.env 이중 관리 제거). ──
|
||
# env 는 부트스트랩·실행 입력만 남는다:
|
||
# APP_ENV : 어떤 toml 을 읽을지(local=호스트 실행 / docker=컨테이너에 마운트된 config.docker.toml)
|
||
# PROCESS_COUNT : 실행 스크립트·부하벤치의 대화형 입력(uvicorn 워커 수 임시 override)
|
||
# WORKER_CONCURRENCY : 워커 실행 스크립트의 대화형 입력(worker_main 이 읽음)
|
||
# LPS_LIVE : 라이브 스모크 테스트 옵트인(설정이 아니라 실행 스위치)
|
||
# 시크릿 포함 실값은 환경별 config.<APP_ENV>.toml(미커밋)에 두고, Docker 는 파일을 마운트한다.
|
||
APP_ENV = os.environ.get("APP_ENV", "local")
|
||
|
||
_config_dir = os.path.dirname(__file__)
|
||
_config_file = os.path.join(_config_dir, f"config.{APP_ENV}.toml")
|
||
|
||
if not os.path.exists(_config_file):
|
||
raise FileNotFoundError(
|
||
f"설정 파일이 없습니다: {_config_file} (APP_ENV={APP_ENV}). "
|
||
"호스트 실행은 config.local.toml 준비, 컨테이너는 config.docker.toml 마운트를 확인하세요."
|
||
)
|
||
|
||
configs = Configs(_config_file)
|
||
|
||
web_server_config: WebServerConfig = configs.get(WebServerConfig)
|
||
log_config: LogConfig = configs.get(LogConfig)
|
||
main_db_config: MainDBConfig = configs.get(MainDBConfig)
|
||
# 섹션이 없으면 기본값(빈/비활성)으로 동작.
|
||
naver_config: NaverConfig = configs.get(NaverConfig) or NaverConfig()
|
||
openai_config: OpenAIConfig = configs.get(OpenAIConfig) or OpenAIConfig()
|
||
decodo_config: DecodoConfig = configs.get(DecodoConfig) or DecodoConfig()
|
||
worker_config: WorkerConfig = configs.get(WorkerConfig) or WorkerConfig()
|
||
alert_config: AlertConfig = configs.get(AlertConfig) or AlertConfig()
|
||
|
||
|
||
def _autosize_pool(cfg: MainDBConfig, process_count: int):
|
||
"""process_count 기반 커넥션 풀 자동 산정.
|
||
|
||
실제 동시 커넥션 = (pool_size + max_overflow) × 2엔진(R/W) × process_count.
|
||
이 값이 connection_budget 를 넘지 않도록 pool_size/max_overflow 를 역산한다.
|
||
budget<=0 이면 비활성(toml 의 pool_size/max_overflow 그대로 사용).
|
||
|
||
반환: 자동 산정을 수행했으면 (pool_size, max_overflow), 아니면 None.
|
||
"""
|
||
budget = cfg.connection_budget
|
||
if budget <= 0:
|
||
return None
|
||
pc = max(1, process_count)
|
||
# 워커·엔진당 커넥션 = 예산 / (2엔진 × process_count). 최소 1(pool_size>=1) 보장.
|
||
# (예산 준수가 최우선 — process_count 가 너무 크면 엔진당 1커넥션까지 줄여서라도 예산을 넘지 않는다)
|
||
per_engine = max(1, budget // (2 * pc))
|
||
# 정상(pool) 60% + 버스트(overflow) 40% 로 분할.
|
||
cfg.pool_size = max(1, round(per_engine * 0.6))
|
||
cfg.max_overflow = max(0, per_engine - cfg.pool_size)
|
||
return cfg.pool_size, cfg.max_overflow
|
||
|
||
|
||
# uvicorn 워커 수 — 실행 스크립트/부하벤치의 대화형 입력만 env 로 임시 override(설정은 toml 이 소스).
|
||
if os.environ.get("PROCESS_COUNT"):
|
||
web_server_config.process_count = int(os.environ["PROCESS_COUNT"])
|
||
|
||
# 커넥션 풀 자동 산정: process_count(위에서 확정) 기준으로 예산 안에 맞춘다.
|
||
# → 멀티워커 배포 시 풀 오버서브스크립션(→커넥션 고갈)을 config 가 스스로 방지.
|
||
_pool_autosized = _autosize_pool(main_db_config, web_server_config.process_count)
|