o2o-negosium-original/docker-compose.yml
민헌 9c2432c818 feat(lps): process_count 기반 커넥션 풀 자동 산정 — 멀티코어 풀 오버서브스크립션 방지
connection_budget(기본 40)을 두고, 기동 시 process_count 에 맞춰
pool_size/max_overflow 를 역산: (pool+overflow)×2엔진×process_count ≤ budget.
워커를 늘려도 config 가 스스로 예산을 지켜 커넥션 고갈→요청 실패를 예방.

- config_models: MainDBConfig.connection_budget 추가
- server_configs: _autosize_pool(process_count 확정 후 산정) + _apply_pool_env_override
  (우선순위 = 명시 DB_POOL_SIZE > 자동 산정 > toml pool)
- web_main: 기동 로그에 실효 풀/총커넥션/예산 출력
- env: DB_CONNECTION_BUDGET override, docker-compose 에 knob 노출
- tests: test_pool_autosize 10케이스(예산 준수·분할비·비활성·infeasible 바닥)
- docs: operations 2-1 멀티코어/풀 섹션 + loadtest README 자동산정 표

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:59:05 +09:00

169 lines
6.9 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Negosium + Negodata 백엔드.
# DB 는 compose 에서 관리하지 않는다 — 각 backend 는 config.docker.toml 의 접속 정보대로
# 외부 PostgreSQL 에 연결한다(호스트에 떠 있는 로컬 postgres, 또는 따로 실행 중인 docker postgres).
# 컨테이너에서 호스트의 DB 에 접속할 때는 host.docker.internal 을 쓴다.
#
# docker compose up -d
# negosium 서버: http://localhost:9300/docs
# negosium 프론트: http://localhost:3300
# negodata 서버: http://localhost:9400/docs
# agent 서버: http://localhost:9500/docs
# anchoring 배치: 포트 없음 — 상주 스케줄러(격주 토 00:00 KST), docker logs anchoring 으로 확인
#
# DB 준비(최초 1회): postgres-init 의 SQL 을 대상 DB 에 적용한다.
# psql -h <host> -p <port> -U <user> -f postgres-init/00-init.sql (스키마 전체: negosium_db + 도메인·learning·anchoring schema)
# psql -h <host> -p <port> -U <user> -f postgres-init/temp-data.sql (임시 데이터 시드: admin / admin1234, company.users)
services:
negosium-backend:
build: ./backend
container_name: negosium-backend
environment:
APP_ENV: local
DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override)
AGENT_BASE_URL: http://host.docker.internal:9500 # 컨테이너→호스트 agent (로컬 uvicorn :9500)
ports:
- "9300:9300"
# 컨테이너에서 호스트의 DB 로 접근 (config.docker.toml 의 host.docker.internal)
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
negodata-backend:
build: ./negodata/backend
container_name: negodata-backend
environment:
APP_ENV: local
DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override)
RELOAD: "1" # uvicorn --reload 활성 → 소스 저장 시 자동 재기동(재빌드 불필요)
SCHEDULER_ENABLED: "1" # 마감 크론 활성(단일 워커라 중복 없음). 운영 다중 워커면 1개 프로세스에서만 1
PYTHONUNBUFFERED: "1" # 컨테이너 로그 실시간 출력(stdout 버퍼링 끔)
volumes:
- ./negodata/backend:/app # 호스트 소스 = 컨테이너 코드. 이게 있어야 수정이 즉시 반영됨
ports:
- "9400:9400"
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
# negodata 프론트 (Vite dev 서버).
negodata-front:
build: ./negodata/front
container_name: negodata-front
ports:
- "3000:3000"
volumes:
- ./negodata/front:/app
- /app/node_modules
restart: unless-stopped
# 협상 에이전트 (negosium_db 공유, learning 스키마 사용).
agent:
build: ./agent
container_name: negosium-agent
environment:
APP_ENV: local
DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override)
ports:
- "9500:9500"
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
# negosium 공급사 프론트 (프로덕션 빌드 정적 서빙, :3300). 브라우저가 backend(:9300)를 직접 호출.
negosium-front:
build: ./frontend
container_name: negosium-front
ports:
- "3300:3300"
restart: unless-stopped
# 앵커링 값 자동 조정 배치 (negosium_db 공유, 포트 없음 — 상주 스케줄러).
anchoring:
build: ./schedules/anchoring
container_name: anchoring
environment:
APP_ENV: local # config.{APP_ENV}.toml 선택 (local/dev/prod)
DB_HOST: host.docker.internal # 컨테이너→호스트 DB
REDIS_HOST: anchoring-redis
TZ: Asia/Seoul
volumes:
- ./schedules/anchoring/config.local.toml:/app/config.local.toml:ro # 시크릿은 마운트 — up 전에 파일 필요
depends_on:
- anchoring-redis
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
logging: # 상주 배치 — 장기 운영 디스크 보호
driver: json-file
options:
max-size: "10m"
max-file: "5"
# 앵커링 조회 캐시 (anchoring 전용)
anchoring-redis:
image: redis:7-alpine
container_name: anchoring-redis
ports:
- "127.0.0.1:6380:6379" # 호스트 로컬만 개방 (무인증 Redis). 6380 = 호스트 redis(6379)와 충돌 회피
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
# ── LPS (인터넷 최저가 검색) ──────────────────────────────────
# API(요청 접수, lean) + 워커(크롤, 헤드풀 Chromium+Xvfb). DB 는 외부(host.docker.internal).
lps-api:
build:
context: ./lps
dockerfile: Dockerfile
container_name: lps-api
environment:
APP_ENV: local
DB_HOST: host.docker.internal # 컨테이너→호스트 DB (config.local.toml의 127.0.0.1 override)
PYTHONUNBUFFERED: "1"
# 멀티코어: PROCESS_COUNT(uvicorn 워커=코어수)를 올리면 커넥션 풀은 자동 산정된다.
# (pool+overflow)×2엔진×PROCESS_COUNT ≤ DB_CONNECTION_BUDGET 를 config 가 스스로 보장.
# PROCESS_COUNT: "4"
# DB_CONNECTION_BUDGET: "40" # 전용 PG(max_connections≈100)면 90 근처로 상향
ports:
- "9600:9600"
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
logging:
driver: json-file
options: { max-size: "10m", max-file: "5" }
lps-worker:
build:
context: ./lps
dockerfile: Dockerfile.worker # Chromium + Xvfb (headless 는 안티봇에 탐지됨)
container_name: lps-worker
environment:
APP_ENV: local
DB_HOST: host.docker.internal
PYTHONUNBUFFERED: "1"
WORKER_CONCURRENCY: "1" # 상품 동시 검색 수(워커별 브라우저 세트, Chrome 4×N)
LPS_PROFILE_DIR: /profiles # Chrome 프로필을 영속 볼륨에 → 재시작해도 cf_clearance 유지(재웜업 회피)
# ── 프로덕션 시크릿 주입(이미지에 안 굽고 env 로). 아래 주석 해제 후 값 채우거나 secrets 매니저 연동 ──
# OPENAI_API_KEY: ${OPENAI_API_KEY}
# DECODO_USERNAME: ${DECODO_USERNAME}
# DECODO_PASSWORD: ${DECODO_PASSWORD}
# DECODO_HOST: ${DECODO_HOST}
# NAVER_KEYS: ${NAVER_KEYS} # "id1:secret1,id2:secret2"
volumes:
- lps-profiles:/profiles # Chrome 프로필(쿠키) 영속
extra_hosts:
- "host.docker.internal:host-gateway"
shm_size: "1gb" # Chrome 는 /dev/shm 을 많이 씀 — 부족하면 탭 크래시
restart: unless-stopped
logging:
driver: json-file
options: { max-size: "10m", max-file: "5" }
volumes:
lps-profiles: