Workers Free 플랜의 CPU 한도를 웃도는 요청이 걸리고 있었던 걸 발견했다..
새벽에 서비스 확인 차 kculturepaju.com에 들어갔다.
출연진 소개 페이지에 사진이 안 뜨고 있었다. 새로고침을 몇 번 해봐도 그대로였고, 결국은 Cloudflare가 띄우는 "rate limited" 에러 페이지까지 봤다.

트래픽이 몰릴 시간대도 아닌 새벽인데 rate limit이라니, 뭔가 잘못됐다는 직감이 들어서 바로 Cloudflare 대시보드로 들어갔다.
Workers 프로젝트(k-culture-festival)의 메트릭 화면을 열었더니 숫자가 심상치 않았다.

Error 1102 (Worker exceeded resource limits)로 찍혀 있었다이 프로젝트는 저번 포스팅에서 다뤘듯 Workers Free 플랜으로 운영 중이었다. Free 플랜의 요청당 CPU 한도가 10ms라는 건 알고 있었지만, 지금까지 별 문제가 없었으니 "우리 트래픽 규모면 충분하다"고 막연히 생각하고 있었다.
그 생각이 틀렸다는 걸 대시보드 숫자가 바로 알려줬다.
이전에 진행한 '모두하나대축제' 프로젝트는 처음부터 Vercel Pro 요금제를 결제해서 운영했다. 그래서 무료 플랜의 리소스 한도를 실제로 겪어볼 일이 없었다.
이번 프로젝트에서 Cloudflare Workers를 처음 선택하면서도, "이전에도 문제없었으니 이번에도 괜찮겠지" 하는 근거 없는 안심이 있었던 것 같다. 무료 플랜으로 감당이 안 될 수 있다는 가능성을 초기에 점검하지 않고 넘어간 게 이 사달의 시작이었다.
제일 먼저 의심한 건 트래픽 몰림이었다. 새벽에도 오류가 나는 게 이상했지만, 그래도 "동시 접속자가 늘면 서버 자원을 나눠 쓰다 못 버티는" 흔한 시나리오를 먼저 떠올렸다.
그런데 Cloudflare Workers의 CPU 시간 한도는 애초에 요청 1건 단독 기준이다. 동시에 들어온 요청끼리 CPU 자원을 놓고 같이 쓰거나 경쟁하지 않는다. 방문자가 1명이든 100명이든, 각 요청은 독립적으로 자기 CPU 한도만 넘기면 그대로 실패한다.
Limits
Cloudflare Workers plan and platform limits.

그렇다면 왜 오류가 발생했을까.
계정의 Worker와 연결된 Cloudflare Agent에 질문을 해본 결과 해당 Worker에는 CPU 자원 경쟁이 문제가 아니라, 단순히 "한도를 넘길 위험이 있는 요청"의 발생 건수 자체가 많았기 때문이었다.
Free 플랜의 요청당 CPU 한도는 10ms로 명시돼있다.
계정 사용량 페이지에서 실제 사용량을 확인해보니,
요청 1건을 처리하는 데 드는 비용 자체가 이미 한도의 4~6배였다.
트래픽이 적었던 지금까지는 이 초과분이 "가끔 실패하는 요청" 정도로 묻혀 있었을 뿐이지, 애초부터 못받쳐주는 상태였던 것이다.
이 사이트는 겉보기엔 정적인 행사 소개 페이지지만, 실제로는 Next.js(OpenNext) 기반 SSR 구조라 요청마다 다음을 매번 새로 실행한다.
대화형 에이전트를 프로젝트에 연결하고 CPU를 잡아먹는 지점을 특정해봤다.
| 항목 | 결론 |
|---|---|
| OpenNext/Next RSC 렌더링 파이프라인 | 주요 원인으로 추정 (요청마다 반복 실행) |
src/lib/ga4.ts GA4 액세스 토큰 미캐싱 (RSA 서명 매회 재실행) | 부차적 원인, 관리자 통계 페이지에서만 발생 |
| 비밀번호 해싱(PBKDF2), HTML sanitize, 이미지 리사이즈 | 요청 경로와 무관함을 확인, 원인 아님 |
결국 프레임워크 자체의 렌더링 비용이 Free 한도(10ms) 대비 처음부터 과도하게 컸다는 결론이었다.
캐싱을 깜빡했다거나 특정 함수 하나가 느려서가 아니라, SSR이라는 구조 자체와 무료 티어 한도가 애초에 안 맞았던 것이다.
Workers Paid 요금제로 전환했다 ($5/월 + 사용량)
| 항목 | Free | Paid |
|---|---|---|
| 요청당 CPU 한도 | 10ms | 30초 (최대 5분까지 설정 가능) |
| 월 포함 CPU | — (요청당 컷) | 30,000,000ms |
| 월 포함 요청 | 100,000/일 | 10,000,000/월 |
| 한도 초과 시 | 요청 실패 | 초과분 종량 과금 ($0.02/백만ms, $0.30/백만 요청) |
Pricing
Workers plans and pricing information.

