Skip to content

크롤 쉼에 동시 렌더링 상한·페이지 캐시를 두고 텍스트 모드로 돌린다 - #31

Merged
nam-jaehyun merged 5 commits into
mainfrom
feat/shim-gate
Sep 7, 2026
Merged

nam-jaehyun merged 5 commits into
mainfrom
feat/shim-gate

Conversation

@nam-jaehyun

Copy link
Copy Markdown
Member

#29 머지 뒤 base 브랜치 삭제로 #30이 닫혀 같은 내용을 main 기준으로 다시 연 PR입니다.

변경

  • services/crawl4ai-shim/gate.py: Gate(동시 렌더링 상한 + 바운드된 대기)와 PageCache(TTL·LRU).
  • app.py: 캐시 적중이면 브라우저를 열지 않고 답한다. 상한을 넘어 QUEUE_TIMEOUT_MS 안에 자리가 안 나면 {"success": false, "error": "busy…"}. 성공한 스크랩은 캐시에 넣는다. /health에 pages{active,waiting,limit}·cache{entries,ttl_s}.
  • docker-compose.yml: MAX_CONCURRENT_PAGES=8, QUEUE_TIMEOUT_MS=15000, CACHE_TTL_S=900.
  • Dockerfile: gate.py 복사 추가. docs/tools.md 문단.

측정 (운영, 8코어, KloudChat api 컨테이너에서 병렬 호출)

병렬 전 p50 / 최대 후 p50 / 최대
8 (콜드) 3.8s / 7.2s 3.7s / 7.7s
16 (콜드) 7.6s / 11.6s 2.2s / 12.4s
32 (콜드) 20.6s / 26.5s 15.5s / 19.8s
16 (캐시 적중) – 0.3s / 0.3s

모두 성공. 32 병렬에서는 8장씩 네 차례로 나뉘어 도는 모양이라 최대값은 대기 시간이 대부분이다. 더 늘리려면 쉼 복제(브라우저 여러 대)가 다음 단계다.

확인

tests/test_gate.py 5건, ruff 통과. 운영에 배포해 위 표를 쟀다.

추가: 텍스트 모드

이미지·글꼴·미디어는 모델에 전달되지 않으므로(쉼은 마크다운만 돌려준다) 브라우저가 내려받지 않게 text_mode=True를 켰다. 스크립트는 그대로 돈다. 같은 12쪽 전후 비교: 본문 길이 동일(12쪽 모두 본문 있음), 합계 64.4→59.2초, 무거운 쪽 10.5→8.0초·11.4→8.4초.

/tools/fetch 는 인증 없이 닿고 쉼은 LiteLLM·데이터베이스·다른 도구 서비스와
같은 사설 네트워크에 있어, 넘겨받은 URL 로 내부 서비스를 더듬을 수 있었다
(SSRF). file:// 같은 스킴도 그대로 브라우저에 넘어갔다.

- netguard.refusal: http(s) 만 허용하고, 호스트를 IP 로 풀어 하나라도
  공개 주소가 아니면(루프백·사설·링크로컬·CGNAT·메타데이터 이름·점 없는
  서비스 이름·.local/.internal) 거절한다.
- 브라우저가 리다이렉트를 따라간 뒤 도착한 주소도 같은 기준으로 다시 본다.
- 시험 22건, CI 의 pytest 대상에 추가.
2026-09-08 이 네트워크에서 임시 인스턴스로 엔진을 하나씩 다시 쟀다. 한국어
생활 질의 6개(약 병용, 최저임금, 장학금 신청 기간, 소아과, 모델 이름,
열차 요금)와 영어 개발 질의 4개를 질의어가 실린 결과 수로 채점했다.
  yandex   한국어 6/6, 영어 4/4 — 네이버 블로그, 지식iN, 아하, 언론사
  bing     한국어 1/6, 영어 0/4 — 첫 화면, 사전, 바이두 잡음
  yahoo    Suspended: HTTP protocol error
  google/naver/duckduckgo/startpage/brave/mojeek/qwant/presearch 막힘
운영에서 34개 페르소나 질의의 상위 5건 중 질의어를 절반 이상 실은 결과가
166건 중 2건(1%)이었고, 바꾼 뒤 170건 중 87건(51%), 첫 화면은 39→17.
bing 은 예비로 weight 0.5 에 남긴다: KloudChat 이 질의어 겹침으로 다시
정렬하므로 둘 다 답하면 bing 의 엉뚱한 결과가 아래로 내려간다.
Chromium 한 대가 모든 요청을 받는데 동시 수 제한이 없어, 몰리면 탭이
서로 CPU 를 뺏다가 30초 타임아웃에 함께 걸렸다(32 병렬에서 중앙값
20.6초, 최대 26.5초). `gate.py` 의 두 장치를 앞에 둔다.
- Gate: 동시에 MAX_CONCURRENT_PAGES(8)장만 렌더링하고, 나머지는
  QUEUE_TIMEOUT_MS(15초)까지 기다린 뒤 「busy」로 거절한다. 부르는 쪽
  (KloudChat)은 읽지 못한 페이지처럼 요약문으로 물러난다.
- PageCache: 성공한 스크랩을 URL·formats·본문 플래그로 CACHE_TTL_S
  (15분) 동안 메모리에 둔다. 같은 시간대에 같은 것을 묻는 사용자들은
  같은 상위 결과를 읽는다.
/health 가 렌더링 중·대기 중·캐시 항목 수를 보인다.

운영 측정(8코어): 16 병렬 콜드 p50 7.6→2.2초, 32 병렬 콜드 p50
20.6→15.5초·최대 26.5→19.8초(전부 성공), 캐시 적중 시 0.3초.

Dockerfile 이 파일을 하나씩 복사하므로 gate.py 를 목록에 넣었다 —
처음 배포에서 빠뜨려 쉼이 재시작 루프에 들어갔었다.

확인: tests/test_gate.py 5건(상한·대기·거절, TTL, 키, 축출), ruff 통과.
이미지·글꼴·미디어는 모델에 전달되지 않는다 — 쉼은 마크다운 본문만
돌려주고 KloudChat 은 그 텍스트만 검색 결과에 붙인다. 그러니 브라우저가
내려받을 이유가 없다(`text_mode=True`, 스크립트는 그대로 돈다).

운영에서 같은 12쪽을 전후로 읽어 비교: 본문 길이는 동일(위키백과
463,355→463,455자, 파이썬 71,202→71,202자 등, 12쪽 모두 본문 있음),
합계 시간 64.4→59.2초, 무거운 쪽은 10.5→8.0초·11.4→8.4초.
@nam-jaehyun
nam-jaehyun merged commit ddeac97 into main Sep 7, 2026
5 checks passed
@nam-jaehyun
nam-jaehyun deleted the feat/shim-gate branch September 7, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant