o2o-negosium-original/lps/docs/2026-08-07-session-notes.md
민헌 7dc925bcd4 docs(lps): 2026-08-07 세션 기록 — IP 로테이션 검수·네이버 UA 규명·결과 상태 정의
다음 세션이 이어받을 수 있게 남긴다.

- docs/2026-08-07-session-notes.md 신규: 세 덩어리(IP 로테이션 결함 6개, 네이버 컨테이너 차단이
  실은 UA 플랫폼 토큰이었다는 규명, '못 봤다'를 '없다'고 말하던 문제의 4단계 해결)와
  **되돌리기 전에 볼 근거**(예산을 IP 에 묶는 이유, 태우지 않는 판정, 임대 없이 크롤 금지,
  UA 는 플랫폼만 바꾸고 버전은 실값, 상태는 하나로 저장하고 표시에서 접기).
- 미해결: 쿠팡 간헐 차단 — IP 에 따라 갈리므로 환경이 아니라 평판. 쿠팡만 gate(해외 IP)를 쓰는 게
  유력한 원인이고, 네이버처럼 kr_host 로 바꾸는 검증이 중단된 상태.
- 다음 할 일 5가지(운영 마이그레이션 → 쿠팡 KR 검증 → 예산 튜닝 → AI 매칭 정확도 → admin 잡목록).
- 주의사항 5가지: **마이그레이션→코드 순서 강제**(역순이면 최저가 조회가 통째로 실패, 재현 확인),
  워커 켜고 pytest 하면 test_job_queue 3개 깨짐, JS 지문 위장은 patchright 가 막아 불가,
  로컬 negodata-front 는 node_modules 가 named volume 이라 재빌드로 의존성이 안 늘어남.
- README 문서 표에 링크 추가.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:02:25 +09:00

88 lines
6.6 KiB
Markdown
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.

