diff --git a/schedules/anchoring/README.md b/schedules/anchoring/README.md index 302a78e..4751281 100644 --- a/schedules/anchoring/README.md +++ b/schedules/anchoring/README.md @@ -5,6 +5,7 @@ `schedules/anchoring` 아래에서 **완전 독립**으로 동작 — backend 코드를 import 하지 않는다. > 규범 문서: **`docs/개발용.md`** (정책: `docs/기획용.md`, 흐름 해설: `docs/워크플로우.md`, 타 팀 적용: `docs/인수인계.md`) +> **처음 오신 분 / 운영 담당자** → **`docs/운영및유지보수.md`** 부터 보세요 (설치·실행·로그 읽기·트러블슈팅). ## 경계 diff --git a/schedules/anchoring/docs/운영및유지보수.md b/schedules/anchoring/docs/운영및유지보수.md new file mode 100644 index 0000000..629f02c --- /dev/null +++ b/schedules/anchoring/docs/운영및유지보수.md @@ -0,0 +1,237 @@ +# 앵커링 서비스 — 운영 및 유지보수 가이드 + +> **대상 독자**: 이 프로젝트를 처음 보는 운영/개발 담당자. 이 문서 하나로 설치 → 실행 → 로그 확인 → 문제 해결까지 따라할 수 있게 쓰였습니다. +> **함께 볼 문서**: 무엇을 하는 시스템인지 → `기획용.md` / 흐름 그림 → `워크플로우.md` / 구현 규범 → `개발용.md` / 타 팀 적용 → `인수인계.md` + +--- + +## 목차 + +1. [이 서비스는 무엇인가](#1-이-서비스는-무엇인가) +2. [구성 요소 한눈에](#2-구성-요소-한눈에) +3. [처음 설치하고 실행하기](#3-처음-설치하고-실행하기) +4. [정상 동작 확인 체크리스트](#4-정상-동작-확인-체크리스트) +5. [로그 읽는 법](#5-로그-읽는-법) +6. [자주 하는 운영 작업](#6-자주-하는-운영-작업) +7. [문제 해결 (트러블슈팅)](#7-문제-해결-트러블슈팅) +8. [DB로 이력 추적하기](#8-db로-이력-추적하기) +9. [절대 하면 안 되는 것](#9-절대-하면-안-되는-것) +10. [정기 점검 체크리스트](#10-정기-점검-체크리스트) + +--- + +## 1. 이 서비스는 무엇인가 + +협상 시스템의 **앵커링 값**(협상 합의 기준선을 목표가에서 몇 % 아래에 둘지)을 **격주 토요일 00:00(KST)** 에 협상 성공률을 보고 자동 조정하는 배치 서비스입니다. + +- backend/negodata/agent 와 **완전히 독립**된 컨테이너로 돕니다. 이 서비스가 꺼져 있어도 협상·견적은 정상 동작합니다(값 조정만 멈춤). +- 켜두기만 하면 스케줄이 자동으로 돕니다. 사람이 정기적으로 할 일은 없고, 격주 배치 다음 날 로그 한 번 확인이 전부입니다(§10). + +## 2. 구성 요소 한눈에 + +``` +[anchoring 컨테이너] ──── 격주 배치 실행 (APScheduler 내장) + │ 읽기: negotiation.sessions / quotation.quotations / partner.items + │ 쓰기: anchoring.rate_adjustments (조정 이력) + sessions.anchoring_adjustment_id (채점 마킹) + ▼ +[PostgreSQL (외부, negosium_db)] [anchoring-redis 컨테이너] + 진실 원천 — 영구 이력 조회 캐시(사본) — 없어져도 복구됨 +``` + +| 구성 요소 | 역할 | 죽으면? | +|---|---|---| +| anchoring 컨테이너 | 격주 조정 배치 + 캐시 갱신 | 조정만 멈춤. 재기동 후 `--once`로 캐치업 | +| anchoring-redis | rate 조회 캐시 (negodata가 참조) | **무해** — 자동으로 DB 폴백, 복구 시 자가 회복 | +| PostgreSQL | 모든 데이터의 원본 | 서비스 전체 의존 (기존 DB 운영 정책에 따름) | + +## 3. 처음 설치하고 실행하기 + +### 사전 준비 + +- PostgreSQL(negosium_db) 접속 정보 (기존 `postgres-init/01~04` 스키마가 적용된 DB) +- Docker (운영) 또는 Python 3.12+ (로컬 개발) + +### STEP 1 — DB 스키마 적용 (최초 1회) + +```bash +cd schedules/anchoring +psql -h -U <계정> -d negosium_db -f schema.sql +``` + +- 테이블 1개(`anchoring.rate_adjustments`)와 `negotiation.sessions` 컬럼 3개를 추가합니다. +- `IF NOT EXISTS` 라 **여러 번 실행해도 안전**합니다. + +### STEP 2 — 설정 채우기 + +```bash +cp config.toml.example config.toml +# config.toml 열어서 [db] 호스트/계정/비밀번호 채우기 +``` + +환경변수로 덮어쓸 수도 있습니다(우선순위: env > config.toml > 기본값): +`DB_HOST` `DB_PORT` `DB_USER` `DB_PASSWORD` `DB_NAME` / `REDIS_HOST` `REDIS_PORT` `REDIS_PASSWORD` / `LOG_LEVEL` + +### STEP 3-A — 도커로 실행 (운영 권장) + +```bash +docker compose up -d --build +docker logs -f anchoring # 기동 로그 확인 (아래 §4) +``` + +redis 가 함께 뜨고, 로그 로테이션(10MB×5)·재시작 정책까지 자동 설정됩니다. + +### STEP 3-B — 로컬 파이썬으로 실행 (개발용) + +```bash +python3 -m venv .venv && .venv/bin/pip install -r requirements.txt +PYTHONPATH=src .venv/bin/python -m anchoring.main # 스케줄러 상주 +# 또는 +PYTHONPATH=src .venv/bin/python -m anchoring.main --once # 배치 즉시 1회 실행 후 종료 +``` + +## 4. 정상 동작 확인 체크리스트 + +기동 직후 로그에 아래 3줄이 순서대로 보이면 정상입니다: + +``` +[main] 정적 기본 테이블 로드·검증 완료 (33,334칸) +[scheduler] 등록 — 매주 토 00:00 Asia/Seoul (격주 게이트는 잡 내부) +[main] 스케줄러 상주 시작 — 다음 실행 예정: 2026-07-04 00:00:00+09:00 +``` + +배치가 실제로 도는지 즉시 확인하고 싶으면: + +```bash +PYTHONPATH=src .venv/bin/python -m anchoring.main --once +# 도커: docker exec anchoring python -m anchoring.main --once +``` + +마지막 줄에 `종료 {'run_id': ..., 'status': 'done', ...}` 가 나오면 성공입니다. +(협상 데이터가 없으면 `scanned: 0` — 이것도 정상) + +테스트 스위트로 확인하려면: + +```bash +PYTHONPATH=src .venv/bin/python -m pytest tests/ -q # 15 passed 기대 +``` + +## 5. 로그 읽는 법 + +### 로그 한 줄의 구조 + +``` +2026-07-02 16:45:12+0900 INFO anchoring [batch 20260702-164512] 조정 company=f23c… type=1 bracket=10 n=13 성공=8 10‰→30‰ adj_id=32 +└──── 시각(항상 KST) ──┘ └레벨┘ └── 회차 태그 ──────┘ └──────────────── 내용 (key=value 형식) ────────────────┘ +``` + +- **시각은 항상 한국시간(+0900)** — 서버 시간대와 무관하게 고정돼 있습니다. +- `[batch 20260702-164512]` = **회차 태그**(run_id, 배치 시작 시각). 한 회차의 모든 로그가 같은 태그를 답니다. +- `‰`(천분율) 표기: `10‰ = 1%`. `10‰→30‰` 는 "1%에서 3%로 올렸다"는 뜻. + +### 회차 하나의 로그 흐름 (위에서 아래로) + +| 라인 | 의미 | +|---|---| +| `시작 — ISO 주차 27, force=False` | 배치 깨어남. force=True 는 수동 실행(`--once`) | +| `캐시 re-SET n칸` | 조정 이력 있는 칸 전체를 Redis 에 다시 적재(매주, 캐시 자가 회복) | +| `격주 게이트 미충족 — 평가 스킵` | 이번 주는 쉬는 주(격주). **정상 동작** | +| `제외 확정 마킹 n건` | 가격을 안 써낸 협상들을 채점 대상에서 영구 제외 처리 | +| `조정 company=… n=13 성공=8 10‰→30‰ adj_id=32` | **칸 하나의 값이 조정됨** — adj_id 로 DB 행과 대조 가능 | +| `회사요약 company=… 평가=1 상승=1 …` | 회사(테넌트)별 이번 회차 집계 | +| `종료 {…}` | 회차 전체 요약(스캔 건수, 평가 칸 수, 이월 등) | + +### 자주 쓰는 검색 명령 + +```bash +docker logs anchoring | grep "batch 20260705" # 특정 회차 전체 보기 +docker logs anchoring | grep "company=" # 특정 회사만 (조정 + 회사요약) +docker logs anchoring | grep -E "WARNING|ERROR" # 이상 신호만 +docker logs anchoring | grep "조정 " # 값이 바뀐 칸만 +docker logs anchoring | tail -20 # 최근 상태 +``` + +### 레벨별 대응 기준 + +| 레벨 | 의미 | 대응 | +|---|---|---| +| INFO | 정상 동작 기록 | 조치 불필요 | +| WARNING | 동작은 하지만 점검 필요 | §7 트러블슈팅에서 해당 메시지 찾기 | +| ERROR | 칸 단위 실패(다른 칸엔 영향 없음) | 스택 확인. 실패 칸은 다음 회차 자동 재시도 | + +**핵심 규칙: WARNING 이상이 하나라도 있으면 들여다본다. INFO 뿐이면 건강하다.** + +## 6. 자주 하는 운영 작업 + +| 작업 | 명령 | +|---|---| +| 수동 배치 1회 (격주 게이트 무시) | `docker exec anchoring python -m anchoring.main --once` | +| 재기동 | `docker compose restart anchoring` | +| 서비스 중지/시작 | `docker compose stop` / `docker compose up -d` | +| 설정 변경 반영 | config.toml 수정 → `docker compose up -d --build` | +| 다음 실행 예정 시각 확인 | `docker logs anchoring \| grep "다음 실행 예정"` | +| 로그 레벨 올리기(디버깅) | env `LOG_LEVEL=debug` 로 재기동 | + +## 7. 문제 해결 (트러블슈팅) + +| 증상 (로그 메시지) | 원인 | 조치 | +|---|---|---| +| 기동 실패 + `BaseTableError: 정적 테이블 …` | `resources/anchoring_base.json` 손상/수정됨 | **의도된 안전장치** — git 으로 파일 원복 후 재기동. 이 파일은 절대 수정 금지 | +| 기동 실패 + DB 연결 예외 | config.toml/env 의 DB 접속 정보 오류 | 접속 정보 확인, `psql` 로 직접 접속 테스트 | +| `[redis] GET/SET 실패 … DB 폴백` WARN | Redis 다운/네트워크 | **서비스는 계속 정상 동작**(DB 폴백). `docker compose up -d anchoring-redis` 로 복구하면 다음 실행 때 캐시 자동 재적재 | +| `redis 실패 누계 get=… set=…` WARN | 위와 동일(회차 요약) | 위와 동일 | +| `가격 제시 흔적 0%` WARN | backend 의 가격 기록 배선이 끊김(배포 사고 등) — 학습이 조용히 멈추는 신호 | backend 팀에 `chat_service` 의 `last_offered_price` 갱신 경로 점검 요청 | +| `칸 평가 실패 company=…` ERROR | 해당 칸 DB 오류/마킹 경합 | 스택 확인. 실패 칸은 마킹되지 않아 **다음 회차 자동 재시도** — 같은 칸이 연속 실패하면 개발 팀 문의 | +| 종료 요약이 WARNING (`failed_cells > 0`) | 일부 칸 실패 | 바로 위 ERROR 라인들 확인 | +| 토요일 00:00 에 서비스가 꺼져 있었음 | 배치 회차 누락 | 데이터 유실 없음(자동 이월). 재기동 후 `--once` 로 즉시 캐치업 | +| 로그가 아무것도 안 나옴 | 컨테이너 죽음 | `docker ps -a` 로 상태 확인 → `docker logs anchoring` 마지막 로그 → 재기동 | + +## 8. DB로 이력 추적하기 + +로그는 로테이션되지만 **DB 이력은 영구**입니다. "왜 이 값이 됐는가"는 항상 DB로 답할 수 있습니다. + +```sql +-- ① 어떤 회사의 값 변천사 (시간순) +SELECT id, created_at, supplier_type, price_bracket_index, + nego_count, success_count, anchor_rate_before, anchor_rate_after +FROM anchoring.rate_adjustments +WHERE company_id = '' +ORDER BY id; + +-- ② 특정 조정(adj_id)의 근거가 된 협상들 +SELECT s.session_id, s.status, s.target_anchoring_price, s.last_offered_price, s.bid_price +FROM negotiation.sessions s +WHERE s.session_id IN ( + SELECT jsonb_array_elements_text(consumed_session_ids)::uuid + FROM anchoring.rate_adjustments WHERE id = +); + +-- ③ 특정 협상이 어느 조정에 채점됐나 +SELECT anchoring_adjustment_id FROM negotiation.sessions WHERE session_id = ''; +-- NULL = 아직 채점 전(다음 회차로 이월) / 0 = 채점 제외 확정 / 숫자 = 해당 조정 id → ② 로 +``` + +로그의 `adj_id=32` ↔ DB 의 `rate_adjustments.id=32` 가 같은 것을 가리킵니다. + +## 9. 절대 하면 안 되는 것 + +이 시스템의 신뢰성은 "기록이 불변"이라는 전제 위에 서 있습니다 (상세 근거: `개발용.md` §12). + +- ❌ `anchoring.rate_adjustments` 행을 **UPDATE/DELETE** — 조정 이력은 유일한 진실 원천 +- ❌ `sessions` 의 `target_anchoring_price` / `anchor_rate_permille` 수동 수정 — 채점 근거가 오염됨 +- ❌ `resources/anchoring_base.json`(기준표) 수정 — 검증 실패로 기동이 막히며, 값 변경은 정책 재확정 사안 +- ❌ 상수(조정폭 δ, 경계 60/30, 상·하한, 10건 임계, 배치 주기) 임의 변경 — 전부 정책 고정값 +- ❌ anchoring 컨테이너를 **2개 이상 동시 실행** — 중복 조정 방지 장치(롤백)가 막아주긴 하지만 설계상 단일 인스턴스가 원칙 + +## 10. 정기 점검 체크리스트 + +**격주 배치 다음 날(일요일) 5분 점검:** + +```bash +docker logs anchoring | grep -E "WARNING|ERROR" | tail # ① 이상 신호 없나 +docker logs anchoring | grep "종료" | tail -1 # ② status: done 인가 +docker logs anchoring | grep "다음 실행 예정" # ③ (재기동했다면) 다음 스케줄 정상인가 +``` + +- ① 이 비어 있고 ② 가 `'status': 'done'` 이면 끝. +- `carryover_cells`(이월)가 계속 크기만 하고 `evaluated_cells` 가 0인 상태가 몇 달 지속되면 거래량 자체가 적은 것 — 장애가 아니라 정책 검토(희소 칸 과제, `기획용.md` FAQ) 대상입니다. +- 분기에 한 번쯤: 조정 이력 백업이 DB 백업 정책에 포함돼 있는지 확인 (`rate_adjustments` 는 영구 보존 대상).