본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Backend

대규모 웹 크롤러 설계

추가
목차7개 항목
출처와 참고

Andrew Chan의 「Crawling a billion web pages in just over 24 hours, in 2025」(2025)를 바탕으로 재구성한 노트다.

무엇을 정하는 문제인가

크롤러가 웹이라는 그래프를 어떻게 탐색하는지(URL 프런티어, 재크롤링, PageRank)는 웹 검색엔진의 동작에 있다. 이 노트는 그 크롤러를 정해진 시간과 예산 안에 실제로 돌리는 시스템 설계를 다룬다. 기준 규모는 HTML만 가져와 <a> 링크를 따라가는 크롤링으로 하루 10억 페이지, 머신 십여 대다.

제약내용
예의(politeness)robots.txt를 지키고, 연락처를 넣은 User-Agent를 쓰고, 요청이 오면 도메인을 제외 목록에 넣는다. 같은 도메인에는 수십 초 이상 간격을 둔다. 작은 사이트를 보호하려면 시드 도메인 목록 안에서만 돈다
장애 허용멈췄다가 이어서 돌 수 있어야 한다. 크롤링은 웹의 표본이므로 복구할 때 방문 기록 일부를 잃어도 된다
JavaScript실행하지 않으면 훨씬 싸다. 실제로 렌더링해야 하는 크롤링은 같은 규모라도 몇 배 비싸다. 렌더링 방식과 크롤러의 관계는 SEO 기초

robots.txt를 무시하거나 다른 에이전트로 위장하거나 한 엔드포인트를 쉼 없이 두드리는 크롤러는 사이트 운영자에게 큰 부담이다. AI 학습용 수집이 늘면서 사이트 쪽 방어도 늘었고, Cloudflare처럼 크롤링에 값을 매기는(pay-per-crawl) 시도도 있다.

설계: 기능별로 나누지 않는다

교과서적 분리 설계는 파싱, 페칭, 저장소, 크롤 상태를 각각 다른 머신 풀로 나눈다. 예산이 작을 때는 반대로, 모든 기능을 담은 독립 노드가 시드 도메인을 나눠 맡는 편이 단순하고 싸다. 노드 사이 통신이 없으므로 노드를 더하는 것만으로 확장된다.

flowchart LR
  subgraph node["노드 하나 (예: 16 vCPU, 128GB RAM, 25Gbps, 로컬 NVMe 수 TB)"]
    redis["Redis: 크롤 상태<br/>· 도메인별 프런티어 (가져올 URL 목록)<br/>· 준비 큐: 다음에 가져와도 되는 시각순으로 정렬한 도메인<br/>· 방문한 URL + 메타데이터 + 저장 경로<br/>· 본 URL 블룸 필터<br/>· 도메인 메타데이터 (제외 여부, robots.txt와 만료 시각)<br/>· 파싱 큐"]
    f["페처 N개"] -->|"가져온 HTML"| pq["파싱 큐"] --> p["파서 M개"]
    p -->|"링크 추출"| fr["도메인 프런티어"] --> f
    p -->|"본문 저장"| disk["디스크"]
  end

× 노드 여러 대. 시드 도메인을 노드별로 나눈다

부품동작
준비 큐다음 요청 가능 시각순으로 도메인을 정렬한다. 페처는 준비된 도메인을 꺼내 그 도메인의 프런티어에서 URL 하나를 가져오고, 도메인을 새 시각으로 다시 넣는다. 도메인당 지연이 자료 구조에 들어 있다
블룸 필터URL이 프런티어에 이미 들어갔는지 빠르게 확인한다. 아직 가져오지 않은 URL도 걸러야 하므로 방문 기록과 따로 둔다. 가끔 처음 보는 URL을 봤다고 잘못 판정하지만 표본이라 괜찮다
페처I/O를 기다리는 일이라 비동기(asyncio 등)로 코어 하나에 수천 개의 동시 요청을 돌린다
파서CPU를 쓰는 일이라 프로세스 수로 늘린다. 프로세스당 동시 작업은 수십 개면 CPU가 찬다
로컬 캐시robots.txt 같은 도메인 정보를 페처·파서가 LRU 캐시로 들고 있어 상태 저장소 부하를 줄인다

asyncio 같은 사용자 공간 멀티태스킹이 문맥 전환 없이 높은 동시성을 얻는 원리는 비동기 서버의 이벤트 루프에 있다.

판단이유
크롤 상태는 인메모리 저장소(Redis)자료 구조는 단순하지만 프런티어 질의가 매우 잦다. SQLite·PostgreSQL로는 빠르게 만들기 어렵다
샤드를 너무 잘게 나누지 않는다인기 도메인을 맡은 노드만 바빠지는 핫 샤드가 생긴다
상태 저장소 하나에 붙는 작업자 수에 상한이 있다Redis 한 대의 초당 연산 수가 먼저 찬다. 노드당 작업자 수를 그 아래로 맞춘다
거대한 단일 머신보다 수평 확장큰 머신은 네트워크는 남아도 OS 자원 같은 소프트웨어 한계로 처리량이 비례해 늘지 않는다

