정중하고 분산된 Web Crawler 설계: 월 10억 페이지까지 가는 현실적인 로드맵

AI가 먹는 ‘웹 데이터’, 그 뒤에서 움직이는 조용한 엔진
요즘 AI는 바다처럼 거대한 온라인 텍스트로 학습합니다. 그런데 그 데이터는 누가, 어떻게 가져올까요? 눈에 잘 띄지는 않지만 끊임없이 인터넷을 누비는 web crawler가 있습니다. 시작은 단순해 보입니다. seed URLs 몇 개로 출발해 페이지를 받아오고, 링크를 뽑아 또 방문하는 식이죠. 하지만 규모를 조금만 키우면 얘기가 완전히 달라집니다. 네트워크는 흔들리고, 사이트는 제한을 걸고, 무엇을 먼저 방문할지의 우선순위와 공정함(politeness), 그리고 멈추지 않는 안정성이 진짜 과제가 됩니다.
이 글에서는 **월 10억 페이지(대략 초당 400 페이지)**를 처리하는 polite·distributed·priority‑aware한 crawler 설계를 처음부터 끝까지 정리합니다. 어떤 언어를 쓰든 상관없습니다. 핵심은 개념과 구조입니다.
목표를 수치로 정리해보기
- 월간 목표: 1,000,000,000 pages
- 평균 처리량: 약 400 pages/sec
현실에서는 트래픽이 고르게 흐르지 않습니다. 피크가 생기고, 재시도와 backoff가 섞입니다. 시스템은 부러지지 않고 유연하게 휘어야 합니다.
왜 순진한 BFS만으로는 금방 막히는가
처음엔 BFS가 솔깃합니다. 큐에 seed URLs를 넣고 하나씩 꺼내서 fetch → link extract → 큐 뒤에 추가… 반복. 그런데 곧 두 가지 문제가 도드라집니다.
- Host hot‑spot 문제. 대부분의 페이지는 같은 사이트 내부로 많이 링크합니다. 대표적으로 위키 계열이 그렇죠. 순수 BFS는 특정 host만 계속 두드려서 rate limit에 걸리거나 차단을 유발합니다.
- URL의 가치가 제각각이라는 점. apple.com 같은 홈은 한 번 방문할 가치가 큽니다. 반면 외딴 포럼의 오래된 글은 그만큼 급하지 않죠. 모든 URL을 동일하게 취급하면 자원 낭비입니다.
해법은 간단합니다. **정중함(politeness)**과 **전략(prioritization)**을 시스템 차원에서 강제하는 것.
Politeness의 기본: host‑aware scheduling + hashed queues
정중한 크롤링의 1원칙은 이렇습니다. 같은 host를 너무 자주 치지 않는다.
전역 큐 하나 대신, URL을 host 기준으로 묶어 다룹니다. 다만 host 수가 수백만이므로 큐를 host마다 하나씩 만들 수는 없습니다. 그래서 고정 개수의 queues(예: 수천 개)를 두고, bucket = hash(host) % NUM_QUEUES로 매핑합니다. 이렇게 하면 같은 host에서 나온 URL은 항상 같은 큐로 흘러갑니다.
worker threads는 여러 bucket에서 항목을 꺼내지만, 요청을 보낼 때는 per_host_min_interval을 지켜 딜레이를 둡니다. 이 조심스러운 간격이 blocklist로부터 우리를 지켜주고, 작은 서버에게도 숨 쉴 시간을 줍니다.
정리하면,
- URL → host 추출 → hash → bucket 선택 → 해당 queue에 push
- worker는 큐에서 pop하되 per‑host pacing을 강제로 준수
이 한 가지 변화만으로도 공격적인 crawler가 예의 바른 손님으로 바뀝니다.
전략의 핵심: prioritizer와 frontier
다음은 prioritizer입니다. 새로 들어온 URL을 점수화해 frontier(우선순위가 반영된 대기 목록)에 넣습니다. 점수가 높은 URL은 더 빨리 스케줄되고, 낮은 것은 여유가 있을 때로 밀립니다.
유용한 점수 신호 예시
- Estimated popularity: 신뢰도 높은 페이지로부터 얼마나 링크되는가
- Change frequency: 자주 업데이트되는 홈/뉴스 vs. 거의 변하지 않는 아카이브
- Cross‑site inlinks: 동일 사이트 내부 링크보다 다른 도메인에서의 인링크
대형 시스템은 여기서 ML 기반 prioritization을 도입해 실시간으로 가중치를 조절하기도 합니다. 하지만 간단한 휴리스틱만으로도 체감 효과가 큽니다. 요점은 무작위가 아니라 의도적으로 웹을 탐색한다는 것.
중복 지옥 탈출: URL‑seen + content‑seen
웹에는 복제·미러·재게시가 넘칩니다. 이 **중복(redundancy)**는 대역폭과 CPU를 잡아먹습니다. 두 겹으로 막습니다.
- URL‑seen: 정확히 같은 URL을 두 번 크롤하지 않도록 빠른 membership check를 둡니다. sharded hash set과 주기적 compaction, Bloom filter로 warm cache를 구성하면 효율적입니다.
- content‑seen: fetch 후 정규화한 텍스트/DOM에 대해 content hashing을 하고, 이미 본 hash라면 indexing을 건너뛰거나 낮은 우선순위로 처리합니다. near‑duplicate를 잡으려면 shingles 같은 기법을 고려합니다.
둘을 함께 쓰면 코퍼스가 더 깔끔해지고 crawler도 가벼워집니다.
Download 이후 파이프라인: parsing → links → filtering
페이지를 받아오면 parser가 일합니다.
- 깨진 마크업도 견딜 수 있게 파싱
- 의미 있는 본문 텍스트 추출
- 링크 수집 및 relative → absolute 변환
- 새 URL은 prioritizer로 전달
중간의 URL filter는 우리가 원치 않는 것(대용량 image/video 파일, 차단 도메인 등)을 제거합니다. 이렇게 frontier를 정돈해 대역폭을 중요한 곳에만 투자합니다.
루프 요약:
Download → Parse → Extract → Filter → Prioritize → Schedule → Download…
단어로 쓰면 간단하지만, 제대로 돌면 강력합니다.
억 단위로 확장하려면: 분산, 캐시, 그리고 잃어버리지 않기
월 10억 페이지에 가려면 이 시스템은 자연스럽게 distributed가 됩니다.
- 지역 분산(Regional shards): frontier를 분할하고, 대상 서버에 가까운 리전에 workers를 배치해 지연과 비용을 줄입니다. 어느 리전이든 동일한 politeness 규칙을 따릅니다.
- Global politeness 조율: 여러 worker가 있다고 해서 한 host를 동시에 두들기면 안 됩니다. consistent hashing으로 특정 host의 스케줄링 소유자를 단일화합니다.
- DNS는 병목입니다. lookup이 느립니다. 합리적 TTL로 DNS caching을 적극 사용하고, 법·정책을 준수하는 범위에서 connection pooling을 활용합니다.
- Checkpointing & 복구: 크래시와 네트워크 플랩은 일상입니다. frontier 스냅샷, URL‑seen 다이제스트, fetch 오프셋 등을 checkpoint로 남겨 중단 지점에서 재시작합니다.
핵심 태도는 하나입니다. 실패를 전제로 설계하고, 계속 전진하는 것.
한눈에 보는 전체 아키텍처
Control plane
- Frontier service (priority queue abstraction)
- Host‑queue router (hashing hosts → fixed buckets)
- Politeness rate limiter (per‑host intervals)
- URL‑seen / content‑seen services (membership + similarity)
Data plane
- Fetchers (HTTP client with retries, timeouts, backoff)
- Parsers/normalizers (robust HTML handling, text extraction)
- Link extractor & URL canonicalizer
- Filters (filetype, block/allow lists)
Reliability & Scale
- Regional sharding, consistent hashing, global coordination
- DNS cache, connection pooling
- Metrics, tracing, structured logs
- Checkpointing & snapshot restore
단순한 루프를 넘어, 공정함·규모·지능을 균형 있게 엮은 system이 완성됩니다.
작은 예제로 따라가보기: seeds → steady state
- Seed 로딩: 신뢰도 높은 홈, 주제별 디렉터리, 연구 허브 등
- Frontier 주입: seeds에 높은 초기 score 부여
- Host 큐 매핑: hash로 bucket 결정 → 해당 queue에 적재
- Fetch & parse: 본문 추출, 링크 발견, content hash 계산
- Filter & prioritize: 원치 않는 URL 제거, 나머지 점수화 후 frontier로 복귀
- Dedup: URL‑seen / content‑seen 갱신, 중복은 패스
- Checkpoint: 스냅샷 주기적 저장
- 반복: 시간이 갈수록 평균 ~400 fetches/sec 근처에서 안정화
손볼 수 있는 튜닝 포인트
- Per‑host delay: 기본은 보수적으로(예: 1–2s 이상). 상황에 따라 천천히 조정.
- Retry strategy: exponential backoff + jitter. 원 서버 보호를 위해 retry 상한을 둡니다.
- Frontier decay: 오래된 score는 서서히 낮춰 신선한 문서가 올라오게 합니다.
- Content hashing: 텍스트 정규화 후 hash. near‑dupe는 shingle 기반 탐지 고려.
- Sharding key: host 또는 registrable domain 기준을 권장(ownership 안정화).
목표가 freshness인지 breadth인지에 따라 다이얼을 미세 조정하세요.
자주 받는 질문 (FAQ)
BFS는 쓸모가 없나요?
아닙니다. 데모나 소규모 크롤에는 여전히 간단하고 유용합니다. 다만 규모가 커지면 prioritization + host‑aware scheduling과 결합해야 현실적입니다.
어떻게 ‘정중함’을 보장하죠?
per‑host throttling, 서버의 backoff 신호 존중, 동일 origin 동시 타격 금지. 서버가 빠르더라도 최소 간격은 유지하세요.
중복은 어떻게 다루나요?
입구에서 URL‑seen으로 막고, 내려받은 뒤에는 content‑seen으로 near‑dupe까지 걸러냅니다.
시스템을 어떻게 ‘살려’두나요?
checkpointing, idempotent queue 동작, 보수적인 retry 정책. 부분 실패를 정상 상태로 상정하세요.
마무리: 실패를 감안하고, 공정함을 지키며, 똑똑하게 스케일하기
처음에는 ‘다운로드하고 링크 따라가기’라는 단순한 루프에 불과했습니다. 이제는 polite·distributed·priority‑aware한 설계로 월 10억 페이지도 차분하게 소화하는 현실적인 로드맵을 가졌습니다. 웹은 늘 요란하고, 실패는 일상입니다. 괜찮습니다. 실패를 전제하고, 공정함을 원칙으로 삼고, 우선순위를 전략으로 다루면 됩니다.
다음 커리어 스텝을 준비 중이라면? system design, coding, behavioral, machine learning, OOD를 아우르는 커뮤니티에서 실전 대비를 해보세요. 학습 경로는 명확하고, 연습은 풍부하며, 사람들은 친절합니다. 자세한 내용은 커뮤니티 사이트에서 확인하세요.
'일상 > IT' 카테고리의 다른 글
| Norton Neo 브라우저 후기: AI 요약·Ad Block까지 되는 차세대 브라우저 써보니 (0) | 2026.01.10 |
|---|---|
| HTTP 402 Payment Required 제대로 쓰는 법: X42로 API micropayment 구현하는 완벽 가이드 (0) | 2026.01.05 |
| Cursor 2.0 Composer 실제 속도 체감 후기: Claude·GPT-5와 비교한 병렬 에이전트 워크플로 (0) | 2025.12.05 |
| 2025 vs 2022 소프트웨어 개발 변화 총정리: AI 코드 생성, CI/CD, staging 비교 가이드 (0) | 2025.11.29 |
| 오픈소스 해방 전선: Libra/Libriophone 비전과 모바일 자유 전쟁 (로컬라이즈 버전) (0) | 2025.11.28 |