개발 slecs

악용으로 터지던 PDF 무료 미리보기, 3페이지 제한으로 안정화

목차

무제한 무료 PDF 미리보기 기능을 제공했는데, 악의적인 사용자들이 대용량 파일을 계속 요청하면서 서비스가 자주 장애를 겪기 시작했다. 이 문제를 간단하면서도 효과적인 3페이지 제한으로 해결한 작업을 정리해본다.

문제: 무제한의 두 가지 악순환

처음 이 기능을 설계할 때는 "무료 사용자도 충분히 체험하도록"이라는 기조로 시작했다. 하지만 현실은 더 복잡했다.

첫 번째 문제는 악용이었다. 무료 사용자(정확히는 악의적인 사용자)가 제한 없이 대용량 PDF 파일(수십~수백 MB, 수백 페이지)을 연속으로 요청하기 시작했다. 각 요청마다 서버의 CPU와 메모리를 많이 소비하는데, 하나의 악의적인 패턴이 시스템 전체를 느리게 만들었다. 특히 이커머스 시즌에 정상 사용자들의 요청이 몰릴 때와 겹치면 전체 응답 속도 저하로 이어졌다.

두 번째는 Cloudflare 타임아웃이었다. CDN과 DDoS 방어를 위해 Cloudflare를 거치고 있는데, 매우 큰 PDF 파일의 추출 요청이 오면 단일 요청의 처리 시간이 Cloudflare의 기본 Request Timeout 한도를 넘곤 했다. 그러면 사용자는 504 Gateway Timeout 에러를 받게 되고, 우리는 원인을 찾기 위해 디버깅을 해야 했다. 실제로 API 로그에는 아무것도 기록되지 않았고(Cloudflare에서 차단되었으므로), 모니터링이 어려워졌다.

해결: 무료 미리보기를 3페이지로 제한하기

가장 간단하면서도 효과적인 해결책은 무료 미리보기를 첫 3페이지만 추출 가능하게 제한하는 것이었다.

관점 무제한 (이전) 3페이지 제한 (현재)
악용 방지 ❌ 무제한 악용 가능 ✅ 대용량 악용 원천 차단
응답 시간 불안정 (5~60초) 안정적 (0.5~2초)
Cloudflare 타임아웃 빈번 발생 거의 없음
무료 사용자 체험 무제한 (하지만 느림) 제한적 (하지만 빠름)

3페이지라는 숫자는 다음 세 가지를 고려해서 정했다:

1) 충분한 미리보기 체험
서비스의 가치를 판단하기에 3페이지면 충분했다. 한두 페이지는 너무 작아서 문서의 내용을 판단할 수 없고, 5페이지를 넘으면 실제 유료 사용자와의 차이가 희미해진다. 우리의 핵심은 "체험"이지 "완전한 서비스"가 아니었다.

2) 리소스 사용량의 실질적 제어
3페이지 추출에 필요한 CPU와 메모리는 무시할 수 있는 수준이다. 설령 악용자가 계속 요청하더라도 시스템에 주는 부담이 미미하다. 이는 동시에 정상 사용자의 요청을 빠르게 처리할 여유를 확보한다는 뜻이다.

3) Cloudflare 안정성 보장
3페이지 추출은 거의 항상 몇 초 안에 완료된다. 이는 Cloudflare의 타임아웃 한도 내에 안전하게 들어간다는 보장이다.

구현과 트레이드오프

// src/app/api/extract/route.ts (개념적 구조)
async function extractPdf(file: File, isFreeUser: boolean) {
  const pages = await parsePdf(file)

  // 무료 사용자는 최대 3페이지
  if (isFreeUser && pages.length > 3) {
    return {
      pages: pages.slice(0, 3),
      totalPages: pages.length,
      wasCapped: true,
      message: "무료 미리보기는 처음 3페이지입니다. 전체 내용 추출은 유료 구독이 필요합니다."
    }
  }

  return {
    pages: pages,
    totalPages: pages.length,
    wasCapped: false
  }
}

이 결정은 분명히 무료 사용자에게는 "제한"이다. 하지만 얻은 이득이 더 컸다:

  • 정상 사용자 경험 개선: 느려진 응답 대신 빠르고 안정적인 경험을 제공하게 되었다.
  • 운영 비용 감소: 악용으로 인한 과도한 리소스 소비가 줄었고, 장애 대응 비용도 감소했다.
  • 신뢰성 향상: Cloudflare 타임아웃 장애가 거의 사라져서 모니터링도 간단해졌다.
  • 유료 전환 유인: 명확한 "제한"은 오히려 자연스러운 업셀 포인트가 되었다.

팀 내에서는 "정말 3페이지일까? 2페이지는? 5페이지는?"이라는 논의가 있었다. 결론은 이 정도가 최적이라는 데 모았다. 너무 관대하면 악용 방지 효과가 떨어지고, 너무 인색하면 무료 사용자들의 이탈 위험이 생겼기 때문이다. 팀장으로서 이런 "정성적인 한계값 결정"이 가장 어려운 부분이었다.

배운 점: 합리적 한계와 투명성

이번 작업을 통해 깨달은 가장 큰 교훈은, 무료 서비스의 한계값을 정하기는 과학보다는 예술에 가깝다는 것이다.

우리는 보통 데이터 기반 의사결정을 추구한다. 하지만 "최적의 페이지 수"를 찾기 위해 A/B 테스트를 할 수도 없었다(이미 장애가 터지고 있었으니까). 결국 경험과 직관에 의존할 수밖에 없었고, 다행히 그 선택이 옳았다.

비슷한 상황에서 고려할 만한 전략들:

  • 레이트 제한: 초당 API 요청 수 제한 (예: 3req/sec)
  • 용량 제한: 파일 크기 제한 (예: 최대 10MB)
  • 시간 제한: 단일 요청의 최대 처리 시간 (예: 10초)
  • 누적 제한: 월간 총 사용 한도 (예: 500페이지/월)

우리가 선택한 "페이지 수 제한"은 구현이 간단하고, 사용자 입장에서도 가장 직관적이었다.

한 가지 중요한 건, 제한을 설정한 후 반드시 사용자에게 명확하게 알려야 한다는 것이다. 숨겨진 제한은 신뢰를 깬다. 우리는 UI에서 제한 사실을 분명히 표시했고, 유료 구독 옵션도 함께 제시했다. 이렇게 투명하게 하니 사용자들의 반감도 거의 없었다.

이 변경 후로 서비스의 안정성이 눈에 띄게 개선되었다. 장애 건수가 90% 이상 줄었고, 평균 응답 시간도 10배 이상 빨라졌다. 무료 사용자 중 일부의 이탈이 있을 수 있지만, 전체 시스템 건강도와 나머지 사용자들의 만족도 측면에서는 분명한 성공이었다.


🛒 이 글과 어울리는 추천 상품

*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

댓글 0

첫 댓글 달아줘.