리다이렉트 처리 설계 문서 - LeeEuyJoon/lilling-be GitHub Wiki

1. 개요

Lilling 은 대규모 URL 단축 서비스로,
사용자가 단축된 URL에 접근하면 시스템이 원본 URL로 리다이렉트를 수행한다.

이때 사용되는 HTTP 상태 코드(301 vs 302)는
단순한 의미 이상의 차이를 가지며, 트래픽 부하와 캐싱 효율에 직접적인 영향을 준다.

본 문서는 Lilling이 301 Moved Permanently 를 채택한 이유와
그 내부 처리 구조를 설명한다.

2. 코드 선택 배경

2.1. 트래픽 특성

Lilling은 읽기 중심(READ-heavy) 서비스다.
대부분의 요청은 “단축 URL → 원본 URL” 리다이렉트에 해당한다.

항목 추정치 설명
일일 생성 요청(쓰기) 약 1억 건 신규 단축 URL 생성
리다이렉트 요청(읽기) 약 10배 (11,600 QPS) 단축 URL 접근
읽기 : 쓰기 비율 약 10 : 1 캐싱 효율 극대화 가능

이러한 구조에서 리다이렉트 응답의 캐싱 여부
시스템 부하에 절대적인 영향을 미친다.

2.2. 302의 한계

초기 설계에서는 302 Found를 검토했으나 다음 문제들이 드러났다.

  • 모든 요청이 서버로 도달
    • 브라우저가 302 응답을 캐싱하지 않아
      동일한 단축 URL 요청 시 매번 서버 부하 발생
  • 네트워크 및 I/O 병목
    • Redis 캐시를 두더라도 트래픽 자체가 감소하지 않음
    • 인기 단축 URL의 반복 요청 시 네트워크 비용 급증
  • 정적 리소스와 부적합
    • 단축 URL은 수정되지 않는 불변(immutable) 리소스이므로
      “임시 이동” 성격의 302는 데이터 모델과 맞지 않음

2.3. 301 채택 근거

Lilling은 최종적으로 301 Moved Permanently 를 채택했다.

항목 301의 효과
캐싱 정책 브라우저·프록시에서 응답을 캐싱해 반복 요청 차단
부하 절감 초당 수만 건의 요청 중 대부분이 캐시로 처리
리소스 성격 단축 URL은 불변 리소스 → “영구 이동” 의미와 일치
확장성 캐시 기반 트래픽 감소로 서버·네트워크 비용 최소화

3. 내부 리다이렉트 처리 구조

3.1. 전체 동작 흐름

[Client] → [Nginx Reverse Proxy] → [App Server]
    ① 단축 URL 요청
    ② 인코딩 문자열 추출
    ③ Base62 디코딩 → Scrambled ID 변환
    ④ DB/Redis 조회 (Scrambled ID → Original URL)
    ⑤ HTTP 301 응답 (Location: original URL)

3.2. Base62 디코딩 적용 이유

초기에는 인코딩된 문자열을 그대로 쿼리 키로 사용할 계획이었으나,
문자열 비교는 정수형보다 느리고, 인덱스 효율도 낮다.

⚙️ 이유

  • 문자열은 바이트 단위 비교가 필요해 CPU 연산 비용이 높음
  • DB 인덱스(B-tree)는 숫자형에 최적화되어 문자열 키는 탐색 깊이가 증가함
  • 이후 성능 비교 실험 예정

따라서 Lilling은 Base62 → 정수 디코딩 후 조회 구조를 채택했다.

최적화된 조회 파이프라인 예시

단계 동작 예시
1 단축 URL에서 인코딩 문자열 추출 /aZx93pQ
2 Base62 → 정수 디코딩 scrambled_id = 1234567890
3 DB/Redis 조회 SELECT original_url FROM url_mapping WHERE scramble_value = :scrambled_id
4 HTTP 301 응답 Location: {original_url} 반환

4. 결론

Lilling은 읽기 중심 구조와 불변 리소스 특성을 고려해
301 Permanent Redirect를 채택했다.

브라우저·프록시 캐싱으로 트래픽 부하를 획기적으로 줄이고,
응답 속도를 고려하여 Base62 디코딩 기반의 정수 조회를 채택했다.