핵심은 요청당 CPU 한도가 10ms → 30초(기본값)로, 숫자로 보면 3,000배 늘어난다는 점이다.
SSR 렌더링이 보통 10~20ms대, 무거운 프레임워크는 그 이상까지 CPU를 쓰는 걸 감안하면, Free의 10ms는 애초에 SSR 앱이 버틸 수 있는 한도가 아니었다...
Next.js SSR 정도의 사용량이라면 Paid의 기본값 30초로도 충분히 커버된다.
이 30초 기본값도 필요하면 더 올릴 수 있다. wrangler.jsonc(또는 wrangler.toml)에 아래처럼 명시하면 최대 5분(300,000ms)까지 설정 가능하고,
{
"limits": {
"cpu_ms": 300000
}
}대시보드에서도 Settings → CPU time limit에서 같은 값을 조정할 수 있다. 지금은 기본값으로도 여유가 있어서 따로 건드리진 않았다.
결제하고 나니 요청당 컷이 사실상 사라졌다. 구독은 정상 활성화됐고 현재 결제 주기 종료일은 2026년 9월 25일이라, 축제 기간(9/12)을 넉넉히 덮는다. 도메인 자체의 존 요금제는 그대로 Free로 뒀다. WAF 관리형 규칙이나 봇 스코어링 같은 고급 기능이 지금 이 사이트에 필요한 요구사항은 아니라고 판단했다.
실제 사용량을 관측했다.
숫자만 보면 이번 업그레이드로 축제 기간까지는 비용·안정성 둘 다 여유가 충분하다.
Bot Fight Mode: 수치 측정에서 특정시각에 요청이 과도하게 몰린 지점이 있어 의아했다. 크롤러나 봇들이 앱에 접속해 정보들을 긁어간 것이라 판단했고, 이를 차단할까 생각도 해보았지만 Free 플랜에서는 Googlebot·Bingbot도 예외 없이 차단될 수 있어서 SEO 리스크가 있었다(축제 페이지라 SEO가 생명이다). 지금은 켜지 않고, 필요해지면 WAF 커스텀 규칙(not cf.client.bot)으로 화이트리스팅 방식으로 대체하는 쪽을 생각하고 있다.
GA4 액세스 토큰 캐싱(1시간짜리 토큰을 매 요청마다 재발급하고 있다)도 손봐야 하는데, 관리자 페이지에서만 발생하는 문제이고 관리자 수와 빈도도 많지 않기에 우선순위는 낮게 잡았다.
자주 안 바뀌는 공개 페이지(공지사항 등)에 정적 생성/ISR 캐싱을 붙이면 CPU 비용을 구조적으로 줄일 수 있다. 다만 사전신청 시간에 맞게 링크 버튼 오픈처럼 실시간성이 필요한 페이지는 캐싱에서 제외해야 한다. 공지사항도 관리자들이 주기적으로 내용들을 수정하고 있기에 아직은 보류중이다.
Workers Paid가 "코드 실행(CPU/요청) 비용"을 다룬다면, 존 요금제는 도메인 앞단의 CDN·보안 계층을 다룬다.
여기 나오는 **WAF(Web Application Firewall)**는 요청이 Worker 코드에 도달하기 전에 앞단에서 걸러주는 방화벽이다. SQL 인젝션이나 XSS 같은 알려진 공격 패턴, 비정상적인 스캐닝, 봇 트래픽을 규칙 기반으로 미리 차단해서, 애초에 CPU를 쓸 필요도 없이 요청을 거부한다.
| Free (현재) | Pro ($20/월) | Business ($200/월) | |
|---|---|---|---|
| WAF 규칙 | 5 | 20 | 100 |
| WAF 관리형 규칙셋 (OWASP 등) | 미포함 | 포함 | 포함 |
| 봇 감지/챌린지 | 기본만 | 있음 | 정교한 수준 |
| 이미지 최적화 | 미포함 | 원클릭 최적화 | 동일 |
| 가동시간 SLA | 없음 | 없음 | 100% 보장 |
지금 이 사이트(정보 제공형, 트래픽 소~중 규모)는 Free로 충분하고, 아래 상황이 실제로 벌어질 때만 업그레이드를 검토하면 된다.
/api/admin/*)에 대한 무차별 대입 공격이나 스캐닝이 확인될 때CPU 시간 제한 초과는 트래픽 동시성 문제가 아니라, 요청 1건당 렌더링 비용이 처음부터 무료 플랜에 과도했던 구조적 문제였다. Paid 요금제로 즉시 해결됐고, 축제 기간까지 예상 트래픽을 감안해도 여유는 충분하다.

Paid로 전환한 시점 이후로는 버전별 오류 그래프에 찍히던 막대가 그대로 사라졌다..
이번 일로 뼈아프게 5달러 짜리 교훈을 얻었다...
이새 스택이나 새 플랫폼으로 프로젝트를 시작할 때는 이전 경험과 무관하게, 그 플랫폼의 플랜과 티어 한도를 초기에 반드시 확인해야 한다는 것... 새벽에 놀라서 화들짝 대시보드를 뒤지는 일은 한 번으로 충분하다..