8000 feat: 전체 중계 일일 예산 100배·속도 10배 증액 by hmmhmmhm · Pull Request #253 · hmmhmmhm/daiso-mcp · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ CLI는 로컬 npm 패키지를 실행합니다. 공개 원격 MCP 연결에는
[응답 모델](docs/service-reference.md#mcp-표준-응답-모델)에서 필드 정의를 확인하세요.

공개 GET API 중 올리브영·CGV·CU·GS25에는 IP당 하루 합산 3,000회(KST 기준) 제한이 적용됩니다.

Mac 중계의 일일 예산은 100배, 분당 속도는 10배로 증액했습니다. 원본 호출 기준 한도는 편의점 공유 일 1,000만 회·분 690회, 올리브영과 Dtryx·CGV 공유는 각각 일 30만 회·분 300회입니다. 자세한 집계와 재시도 기준은 [중계 한도 안내](docs/health-check-stock-and-quota.md)를 참고하세요.

조회가 실패하면 [서비스 상태](https://aka-page.betteruptime.com/)를 확인하세요.
서버 운영과 제한 집계는 [운영 가이드](scripts/ops/README.md)에 있습니다.

Expand Down
4 changes: 2 additions & 2 deletions docs/cgv-relay-recovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

2026-10-08 점검에서 Worker 직접 요청은 HTTP 403으로 차단되었으나, 같은 CGV 공식 API를 Mac에서 서명·브라우저 헤더와 함께 호출하면 HTTP 200을 반환했습니다. 인증된 로컬 중계 실측은 극장 174건, 영화 5건, 시간표 30건을 확인했습니다. 이 수치는 점검 시점의 결과이며 날짜와 극장에 따라 달라집니다.

CGV는 `/v1/cgv/{theaters,movies,timetable,timetable-movie}` 경로로 기존 Dtryx 4320 프로세스를 사용합니다. 별도 예산을 만들지 않고 `DTRYX_RELAY_TOKEN`, `DTRYX_RELAY_STATE_DIR/quota.json`, Dtryx 로그를 공유합니다. 기본 상한은 `scripts/relay/quota.ts`의 일 3,000회·분 30회이며 CGV와 Dtryx의 실제 중계 원본 요청을 합산합니다. CU·세븐일레븐·GS25의 별도 편의점 원장은 공유하지 않습니다.
CGV는 `/v1/cgv/{theaters,movies,timetable,timetable-movie}` 경로로 기존 Dtryx 4320 프로세스를 사용합니다. 별도 예산을 만들지 않고 `DTRYX_RELAY_TOKEN`, `DTRYX_RELAY_STATE_DIR/quota.json`, Dtryx 로그를 공유합니다. 기본 상한은 `scripts/relay/quota.ts`의 일 300,000회·분 300회이며 CGV와 Dtryx의 실제 중계 원본 요청을 합산합니다. CU·세븐일레븐·GS25의 별도 편의점 원장은 공유하지 않습니다.

중계는 검증된 성공 응답만 재사용합니다. 극장·영화 목록은 5분, 시간표·영화별 시간표는 30초 뒤 만료됩니다. 동일 작업과 조회 조건으로 겹친 진행 요청은 원본 조회와 예산을 한 번만 소비합니다. 원본 HTTP 오류·업무 오류는 캐시하지 않으며, 시간표 캐시가 만료되면 좌석 수량을 다시 조회합니다. 이 중계 캐시는 REST 캐시와 별도이며 MCP 요청에도 적용됩니다. 분·일 상한은 변경하지 않습니다.
중계는 검증된 성공 응답만 재사용합니다. 극장·영화 목록은 5분, 시간표·영화별 시간표는 30초 뒤 만료됩니다. 동일 작업과 조회 조건으로 겹친 진행 요청은 원본 조회와 예산을 한 번만 소비합니다. 원본 HTTP 오류·업무 오류는 캐시하지 않으며, 시간표 캐시가 만료되면 좌석 수량을 다시 조회합니다. 이 중계 캐시는 REST 캐시와 별도이며 MCP 요청에도 적용됩니다. 캐시는 설정된 분·일 예산 안에서 원본 호출을 줄입니다.

5분 주기 경로 점검은 CGV 직접 요청과 중계를 확인해 사용 가능한 경로를 선택합니다. 직접 경로가 차단되면 중계를 사용하고, 직접 경로가 회복되면 점검 결과에 따라 복귀합니다. 일반 요청 중 직접 실패도 경로 선택에 반영됩니다. `/health`의 `config.cgvRelay`는 유효 설정 여부만 나타내며 연결 성공을 뜻하지 않습니다.

Expand Down
6 changes: 3 additions & 3 deletions docs/convenience-relay.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ CONVENIENCE_RELAY_TOKEN=your_convenience_token
CONVENIENCE_RELAY_STATE_DIR=/absolute/path/to/convenience-state
```

상태 경로는 Dtryx 상태 경로와 다른 디렉터리여야 합니다. 이 디렉터리의 `quota.json`에는 재시작해도 유지되는 호출 원장이, `logs/`에는 입력·토큰·키를 제외한 제한된 관측 로그가 저장됩니다. CU·GS25·세븐일레븐이 공유하는 편의점 예산은 분당 69회, 하루 최대 100,000회이며 Dtryx·올리브영 예산과 공유하지 않습니다.
상태 경로는 Dtryx 상태 경로와 다른 디렉터리여야 합니다. 이 디렉터리의 `quota.json`에는 재시작해도 유지되는 호출 원장이, `logs/`에는 입력·토큰·키를 제외한 제한된 관측 로그가 저장됩니다. CU·GS25·세븐일레븐이 공유하는 편의점 예산은 분당 690회, 하루 최대 10,000,000회이며 Dtryx·올리브영 예산과 공유하지 않습니다.

Worker에는 아래 바인딩을 설정합니다. URL은 중계 호스트의 기본 URL이며 `/v1/convenience`를 붙이지 않습니다. HTTPS 또는 로컬 루프백 HTTP만 허용합니다. REST와 MCP가 동일한 설정을 사용합니다.

Expand Down Expand Up @@ -60,11 +60,11 @@ CU 매장 키워드만 입력한 조회는 공식 웹에서 해당 지역 점포

본문 16 KiB, 원본 응답 2 MiB, 동시 원본 호출 4개, 전체 요청 32개를 제한합니다. 본문·대기·원장·원본 처리를 합쳐 15초 기한을 적용하며 취소된 대기 요청은 예산을 소비하지 않습니다. 인증·입력 검증 뒤 성공한 상품·메타정보·초기화·카탈로그는 5분, 매장·실재고는 30초 동안 최대 256개·16 MiB의 메모리 캐시에 저장합니다. 캐시 적중은 호출 예산을 소비하지 않습니다. 오류는 캐시하지 않습니다.

캐시 가능한 작업은 인증·입력 검증을 통과한 뒤 작업명과 모든 조회 조건이 같은 진행 요청을 공유합니다. 최초 동시 조회도 원본 호출·예산 소비는 한 번입니다. 각 응답 body와 request ID는 독립이며 공유를 기다린 요청은 `coalesced` 단계로 기록합니다. 한 요청의 취소는 다른 대기자에게 전파하지 않고, 마지막 대기자가 떠나면 원본 작업과 큐를 취소합니다. 최초 요청의 전체 15초 기한이 지난 공유 작업은 후속 대기자에게도 504를 반환하며, 실패·취소 결과는 캐시하지 않습니다. 동시 접수 32개·원본 4개·분당 69회·일 100,000회 상한은 유지합니다. 서로 다른 상품·지역·인증키·조회 조건은 임의로 합치지 않습니다.
캐시 가능한 작업은 인증·입력 검증을 통과한 뒤 작업명과 모든 조회 조건이 같은 진행 요청을 공유합니다. 최초 동시 조회도 원본 호출·예산 소비는 한 번입니다. 각 응답 body와 request ID는 독립이며 공유를 기다린 요청은 `coalesced` 단계로 기록합니다. 한 요청의 취소는 다른 대기자에게 전파하지 않고, 마지막 대기자가 떠나면 원본 작업과 큐를 취소합니다. 최초 요청의 전체 15초 기한이 지난 공유 작업은 후속 대기자에게도 504를 반환하며, 실패·취소 결과는 캐시하지 않습니다. 동시 접수 32개·원본 4개·분당 690회·일 10,000,000회 상한은 유지합니다. 서로 다른 상품·지역·인증키·조회 조건은 임의로 합치지 않습니다.

리다이렉트·비 JSON·실패 envelope·잘못된 수량은 오류로 반환합니다. 재고 오류를 수량 0으로 바꾸지 않습니다. 세븐일레븐 수량 `-1`은 미확인이며 품절로 표시하지 않습니다. 반환된 매장의 수량이 모두 미확인이면 `stockAvailable`은 false입니다. 캐시 수량은 조회 시점 이후 바뀔 수 있으므로 실제 방문 전 점포 확인이 필요합니다.

분당 상한은 UTC 기준 각 1분 구간에 적용됩니다. 69 × 1,440분 = 99,360회이므로 정상적인 하루 합계는 10만 회 이하이며, 일일 100,000회 상한도 함께 검사합니다. 일일 원장은 UTC 자정(한국 시각 오전 9시)에 새 날짜로 전환됩니다. 한도 변경이나 재시작 시 기존 원장을 초기화하지 않습니다.
분당 상한은 UTC 기준 각 1분 구간에 적용됩니다. 690 × 1,440분 = 993,600회이므로 이 속도를 하루 내내 채워도 합계는 993,600회이며, 일일 10,000,000회 상한도 함께 검사합니다. 2026-10-08 사용자 요청에 따라 일일 예산은 기존의 100배, 분당 속도는 10배로 증액했습니다. 일일 원장은 UTC 자정(한국 시각 오전 9시)에 새 날짜로 전환됩니다. 한도 변경이나 재시작 시 기존 원장을 초기화하지 않습니다.

조회 재시도와 CU 주소·미확인 재고 처리는 [조회 오류 복구와 제한](status-resilience.md)을 참조하세요. 재시도도 같은 편의점 원장을 사용합니다.

Expand Down
2 changes: 1 addition & 1 deletion docs/dtryx-relay.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ node --max-old-space-size=128 --env-file=/absolute/path/dtryx.env \
- `GET /v1/dtryx/health`: 인증된 상태 조회.
- 임의URL·헤더·추가필드·알 수 없는 경로는 거절한다.
- 동시4개, 대기 포함32개, 전체15초 제한. 요청 본문16KiB, 원본 응답2MiB.
- 별도 파일 원장으로 분당30회·UTC일3000회를 제한한다. 재시작해도 유지되며 원장 장애는 차단한다.
- 별도 파일 원장으로 분당300회·UTC일300000회를 제한한다. 재시작해도 유지되며 원장 장애는 차단한다.
- 성공은 원본의 `RetCode=success` 및 유효한 `Recordset`을 확인한 뒤 반환한다. 실패나 미확인 좌석을 정상 데이터로 만들지 않는다.

## 점검과 복구
Expand Down
8000
2 changes: 1 addition & 1 deletion docs/gs25-auth-recovery-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ REST / MCP / CLI → Worker → Access/Tunnel → Mac 편의점 중계
| 공개 주소에서 Python 기본 UA만 403 | 실제 CLI/SDK와 같은 요청으로 대조한다. 당시 `daiso-cli/1.2.9` UA는 200이었다. 차단 기준을 추측해 우회하지 않는다. |
| 200인데 다른 상품/지역 | 상품 코드·상품명·좌표·필터·수량을 검증한다. HTTP 200만으로 복구 완료가 아니다. |

69/분·100,000/일은 편의점 중계의 **합산 자체 상한**이다. 동시 원본 4, 접수 32, 전체 15초, 재고 캐시 30초를 유지한다. CU/Seven의 운영 트래픽도 같은 편의점 원장을 소비한다. 캐시 적중·동일 진행 조회 병합은 원본 소비가 없거나 1회지만 서로 다른 조건은 각각 소비한다. 인증 갱신은 별도 인증 요청이다.
690/분·10,000,000/일은 편의점 중계의 **합산 자체 상한**이다. 동시 원본 4, 접수 32, 전체 15초, 재고 캐시 30초를 유지한다. CU/Seven의 운영 트래픽도 같은 편의점 원장을 소비한다. 캐시 적중·동일 진행 조회 병합은 원본 소비가 없거나 1회지만 서로 다른 조건은 각각 소비한다. 인증 갱신은 별도 인증 요청이다.

## 2. 기존 세션을 먼저 점검한다

Expand Down
2 changes: 1 addition & 1 deletion docs/health-check-stock-and-quota.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

## 429의 출처

CU·GS25·세븐일레븐 Mac 중계는 같은 원장을 공유하며 분당 69회, UTC 하루 100,000회로 제한합니다. Cloudflare 직접 경로의 호출과 별개입니다. 분당 상한에 도달해도 일일 상한에 도달했다는 의미는 아닙니다.
CU·GS25·세븐일레븐 Mac 중계는 같은 원장을 공유하며 분당 690회, UTC 하루 10,000,000회로 제한합니다. Cloudflare 직접 경로의 호출과 별개입니다. 분당 상한에 도달해도 일일 상한에 도달했다는 의미는 아닙니다.

응답의 `diagnostics.quotaReason`이 `minute`, `daily`, `consumer`, `consumer-busy`이면 중계가 확인한 자체 정책 제한입니다. `retryAfter`는 재시도까지 기다릴 초 단위 시간입니다. 단순 HTTP 429만으로 자체 제한과 원본 업체 제한을 구분하지 않습니다.

Expand Down
2 changes: 1 addition & 1 deletion docs/oliveyoung-free-relay.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ GitHub Actions의 `Sync Worker Secrets`는 위 네 OY secrets를 동기화하며

### 전체 호출량과 인증

모든 중계 호출은 전체 분당 30회, UTC 하루 3,000회로 제한합니다. MCP·REST 모두 같은 중계를 거치므로 호출 경로로 우회할 수 없습니다. 원장을 `OY_RELAY_STATE_DIR`에 원자적으로 저장하며 저장 실패·손상은 조회 거절로 처리합니다. 본문 업로드 중인 요청도 최대 8개 슬롯에 포함합니다. 실패한 원본 요청도 이미 소비한 예산을 돌려주지 않습니다.
모든 중계 호출은 전체 분당 300회, UTC 하루 300,000회로 제한합니다. MCP·REST 모두 같은 중계를 거치므로 호출 경로로 우회할 수 없습니다. 원장을 `OY_RELAY_STATE_DIR`에 원자적으로 저장하며 저장 실패·손상은 조회 거절로 처리합니다. 소비자별 신규 원본 작업은 분당 240회로 제한하며 소비자 동시 요청은 2개입니다. 본문 업로드 중인 요청도 최대 8개 슬롯에 포함합니다. 실패한 원본 요청도 이미 소비한 예산을 돌려주지 않습니다.

Cloudflare Access 사용 시 Worker secrets에 `OY_ACCESS_CLIENT_ID`와 `OY_ACCESS_CLIENT_SECRET`을 함께 설정합니다. 기존 `OY_RELAY_TOKEN`과 별개입니다. 사용자 도구 입력으로 전달하거나 덮어쓸 수 없습니다. Access 비밀이 다른 주소로 전달되지 않도록 리다이렉트를 거절합니다. Access 정책은 해당 서비스 토큰만 허용하는 Service Auth로 구성하며, Tunnel은 localhost:4319와 최종 404 경로만 연결합니다. 브라우저 디버깅 주소와 개인 네트워크 경로는 연결하지 않습니다.

Expand Down
8 changes: 5 additions & 3 deletions docs/relay-observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ rg --fixed-strings '확인할-request-id' "$OY_RELAY_STATE_DIR/logs" "$DTRYX_REL

인증과 payload 검증 뒤 공통 릴레이 캐시를 확인한다. REST와 MCP가 같은 조건을 요청하면 공유된다. 상품 검색/매장은 5분, 상품 ID는 10분, 재고는 1분 TTL이다. 최대 256항목 및 키·값 직렬화 16MiB, 오래된 항목부터 퇴출한다. 성공만 저장하며 동일 진행 요청은 합친다. 탭이나 브라우저를 추가하지 않는다.

캐시 적중은 quota를 소비하지 않는다. `x-relay-cache: hit/miss/coalesced` 및 health.cache로 확인한다. 서로 다른 검색이 대부분이면 절감 효과는 작다. quota는 기존 분당30/UTC 일일3000을 유지한다. REST 캐시도 별도로 존재하며 products5분/stores24시간/inventory10분이므로 외부 REST 응답의 최신성은 두 계층의 영향을 받는다. `_healthCheck`는 REST 캐시를 새로 조회하지만 공통 릴레이 캐시는 계속 적용된다.
캐시 적중은 quota를 소비하지 않는다. `x-relay-cache: hit/miss/coalesced` 및 health.cache로 확인한다. 서로 다른 검색이 대부분이면 절감 효과는 작다. 현재 OY quota는 분당 300회·UTC 일일 300,000회다. 소비자별 신규 원본 작업은 분당 240회이며, 사용한 원장은 유지한다. REST 캐시도 별도로 존재하며 products5분/stores24시간/inventory10분이므로 외부 REST 응답의 최신성은 두 계층의 영향을 받는다. `_healthCheck`는 REST 캐시를 새로 조회하지만 공통 릴레이 캐시는 계속 적용된다.


## OY 요청 집중 보호 (1.2.7)
## 과거 OY 요청 집중 보호 (1.2.7)

아래 1.2.7·1.2.8 숫자는 변경 이력이며 현재 한도가 아니다. 현재 값은 위의 OY 캐시 안내와 [중계 한도 안내](health-check-stock-and-quota.md)를 따른다.

전역 상한(분당 30회, UTC 하루 3,000회)에 더해, 익명 소비자별 실제 신규 실행을 분당 12회로 제한한다. 요청 본문을 읽기 전부터 동시 미완료 요청은 소비자당 2개만 허용한다. 따라서 세 번째 동시 요청은 캐시 적중 여부를 확인하기 전에 거절될 수 있다. 캐시 적중과 진행 요청 합치기는 실행 횟수를 차감하지 않는다. 대기 작업은 용량을 예약하고 전역 quota를 통과한 실행 직전에 차감하며, 취소·만료·전역 quota 오류는 예약을 반환한다.

Expand All @@ -66,7 +68,7 @@ quick/full 모두 새 검사 요약을 요청한다. 실패 후 한 번 재시

CGV는 `CGV_UPSTREAM_UNAVAILABLE`, API HTTP 503, 원본 HTTP 403이 모두 확인될 때만 `degraded`로 분류한다. 이는 해당 기능이 제한된 상태이며 복구를 뜻하지 않는다. 인증 오류, 타임아웃, JSON 파싱 실패 등은 계속 `fail`이다. 선택 데이터가 없는 `skipped` 검사는 정상 요약에 포함될 수 있다. Actions 성공 여부와 별도로 요약의 저하 서비스 목록을 확인해야 한다. 유료 Zyte 재시도는 활성화하지 않는다.

## 1.2.8 요청 집중 및 교체 시간 개선
## 과거 1.2.8 요청 집중 및 교체 시간 개선

소비자별 신규 실행은 분당 **24회**로 완화했다. 전역 분당 30회·UTC 일일 3,000회는 그대로이며, 소비자별 동시 2개·전체 8개 슬롯과 메모리 상한도 유지한다. 단일 소비자가 지속적으로 요청하면 여전히 제한될 수 있다.

Expand Down
15 changes: 14 additions & 1 deletion docs/seveneleven-stock-recovery-20261008.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Worker의 재고 캐시는 `seveneleven-inventory-v3` 키를 사용하여 기존
30초 저장하고 stale 재사용은 0초로 설정합니다. 확인된 0개 재고는 저장할 수 있으나
오류·미확인 결과는 `Cache-Control: no-store`를 적용합니다.

CU·GS25·세븐일레븐 공통 중계 예산은 분당 69회, 일당 100,000회입니다.
CU·GS25·세븐일레븐 공통 중계 예산은 분당 690회, 일당 10,000,000회입니다.
재고 복구를 위해 이 한도를 올리거나 원장을 초기화하지 않습니다.
429 이후에는 중계가 제공하는 재시도 시간과 운영 예산을 따릅니다.
기본 재고 요청은 최대 20개 점포를 조회합니다. 상품 검색 상한 10회, 매장 검색 1회,
Expand All @@ -49,6 +49,19 @@ Incapsula HTML을 반환했으며, Node 25.9.0은 HTTP 200 JSON을 반환했습
`stockAvailable: true`, `stockError: null`을 확인했습니다. 이는 점검 시점의 결과이며
향후에도 특정 버전이 항상 허용된다는 뜻은 아닙니다.

2026-10-08 00:58 UTC 점검에서는 실제 Node 25.9.0 장기 실행 중계에서도 상품·매장·
인기 검색어 조회가 다시 502를 반환했습니다. 이후 같은 실행 파일의 단일 직접 조회는
상품·매장 모두 HTTP 200이며 설치된 응답 검증도 통과했습니다. 따라서 Node 25로
교체하는 것만으로 지속적인 복구를 보장하지 않습니다. 시간·연결·부하에 따른 차단
가능성은 추가 관측이 필요하며, TLS 지문이나 특정 내부 차단 원인을 확정하지 않습니다.

중계 오류 이벤트의 `status`는 기존 중계 응답 상태이고 `upstreamStatus`는 Seven
원본 HTTP 상태입니다. 예를 들어 `status: 502`, `upstreamStatus: 403`은 원본 HTTP
접근 거절이고, `upstreamStatus: 200`이면 원본 본문 읽기·JSON·응답 검증 오류를
살펴봅니다. HTTP 상태를 확인하지 못한 네트워크·미분류 스트림 오류에는 값을 추정해
붙이지 않습니다. 로그는 정수 100~599만 허용하고 원본 본문·헤더·검색 조건·인증
정보를 저장하지 않습니다. 자체 한도는 `stage: quota`와 별도 한도 진단으로 구분합니다.

변경 전 새 실행 파일로 중계 모듈을 로드해 호환성을 확인합니다. launchd는 `bootout`
후 기존 프로세스가 종료되기 전에 `bootstrap`하면 일시적으로 오류 5를 반환할 수
있습니다. 종료 상태를 확인하고 재등록한 다음 건강 응답과 CU·GS25·세븐일레븐·CGV를
Expand Down
2 changes: 1 addition & 1 deletion scripts/relay/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function createConsumerQuota(now = Date.now, maxEntries = 1024) {
return {
reserve() {
resetMinute();
if (entry.count + entry.reserved >= 24) return null;
if (entry.count + entry.reserved >= 240) return null;
entry.reserved++;
let reserved = true;
return {
Expand Down
Loading
Loading
0