Repository navigation
Description
배경/문제
#189에서 올리브영 조회가 전면 실패하는 원인을 정리했습니다. 이 이슈는 실제로 동작하게 만들기 위해 무엇이 필요한지를 코드 기준으로 정리합니다.
차단 주체가 Cloudflare Managed Challenge로 확인됐습니다. 올리브영 엔드포인트에 직접 요청한 응답 헤더입니다.
HTTP/1.1 403 Forbidden
Cf-Mitigated: challenge
Server: cloudflare
Content-Type: text/html (49KB)
Critical-Ch: Sec-CH-UA-*
통과하려면 챌린지 JS를 실행해 cf_clearance 쿠키를 받아야 하고, 그 쿠키는 IP·UA·TLS 지문에 묶입니다. 따라서 다음은 모두 불가능합니다.
- 헤더·UA 위장
- 브라우저에서 쿠키만 뽑아 Worker에서 재사용 (Worker IP가 달라 무효)
- headless 브라우저 (
docs/zyte-free-access.md실측에서 이미 차단)
scripts/relay/browser.ts가 통과하는 이유도 같은 맥락입니다. page.evaluate() 안에서 credentials: 'include'로 호출해 올리브영 페이지와 동일 출처가 되므로 브라우저가 보유한 cf_clearance가 자동으로 실립니다.
결국 선택지는 실제 브라우저 세션 경유(자체 릴레이) 또는 주거용 IP + JS 렌더링을 파는 상용 API 둘뿐입니다.
제안 내용
먼저 결정이 필요합니다. 유료 API(비용 재개) vs 자체 릴레이(Mac 상시 운영). 코드는 어느 쪽이든 src/services/oliveyoung/transport.ts 한 지점에서 갈립니다.
경로 A: 릴레이 운영화 (코드는 대부분 존재, 연결이 빠짐)
이미 있는 것: scripts/relay/ 993줄(서버·인증 핸들러·브라우저 수명 관리·프로세스 회수·macOS 설치)과 Worker 측 배선(transport.ts, handlers.ts, index.ts의 릴레이 분기 및 Access 헤더 주입).
막아야 할 구멍 7가지입니다.
- Secrets 미동기화 —
.github/workflows/sync-worker-secrets.yml에OY_RELAY_URL,OY_RELAY_TOKEN,OY_ACCESS_CLIENT_ID,OY_ACCESS_CLIENT_SECRET이 없습니다. - 네트워크 경로 부재 (운영 작업) —
Mac 127.0.0.1:4319→ Tunnel → Access(Service Auth) → Worker. 전용 계정, GUI 로그인, DNS, Access 정책은 사람이 구성해야 합니다. - 오류 의미 붕괴 —
transport.ts가expectedStatus: 200, retries: 0으로 호출해, 릴레이의 429(쿼터)·503(busy/만료)·502(업스트림)가 모두올리브영 브라우저 릴레이 요청 실패한 문장으로 뭉개집니다.src/core/errors.ts도 설정 오류만 구분합니다. 재시도 판단·백오프·점검 신호 분리가 불가능합니다. - 재고 조회가 릴레이 용량을 즉시 초과합니다 (최우선) — 릴레이는 브라우저 1개로 완전 직렬 처리하고(
lifecycle.ts의active플래그 +tail체인), 분당 30회·일 3,000회 상한이 있습니다. 반면handleOliveyoungCheckInventory1회는 상품검색 1 + 매장검색 1 + 상품당goodsInfo1 +stock-stores1을Promise.all로 날려stockCheckLimitN일 때 2N+2 요청이 동시에 몰립니다. 8슬롯 초과 503과 15초 대기 만료가 다발합니다. MCP 쪽에 동시성 제한·큐·429 백오프가 없습니다.client.ts의Promise.all을 동시성 1~2로 제한하고stockCheckLimit기본값을 낮춰야 합니다. - 설정 여부가 점검에 안 보입니다 —
src/api/configStatus.ts에 올리브영 릴레이 항목이 없어/health가ok를 주면서 미설정을 드러내지 못합니다. 릴레이 자체의 인증된GET /health도 외부 점검에 연결돼 있지 않습니다. - 헬스체크 요약 잘림 —
.github/workflows/health-checks.yml의lines.slice(0, 6)때문에 실패 12개 중 6개만 보이고 올리브영이 가려집니다. - 릴레이 중단 시 전면 실패 — 캐시가 인스턴스 메모리 30분(
client.ts)과 엣지 캐시뿐입니다.wrangler.toml에 KV가 없습니다. 매장 목록처럼 거의 변하지 않는 데이터를 KV에 영속화하면 릴레이가 멈춰도 매장 검색은 유지할 수 있습니다.
최소 동작 경로
- 경로 결정 (유료 API vs 자체 릴레이)
- 릴레이라면: secrets 4개 동기화 → Tunnel/Access/DNS → Worker 주입 →
/api/oliveyoung/stores1건 실검증 - 코드 보강: 동시성 제한(4) → 오류 코드 보존(3) → 설정 노출(5) → 지속 캐시(7)
- 헬스체크 요약 잘림 해제(6)
대안/검토 사항
경로 B: transport를 상용 API로 교체
- Zyte 복귀: 계정 정지 해제(결제)가 선행되어야 하고,
src/utils/zyte.ts의 하드 차단 해제와docs/zyte-free-access.md·릴리스 노트 수정이 필요합니다. #185를 되돌리는 결정입니다. - 다른 업체(Browserless, ScrapingBee, Bright Data 등): JS 렌더링 + 주거용 IP면 통과합니다. 비용 구조만 다르고 성격은 Zyte와 같습니다.
- Cloudflare Browser Rendering: headless는 이미 차단으로 실측됐고 CF 데이터센터 IP라 기대치가 낮습니다. 검증 비용이 싸므로 시도해 볼 수는 있습니다.
경로 C: 축소해서 정직하게 실패시키기
- 상품 페이지 HTML도 같은 챌린지 뒤에 있어 대체 소스가 없고, 매장 재고는 올리브영 외 출처가 없습니다.
- 복구를 미룬다면 #157이 CU에 적용한 패턴(연결은 유지하고
available: false와 명시적 원인 반환)을 쓰고, 헬스체크에서 올리브영을 "설정 미완" 신호로 분리해 상시 실패부터 정리하는 선택지가 있습니다.
적용 범위 한계
- 릴레이 설정은 바인딩 전용이라 MCP 도구 인자로 넘길 수 없습니다(의도된 설계). 이 복구는
mcp.aka.page운영자만 수행할 수 있고, self-host 하는 쪽은 각자 릴레이를 세워야 합니다.
Activity
hmmhmmhm commentedon Oct 6, 2026
릴레이 연결, 오류 진단, 재고 조회 동시성 제한과 헬스 점검을 반영했습니다. 현재는 브라우저 없이 HTTP 중계로 동작하며(#234, #235), KV 영속 캐시 대신 기존 캐시와 자동 경로 선택을 사용합니다. 복구 방향을 정리해 주셔서 감사합니다.