대규모 트래픽 환경에서 수백만 개의 정적(static) 리다이렉트를 낮은 지연 시간으로 처리하는 것은 시스템 설계의 핵심 과제입니다. 기존 Vercel의 라우팅 규칙(routing rules)이나 미들웨어(middleware)는 각각 한계에 봉착했습니다. 라우팅 규칙은 최대 2,000개의 복잡한 리다이렉트를 지원하지만, 요청당 계산 비용이 증가하는 선형적 문제를 안고 있습니다. 반면, 미들웨어는 유연성을 제공하지만 모든 요청에서 추가 코드를 실행하여 지연 시간(latency)을 늘립니다.
Vercel은 이 문제를 해결하기 위해 전용 조회 경로를 구축하고, 요청당 처리 시간이 근사 상수 시간 또는 로그 시간에 가까워야 했습니다. 그들은 Bloom 필터와 샤딩(sharding) 기법을 결합하는 방식으로 접근했습니다.
1. 초기 설계 및 Bloom 필터 도입:
가장 먼저, 리다이렉트 데이터와 Bloom 필터를 하나의 파일에 통합하여 JSONL 형식을 사용했습니다. Bloom 필터는 원소의 멤버십 여부를 확률적으로 테스트하는 자료구조로, 오탐(false positive)은 가능하지만 미탐(false negative)은 절대 없습니다. 이 필터를 먼저 확인하면, 리다이렉트가 없는 요청(No Redirect)에 대해서는 조회를 완전히 건너뛰어 비용을 극도로 낮출 수 있었습니다.
2. 샤딩(Sharding) 기법의 도입:
하지만 백지 계산(napkin math) 결과, 수백 메가바이트에 달하는 단일 파일은 지연 시간과 메모리 예산을 초과할 위험이 컸습니다. 해결책으로 리다이렉트 경로를 해시하여 여러 개의 작은 조각(shard)으로 분산 저장했습니다. 이는 전체 데이터셋을 한 번에 로드하는 대신, 요청별로 필요한 작은 슬라이스만 불러와서 메모리 부하를 외부 스토리지 및 파일 시스템 캐시로 전환했습니다.
3. 병목 현상 해결: 바이너리 검색(Binary Search)으로의 진화:
이 과정에서 새로운 병목 지점이 발견되었습니다. Bloom 필터가 양성 일치(positive match)를 가리키면, 관련 샤드 전체 JSON을 파싱해야 했는데, 이 JSON 파싱 과정 자체가 CPU 집약적이며 높은 부하 상황에서 심각한 지연 시간 스파이크(latency spikes)를 유발했습니다.
이 문제를 해결하기 위해 데이터 저장 방식을 근본적으로 변경했습니다. 리다이렉트를 JSON 덩어리로 저장하는 대신, 리다이렉트 경로를 키로 하는 이진 검색 구조를 채택했습니다. 각 샤드는 리다이렉트 키를 정렬된 순서로 저장하므로, 로그 시간($O( ext{log } N)$) 복잡도로 원하는 키를 찾을 수 있습니다. 키를 찾은 후에야 해당 특정 리다이렉트에 대한 JSON만 파싱하면 됩니다.
결과 및 의의:
이진 검색 도입으로 인해 조회 비용은 더 이상 샤드 내 전체 데이터 크기에 비례하지 않게 되었습니다. 이는 캐시 적중률(cache hit rates)을 높이기 위해 샤드를 크게 유지할 수 있게 하면서도, 전체 JSON 파싱 오버헤드는 완전히 제거했습니다. 결과적으로 리다이렉트가 실제로 존재하는 요청의 처리 속도가 빨라지고 예측 가능성이 높아져, 고부하 상황에서도 자원 경쟁(resource contention) 문제가 사라졌습니다.
Vercel은 이처럼 단순한 추상화(리다이렉트)를 대규모의 대부분 콜드 데이터셋과 엄격한 엣지 지연 시간 기대치에 결합하는 복잡성을 해결하며, 전용 벌크 리다이렉트 경로를 구축했습니다. (현재 Pro 및 Enterprise 고객 대상이며, 최대 100만 개까지 지원합니다.)