Go to file
민헌 89652fac1f [refactor] negodata/front: 인터넷 최저가 상세 시트 재설계 — 실구매가 중심 + 근거 검증
협상 근거로 쓰는 화면이라 "얼마에 살 수 있고, 그게 어디서 나온 값인가"를
한 번에 검증할 수 있게 정보 구조를 다시 짰다.

구조: 결론 → 판단 → 근거
  - 히어로: 합계(상품가+배송비) 32px + 단가 대비 판정을 한 블록에.
    면은 브랜드보다 채도를 낮춘 --accent 표면, 값만 브랜드 컬러(화면에 단 하나).
  - 판매처 비교표: 상품가/배송비/합계를 몰별로. 상품가 최저와 실구매가 최저가
    다를 수 있어 둘 다 표시. 결과 없는 몰도 '미발견' 행으로 남겨 빈손임을 드러낸다.
  - 검색어(찾은 상품명) + 복사 버튼 → 판매처에서 다시 찾아볼 때 붙여넣기용.
  - 가격 추이: 상품가 단일 기준. 합계 기준은 배송비를 아는 회차에만 값이 생겨
    (네이버는 배송비 미제공 — 실측 39/39) 기준을 바꾸면 값의 의미와 표본 수가
    동시에 변한다. 기준선(상품 단가) 추가 — 이 선 위면 사는 게 손해.
  - 수집 이력: 미발견도 노출(전체의 30%가 not_found 인데 그동안 숨겨져 있었다).

데이터 정직성
  - 배송비 미상(전체 69%)을 0원으로 뭉개지 않는다 — 무료배송으로 읽히면 안 된다.
    모르면 히어로 라벨이 '합계' → '상품가'로 바뀌고 그 사실을 문구로 밝힌다.
  - 화면 주인공(합계)과 목표가 산정에 쓰이는 값(상품가)이 다르므로 각주로 명시.
  - 금액 옆 배송 주석은 '+3,500'(연산으로 오해) → '배송비 포함(3,500원)'.

접근성·일관성
  - 대비 전수 계산: --accent 면 위에서 muted-fg(4.16)·amber-700(4.28)이 AA 미달이라
    accent-foreground(9.85)·800 계열(6.0~6.5)로 교체. 히어로는 큰 글씨 기준 통과.
  - rose(파괴적 뉘앙스) 제거 → 브랜드 인디고 한 톤. 장식 아이콘 전부 제거.
  - 타이포 7단계를 상수 T 로 고정(임의 크기 금지).
  - 차트 점 3종 모양을 DOT 상수로 추출해 차트와 범례가 공유 — 범례는 차트 밖이라
    --color-price 가 해석되지 않아 색이 어긋났다. 전역 토큰으로 통일.
  - Y축 눈금 1·2·5×10ⁿ, 기준 변경 시 애니메이션 끔, 커스텀 X축 tick 제거
    (recharts 의 가장자리 앵커 보정을 건너뛰어 점과 라벨이 어긋났다).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:06:27 +09:00