저장소: 요청당 과금을 계산한다

객체 스토리지(S3)는 용량뿐 아니라 요청마다 값을 받는다. 페이지 10억 개를 하루 저장하면 PUT 요청 비용만으로 크롤링 전체 비용보다 한 자릿수 커진다. 그래서 스토리지 최적화 인스턴스의 로컬 NVMe에 저장하고, 용량은 snappy 같은 빠른 압축(또는 페이지 자르기)으로 맞춘다.

병목은 CPU로 옮겨 갔다

병목2010년대 초2025년 무렵
네트워크 대역폭대학·사내망을 다 쓰지 않도록 속도를 늦췄다데이터센터 대역폭이 넘친다. 노드당 25Gbps 중 약 3분의 1만 쓴다
DNS조회 처리량이 병목이었다상위 도메인 위주라면 문제가 되지 않는다
파싱쿼드코어로 충분했다가장 큰 병목. 페이지가 커졌다
TLSHTTPS가 드물었다핸드셰이크 계산이 CPU 시간의 약 4분의 1을 쓴다
  • 페이지가 커졌다. 상위 도메인 페이지의 비압축 크기 중앙값은 100KB를 넘고, 평균은 2010년대 초의 몇 배다. 처방은 빠른 파서(HTML5 전용 C++ 파서 Lexbor를 감싼 selectolax 등)로 바꾸고, 파서에 넘기기 전에 평균보다 넉넉한 크기에서 자르는 것이다.
  • HTTPS가 기본이 됐다. Firefox에서 HTTPS로 열리는 페이지는 2014년 30%에서 2025년 80% 이상이 됐다. 그래서 페칭도 네트워크가 아니라 CPU에 묶인다. HTTPS 연결 과정은 HTTPS와 TLS에 있다.

메모리는 꼬리에서 터진다

프런티어를 모두 메모리에 두는 설계는 평균이 아니라 꼬리로 계산해야 한다. 방문 기록과 HTTP 클라이언트 메모리는 전체 페이지 수로 어림할 수 있지만, 링크가 아주 많은 인기 도메인 몇 개(대형 포털, 위키백과)의 프런티어는 URL 수억~수십억 개, 수십 GB로 불어난다. 도메인별 프런티어 크기에 상한을 두고, 넘치면 잘라 낸다. 장애 허용 설계가 있으면 쓰러진 노드를 재시작해 이어 가기 쉽다. 이렇게 불어나는 도메인은 대개 크롤러를 붙잡아 두는 함정 사이트(tarpit)가 아니라 정상적인 인기 사이트다.

로그 로테이션처럼 작은 운영 설정도 규모가 열 배로 뛰면 디스크를 채운다. 본 크롤링 전에 목표 규모에 가까운 리허설을 한다.

교과서 어림과 비교

널리 알려진 「페칭 전용 머신 몇 대로 며칠에 100억 페이지」 같은 어림에서 대역폭 사용률 30% 가정 자체는 현실과 맞는다. 다만 그 숫자에 이르려면 파싱과 TLS를 포함한 CPU 최적화가 어림보다 훨씬 많이 필요하다.

정리

  • 예의(robots.txt, 도메인당 지연, 시드 제한)가 첫 번째 제약이다. 도메인당 지연은 준비 큐라는 자료 구조로 강제한다.
  • 작은 예산에서는 기능별 머신 풀보다 도메인 샤드마다 모든 기능을 담은 독립 노드가 낫다. 크롤 상태는 Redis(프런티어, 준비 큐, 블룸 필터)에 둔다.
  • 저장은 요청당 과금을 계산한다. 대량 소형 객체라면 로컬 스토리지가 한 자릿수 싸다.
  • 병목은 네트워크·DNS에서 CPU로 옮겨 갔다. 빠른 파서, 페이지 자르기, TLS 비용을 감안한 CPU 배분으로 푼다.
  • 메모리 계산은 평균이 아니라 꼬리를 본다. 인기 도메인 몇 개의 프런티어가 전체를 쓰러뜨린다.

관련 노트: 웹 검색엔진의 동작: 크롤링, 색인, 랭킹, 질의 처리, 비동기 서버의 이벤트 루프: 다중 접속 서버에서 Reactor 패턴까지, 웹 확장성의 구성 요소: 한 장으로 보는 웹 아키텍처, SEO 기초: 크롤링부터 메타 태그까지

원본 기록1

댓글

GitHub 계정으로 댓글을 남길 수 있어요.