# 2026-08-07 작업 기록 — IP 로테이션 검수 · 네이버 컨테이너 차단 규명 · 결과 상태 정의
다음 세션이 이어받을 수 있도록 **무엇이 왜 바뀌었고, 무엇이 남았는지**만 적는다.
상태 정의는 `result-states.md`, 운영 절차는 `operations.md` 가 소스다.
## 1. 무슨 일이 있었나
세 덩어리다. 앞의 둘은 "고장 났다"에서 출발했고, 셋째는 그 과정에서 드러난 설계 문제다.
**① IP 로테이션이 설계대로 안 돌고 있었다.** 검수해 보니 결함이 6개였고, 그중 하나는
`bot_detection.ip_request_no` 를 항상 `1` 로 만들어 **과거 진단 결론까지 오염**시켰다.
**② 컨테이너에서 네이버만 막혔다.** 원인은 컨테이너가 아니라 **UA 의 플랫폼 토큰**이었다.
네이버는 리눅스 데스크톱 Chrome 을 HTTP 405 로 거부한다.
**③ '못 봤다'를 '없다'고 말하고 있었다.** 차단당해 확인 못 한 몰이 화면에서 '없음(–)'과
구분되지 않았다. 상태를 정의하고 4단계로 화면까지 연결했다.
## 2. 지금 상태
| 항목 | 상태 |
|---|---|
| 네이버 | 컨테이너에서 정상(UA 보정). 검색당 40건 |
| 쿠팡 | **간헐적 차단** — 해외 IP(`gate`) 사용이 원인으로 추정(아래 4번) |
| IP 로테이션 | 예산·회전·소각이 모두 의도대로. 서킷브레이커가 풀 고갈 차단 |
| 결과 상태 | 몰별 7상태 → 운영자 화면은 그대로, 사용자 화면은 3가지로 접음 |
| 잡 | 한 소스가 막혀도 정상 종료(DEAD 아님) |
| 테스트 | lps 292 · negodata 107 passed |
| 배포 | 로컬 전체 재빌드·기동 완료. **운영은 마이그레이션 미적용**(아래 5번) |
## 3. 이번에 정한 것 (되돌리려면 근거부터 볼 것)
- **예산은 브라우저가 아니라 IP 에 묶는다.** 유휴 정리(120s)는 브라우저만 닫고 같은 IP 로
돌아오는데, 브라우저 기준으로 세면 카운터가 매번 초기화돼 **예산이 영영 발화하지 않는다**
(실측: 6회 검색이 전부 같은 포트·`ip_req#1`). 시계도 하나(`_session_started_at`)로 통일했다.
- **회전해도 소용없는 차단은 IP 를 태우지 않는다.** 서로 다른 IP 3개가 연속으로 **첫 요청부터**
막히면 IP 문제가 아니다(평판이면 몇 개는 통과하고, 과사용이면 뒤쪽에서 막힌다). 임계 없이
태우면 잡 16건에 100포트가 30분 쿨다운으로 묶인다. 성공 1회로 자동 해제된다.
- **임대 없이는 크롤하지 않는다.** 풀이 마르면 예전엔 계산식으로 포트를 골라 **남이 쥔 IP 를
같이 썼다**(실측 재현). 장부가 막으려던 바로 그 문제라, 못 잡으면 실패하는 게 맞다.
- **UA 는 플랫폼 토큰만 바꾸고 버전은 실제 값을 쓴다.** 통째로 하드코딩하면 컨테이너 Chrome 이
업데이트될 때 UA 와 엔진이 어긋나 그 불일치가 새 봇 신호가 된다. 쿠팡은 잘 통과하므로 안 건드린다.
- **상태는 하나로 정의·저장하고 표시 단계에서 접는다.** 저장을 단순화하면 운영자가 원인을 못 보고,
표시를 상세화하면 사용자가 못 읽는다. `partial` 을 컬럼으로 둔 것도 같은 이유 — 소비자가
'어떤 상태가 확인된 것인가'라는 판단 규칙까지 알면 상태 정의가 두 곳으로 흩어진다.
- **부분 실패의 `not_found` 는 네거티브 캐시에 넣지 않는다.** 못 본 몰에 있었을 수 있는데
'없음'으로 굳히면 TTL 동안 재검색이 막힌다(사용자가 '다시 검색'을 눌러도 캐시 히트).
## 4. 미해결 — 쿠팡 간헐적 차단
`사용권한이 제한된`(3.4KB)·`errors.edgesuite.net`(0.4KB) 마커로 막히는데 **IP 에 따라 갈린다**
(일부는 통과 → 서킷브레이커가 자동 해제). 즉 환경이 아니라 **IP 평판** 쪽이다.
유력한 원인: **쿠팡만 `gate`(국가 무지정 = 해외 IP)를 쓴다.** 네이버는 `kr_host`(한국 IP)로
바꾼 뒤 안정됐다. 같은 처방이 통할 가능성이 높다.
> 검증하려던 스크립트가 도구 시간 제한으로 중단됐다. 컨테이너에서 `gate` vs `kr` 통과율을
> IP 3개씩 비교하면 된다(네이버 UA 검증과 같은 방식).
⚠️ 맥 컨테이너는 amd64 를 **Rosetta 로 에뮬레이션**한다. 여기서 쿠팡이 막히는 건 실서버와
다를 수 있다 — 실서버에선 쿠팡이 60건 정상이었다. 판단은 실서버 로그로 한다.
## 5. 다음에 할 일 (우선순위)
1. **운영 DB 마이그레이션 적용** — 아직 안 됐다. 순서가 중요하다(아래 6번).
`6_lps_2026-08_dbeaver.sql`(lps_db) · `2026-08-07-iilp-source-state.sql`(negosium_db)
2. **쿠팡 KR 게이트웨이 검증** — 4번. 지금 최저가 커버리지의 최대 구멍이다.
3. **예산 튜닝** — F1 수정으로 `ip_request_no` 가 **처음으로 실제 사용량을 반영**한다.
`ip_req#1` 위주면 IP 평판, `2` 이상이면 예산 하향. **2026-08-06 이전 데이터는 쓰지 말 것.**
4. **AI 매칭 정확도 측정** — 세션 초반에 접근법만 논의하고 미착수. 정답 세트 20~30개로
"몇 % 맞나"를 재야 한다. 지금도 최저가 품질의 최대 병목이다.
5. lps-admin **잡 목록**에 몰별 상태 노출(상품 화면엔 이미 있음, 우선순위 낮음).
## 6. 작업 시 주의
- **마이그레이션 → 코드 순서를 지킬 것.** negodata 가 ORM 전체 엔티티를 조회하므로
(`lps_sync_crud.py:96`), 컬럼이 없는 DB 에 새 코드가 붙으면 **최저가 조회가 통째로 실패**한다
(재현 확인). 역순(마이그레이션 후 옛 코드)은 안전하다 — 옛 컨테이너로 실증했다.
- **워커를 켜 둔 채 pytest 하면 `test_job_queue` 3개가 깨진다.** 워커가 테스트 잡을 집어가서다.
코드 문제가 아니다 — `docker compose stop lps-worker` 후 돌리면 292 passed.
- **JS 로 브라우저 지문을 덮는 방법은 이 스택에서 통하지 않는다.** patchright 가
`add_init_script`(CDP 주입)를 무력화하고, MV3 확장도 값이 안 바뀐다. 다시 시도하기 전에
`services/search/fingerprint.py` docstring 을 볼 것.
- **로컬 dev 의 `negodata-front` 는 `node_modules` 가 named volume 이다.** 의존성이 늘면
이미지를 다시 빌드해도 반영되지 않는다 — 컨테이너 안에서 `npm install` 해야 한다(prod 은
`Dockerfile.prod` 라 해당 없음).
- 테스트가 실DB를 TRUNCATE 하는 문제는 그대로다(`price_history`·`job`·`ip_session`·`bot_detection`).