agent [fix] agent: 협상 투찰가 표시≠타결가 버그 + 카드 사용 횟수 미강제 수정 2026-07-24 11:41:33 +09:00
backend [feat] 공급사 포털: 협상완료 부가정보(select·종료 동의폼)·상태 라벨 통일·예아니오 Enter=예·목록 결과열·재협상 요청/철회 API·테스트 2026-07-24 14:18:52 +09:00
frontend [fix] negosium/front: 협상 챗 에러 말풍선 토스트(하단 짤림 수정) + 가격/할인율 입력 자동 포커스 2026-07-24 18:39:49 +09:00
landing [chore] landing: prod Dockerfile(SSG 빌드→nginx) 추가 2026-07-24 14:52:16 +09:00
lps [fix] lps: 네거티브 캐시 히트 시 이력 누락 수정 + force 재검색 지원 2026-07-28 11:55:23 +09:00
lps-admin feat(lps): docker-compose 에 lps-admin 추가 + 환경설정 config.local.toml 단일화 2026-07-16 11:54:32 +09:00
negodata [refactor] negodata/front: 인터넷 최저가 상세 시트 재설계 — 실구매가 중심 + 근거 검증 2026-07-28 16:06:27 +09:00
postgres-init [feat] negodata/backend: 몰별 최저가(by_mall) 노출 + 강제 재검색(force) 연동 2026-07-28 11:55:38 +09:00
schedules/anchoring [refactor] 공급유형 기준을 상품-협력사 매핑으로 전환 2026-07-07 17:05:07 +09:00
scripts [feat] negodata·공급사포털: 상품 필드 숨김 설정·용어 카탈로그 확장·로그인 전 브랜딩·모바일 대응 2026-07-23 11:29:35 +09:00
.gitignore Merge commit 'dc8c288f' — lps-admin 신설·LPS 운영기능(ip_session/alerts/admin API)·최저가 출처링크 반영 2026-07-24 15:30:36 +09:00
docker-compose.prod.landing.yml [chore] 작업분 일괄: logs.sh 컨테이너 메뉴화 · dbeaver 초기화 SQL · landing 운영 compose · negodata/front eslint 도입 · 업로드 샘플 CSV 2026-07-24 18:45:46 +09:00
docker-compose.yml [fix] lps-worker: compose 에 platform=linux/amd64 명시 (arm64 맥에서 이미지 빌드 실패 해소) 2026-07-28 09:16:19 +09:00
IMK_상품_업로드.csv [chore] 작업분 일괄: logs.sh 컨테이너 메뉴화 · dbeaver 초기화 SQL · landing 운영 compose · negodata/front eslint 도입 · 업로드 샘플 CSV 2026-07-24 18:45:46 +09:00
IMK_협력사_업로드.csv [chore] 작업분 일괄: logs.sh 컨테이너 메뉴화 · dbeaver 초기화 SQL · landing 운영 compose · negodata/front eslint 도입 · 업로드 샘플 CSV 2026-07-24 18:45:46 +09:00
logs.sh [chore] 작업분 일괄: logs.sh 컨테이너 메뉴화 · dbeaver 초기화 SQL · landing 운영 compose · negodata/front eslint 도입 · 업로드 샘플 CSV 2026-07-24 18:45:46 +09:00
o2o_상품_업로드.csv [chore] 작업분 일괄: logs.sh 컨테이너 메뉴화 · dbeaver 초기화 SQL · landing 운영 compose · negodata/front eslint 도입 · 업로드 샘플 CSV 2026-07-24 18:45:46 +09:00
o2o_협력사_업로드.csv [chore] 작업분 일괄: logs.sh 컨테이너 메뉴화 · dbeaver 초기화 SQL · landing 운영 compose · negodata/front eslint 도입 · 업로드 샘플 CSV 2026-07-24 18:45:46 +09:00
README.md refactor(db): postgres-init 2파일 체계로 통합 — 00-init.sql(스키마 전체) + temp-data.sql(시드) 2026-07-07 10:20:44 +09:00

O2O Negosium

동일 구조의 두 서비스(negosium, negodata)가 하나의 PostgreSQL 인스턴스를 공유한다.

구성

o2o-negosium/
├── docker-compose.yml          # 두 backend (DB 는 외부)
├── postgres-init/        # DB·테이블 셋업 SQL (대상 DB 에 1회 적용)
├── backend/                     # negosium 백엔드 (포트 9300)
├── negodata/backend/            # negodata 백엔드 (포트 9400)
├── agent/  front/               # (예정)
└── negodata/front/              # (예정)

두 백엔드는 같은 코드 골격(MVC · 람다 DB · Depends 주입 · JWT 로그인)을 쓴다. 아키텍처/패턴 상세는 각 서버 README 참고: backend · negodata/backend

DB 는 compose 밖 (config 로 연결)

DB 는 docker-compose 에서 관리하지 않는다. 각 backend 는 config.<APP_ENV>.toml 의 접속 정보대로 외부 PostgreSQL(호스트 로컬 postgres, 또는 따로 떠 있는 docker postgres)에 연결한다. 한 PostgreSQL 안에 서비스별 database 를 둔다.

PostgreSQL (외부, 5432)
├── negosium_db     ← negosium-backend
└── negodata_db     ← negodata-backend
  • 컨테이너(docker env)에서 호스트 DB 접근: host.docker.internal:5432 (config.docker.toml)
  • 로컬 실행/테스트(local·test env): 127.0.0.1:5432 (config.local/test.toml)
  • 계정/database 명은 config 에 맞춘다 (기본 postgres / password).
서비스 서버 docs database
negosium-backend http://localhost:9300 /docs negosium_db
negodata-backend http://localhost:9400 /docs negodata_db

빠른 시작

# 1) DB 준비 (최초 1회) — 사용할 PostgreSQL 에 스키마 + 시드 적용
psql -h 127.0.0.1 -p 5432 -U postgres -f postgres-init/00-init.sql     # 스키마 전체 (negosium_db + 도메인·learning·anchoring schema)
psql -h 127.0.0.1 -p 5432 -U postgres -f postgres-init/temp-data.sql   # 임시 데이터 시드 (admin / admin1234)

# 2) 백엔드 기동
docker compose up -d            # 두 backend (DB 는 config 대로 외부 연결)
docker compose logs -f
docker compose down

테스트

# config.test.toml 의 PostgreSQL(기본 127.0.0.1:5432) 이 떠 있어야 한다
cd backend            # 또는 negodata/backend
pip install pytest pytest-asyncio httpx
python -m pytest
  • httpx ASGITransport 로 네트워크 없이 앱을 직접 호출하는 e2e (각 5개).
  • DB_SESSION_MNG 싱글톤의 커넥션 풀이 첫 이벤트 루프에 묶이므로, 모든 테스트가 단일 session 루프를 공유한다(pytest.ini).

성능 / 벤치마크

/loginbcrypt(CPU 바운드) 가 비용의 대부분이다. 초기에는 bcrypt 가 asyncio 이벤트 루프를 막아 아무 일도 안 하는 /healthz 조차 p99 3.3s 가 나왔다.

두 가지 최적화

  1. bcrypt 를 asyncio.to_thread 로 오프로드 — 이벤트 루프 비차단. bcrypt 는 해싱 중 GIL 을 해제하므로 스레드들이 여러 코어에서 실제 병렬 실행된다.
  2. 워커 수 증가 (process_count 1 → 4) — login 처리량을 코어만큼 확장.

Before / After (동일 부하: 100 users)

지표 Before After 변화
/healthz median 1700ms 2ms 850배 개선
/healthz p99 3300ms 14ms 235배 개선
/me p99 3100ms 12ms 258배 개선
/login median 9900ms 220ms 45배 개선
/login p99 15000ms 1400ms 11배 개선
/login RPS 5.5 19.3 3.5배 개선
전체 RPS 17.6 75.2 4.3배 개선

최적화 후 Locust 차트 (100 users)

RPS 가 ~71 로 안정, p95 ~250ms(bcrypt), 실패 0%. median 은 초기 계정생성 버스트 후 바닥으로 떨어진다.

Locust Benchmark

login 은 여전히 가장 느리다(bcrypt 의 의도된 비용). 핵심은 그게 서버 전체를 막지 않는다는 점. 더 높은 처리량은 워커/인스턴스 수평 확장이 정석이다(bcrypt cost 낮추기는 보안 트레이드오프). ⚠️ to_thread 가 이미 단일 워커에서 멀티코어 병렬화를 하므로, 워커를 코어 수만큼 늘리면서 to_thread 까지 쓰면 워커 x 스레드 가 코어를 넘어 오버서브스크립션이 된다(워커는 코어의 절반 안팎).

부하 재현:

docker compose up -d
cd backend && pip install locust
python -m locust -f loadtest/locustfile.py --host http://localhost:9300 --headless -u 100 -r 10 -t 2m
# 부하 중 커넥션 모니터링: psql -h 127.0.0.1 -U postgres -c "SELECT count(*) FROM pg_stat_activity;"

기술 스택

FastAPI · SQLAlchemy(async) · asyncpg · PostgreSQL 16 · python-jose(JWT) · bcrypt · uvicorn · Docker Compose