Claude 구독 요금제의 실제 사용 한도를 SSE 응답의 반올림되지 않은 부동소수점 값에서 역추적해, 크레딧 체계·캐시 비용·API 대비 가성비를 정량화한 분석.
homeabout 수상할 정도로 정밀한 부동소수점, 혹은
2026년 1월 25일
이 글을 읽고 있다면, 아마 Claude의 구독 요금제가 API보다 훨씬 이득이라는 건 알고 있을 것이다. 하지만 정확히 얼마나 더 이득일까? 그리고 실제 한도는 얼마나 될까? 나는 반올림되지 않은 두 개의 부동소수점 값에서 정확한 값을 뽑아냈고, 꽤 눈에 띄는 점들을 발견했다. 어떻게 했는지는 뒤에서 설명하겠지만, 먼저 결과부터 보자.
20× 플랜은 기대만큼 좋은 딜이 아니다. Anthropic 사이트에서 “20× 더 많은 사용량*”이라는 문구는 항상 그 성가신 별표(asterisk)와 함께 붙어 있다. 그 별표가 많은 일을 하고 있다. 5시간 세션 한도는 Pro보다 정말로 20× 높지만, 진짜 질문은 “그걸로 일주일에 실제로 얼마나 더 많은 일을 할 수 있느냐”다. 답은: 5× 플랜 대비 주간 기준으로는 두 배밖에 안 된다.
반대로 5× 플랜은 돈값을 정말 잘한다. 약속한 것보다 꽤 크게 ‘오버딜리버’한다. 가격표의 스위트 스폿이다. Pro 대비 세션 한도가 다섯 배가 아니라 여섯 배이고, 주간 한도는 여덟 배를 훌쩍 넘는다 (이름에 붙은 다섯을 넘는다).
| 티어 | 크레딧/5h | 크레딧/주 |
|---|---|---|
| Tier Pro | 크레딧/5h 550,000 (1×) | 크레딧/주 5,000,000 (1×) |
| Tier Max 5× | 크레딧/5h 3,300,000 (6×) | 크레딧/주 41,666,700 (8.33×) |
| Tier Max 20× | 크레딧/5h 11,000,000 (20×) | 크레딧/주 83,333,300 (16.67×) |
API 가격과 비교하면 모든 플랜이 환상적으로 보인다. 표의 가치 추정은 _하한선_인데, 캐싱 때문에 실질적인 API-환산 비용은 더 유리해진다(잠시 후 설명). 어쨌든 API 대신 플랜 가격을 쓸 수 있다면, 무조건 그걸 쓰자.
| 티어 | 가격 | 크레딧/월 | Opus-요율 토큰 | API 환산 비용 |
|---|---|---|---|---|
| Tier Pro | 가격$20 | 크레딧/월 21.7M | Opus-요율 토큰 입력 32.5M 또는 출력 6.5M | API 환산 비용$163 (8.1×) |
| Tier Max 5× | 가격$100 | 크레딧/월 180.6M | Opus-요율 토큰 입력 270.9M 또는 출력 54.2M | API 환산 비용$1,354 (13.5×) |
| Tier Max 20× | 가격$200 | 크레딧/월 361.1M | Opus-요율 토큰 입력 541.7M 또는 출력 108.3M | API 환산 비용$2,708 (13.5×) |
이 표에 없지만 아주 중요한 것이 하나 있다.
캐시 읽기(cache read). 완전히 무료다.
이건 수학을 더더욱 플랜 쪽으로 기울게 만든다. 에이전틱 루프(예: Claude Code)에서는 모델이 한 턴마다 수십 번의 툴 호출을 한다. 툴 호출 뒤에는 모델이 다시 호출된다. 그때마다 전체 컨텍스트에 대한 캐시 읽기가 발생한다. API는 읽기마다 입력 비용의 10%를 청구하지만, 구독은 아무것도 청구하지 않는다. 이건 금방 누적된다(곧 계산으로 보자).
캐시 쓰기(cache write)도 할인되긴 하는데, API에서는 입력 가격의 1.25×/2×1가 든다. 반면 플랜에서는 일반 입력 가격(할인 없음)으로 청구된다. 모든 채팅 턴은 읽히기 전에 캐시에 먼저 써져야 하므로, 이것도 꽤 중요하다.
그럼 내가 계속 말하는 이 크레딧이란 뭘까?
플랜 사용량을 내부적으로 추적하는 데 쓰는 단위다. “Credits(크레딧)”은 내가 임의로 붙인 이름이고, 이 값들은 어떤 API 필드에도 직접 드러나지 않아서 딱 맞는 공식 명칭이 없다. “크레딧” 정도면 괜찮다고 본다.
크레딧에서 토큰 수로는 어떻게 가나? 공식은 이렇다:
credits_used = ceil(input_tokens × input_rate + output_tokens × output_rate)
credits_used =
ceil(input_tokens × input_rate
+ output_tokens × output_rate)
...그리고 여기에 넣는 값들은 다음과 같다:
| 모델 | 입력 크레딧/토큰 | 출력 크레딧/토큰 |
|---|---|---|
| 모델 Haiku | 입력 크레딧/토큰 2/15 = 0.133... | 출력 크레딧/토큰 10/15 = 2/3 = 0.666... |
| 모델 Sonnet | 입력 크레딧/토큰 6/15 = 2/5 = 0.4 | 출력 크레딧/토큰 30/15 = 2 |
| 모델 Opus | 입력 크레딧/토큰 10/15 = 2/3 = 0.666... | 출력 크레딧/토큰 50/15 = 10/3 = 3.333... |
구체적인 값들은 꽤 임의로 보이지만, 비율은 API 가격 체계를 그대로 반영한다. 출력이 입력의 5×, Opus는 Haiku보다 5× 비싸고, 등등.
실제로 쓸 만한 숫자에 대입해 보자.
먼저 현실적인 최악의 경우부터: 캐싱은 켜져 있지만 캐시가 차갑다(cold). (진짜 최악은 캐싱을 완전히 끈 경우지만, 그건 드물다.)
구독 크레딧
ceil(100K × 2/3 + 1K × 10/3) = 70,000
API 비용
캐시 쓰기: 100K × $5/M × 1.25 = $0.625
출력: 1K × $25/M = $0.025
합계: $0.650
Max 5× 주간
floor(41,666,700 / 70,000) = 595 req/wk
595 × $0.650 = $386.75/week
$386.75 × 52/12 = $1,676/mo
$100/mo를 내고 있음 -> 16.8× 가치
이것만 해도 이미 엄청난 가치고, 이게 현실에서의 기준선이다(캐싱을 꺼도 ~13×였다). 루프에 들어가 캐시가 따뜻해지면(warm) 훨씬 더 좋아진다:
구독 쪽에서는 1K의 새 토큰만 센다는 점에 주목하자. API 쪽에서는 100K 캐시 읽기가 여전히 입력의 10% 비용이 든다.
구독 크레딧
ceil(1K × 2/3 + 1K × 10/3) = 4,000
API 비용
캐시 읽기: 100K × $5/M × 0.1 = $0.05
캐시 쓰기: 1K × $5/M × 1.25 = $0.00625
출력: 1K × $25/M = $0.025
합계: $0.08125
Max 5× 주간
floor(41,666,700 / 4,000) = 10,416 req/wk
10,416 × $0.08125 = $846.30/week
$846.30 × 52/12 = $3,667/mo
$100/mo를 내고 있음 -> 36.7× 가치
API 대비 36배가 넘는 가치.
좋다, 요점 정리는 여기까지. 약속했던 수상할 정도로 정밀한 부동소수점 이야기로 넘어가자.
이 숫자들을 어떻게 다 얻었을까?
작년 가을, Claude.ai 설정 페이지에 새 탭이 생겼다. 사용량(usage) 탭으로, 남은 한도를 두 개의 진행 바로 보여준다.2 곧바로 나는 Claude 채팅과 그 페이지를 왔다 갔다 하게 됐다. 특히 채팅이 길어지면(그리고 캐시가 안 되면, 하지만 그건 다른 이야기) 한도가 _순식간_에 닳는다.
그래서 나는 확장 프로그램을 만들기로 했다.3 우선 사용량 페이지 자체가 어떻게 구현되어 있는지 봤다. 꽤 단순했다. /usage 엔드포인트가 아주 작은 JSON 조각을 반환하는데, 숫자가 가장 가까운 퍼센트 포인트로 반올림되어 있었다. 내가 원한 건 숫자를 더 편하게 보는 방법이었으니, 그걸로도 충분했다.
하지만 더 파고들다가 곧 흥미로운 걸 찾았다. Max 5× 계정에서 생성(generation) 엔드포인트의 SSE 응답은 사용량 값을 반올림되지 않은 double로 내보내고 있었다: 0.16327272727272726.
수상할 정도로 구체적이다. 어떤 분수를 10진수로 바꾼 것처럼 보이기도 한다. 그렇다면 원래 분수를 복구해서 진짜 한도를 얻을 수 있을까? 결론부터 말하면, 가능하다.
실수가 float가 되면, 표현 가능한 값 중 가장 가까운 값으로 반올림된다. 그 float는 아주 작은 구간 [L, U) 안에 있는 모든 유리수를 대표한다. 우리가 다루는 0–1 범위에서는 그 구간 폭이 대략 10−17 정도다.4
원래 분수(부동소수점이 되기 전)는 반드시 [L, U) 안에 있다. 우리는 그 구간에서 가장 ‘간단한’ 분수를 찾고 싶다.
왜 가장 간단한 분수인가? 어떤 소수도 기약분수로 바꿀 수는 있지만, 그건 정보를 더 주지 않는다(이 예시에서는 16327272727272726/100000000000000000). 우리가 원하는 건 원래 분수인데, 아마 분모가 100경(10^17) 같은 큰 수는 아닐 것이다.
하지만 잠깐, 소스 분수가 2/10이라면 우리는 1/5를 복구하게 된다! 가장 단순한 분수를 고르면 거짓 양성(false positive)이 나오지 않을까? 한 샘플만 보면 그렇다. 그래서 여러 샘플을 모은 뒤, 나중에 최소공배수(LCM)를 계산한다. 진짜 분모가 10이라면 때로 5나 2를 복구할 수는 있지만(항상 기약분수로 약분되니까), 10을 나누지 못하는 분모를 복구하는 일은 없다. 따라서 LCM은 커질 수만 있고, 실제 한도를 ‘초과’할 수는 없다. 몇 번만 샘플을 뽑으면, 진짜 분모가 더 큰 수일 가능성은 급격히 작아진다.
그럼 버킷으로 돌아가서, 그 안에서 가장 간단한 분수를 어떻게 찾을까?
스턴–브로코트 트리(Stern–Brocot tree)는 모든 양의 분수를 값의 순서대로 이진 탐색하는 구조인데, 더 간단한 분수가 먼저 나오도록 구성되어 있다. 0/1과 1/0(무한대)에서 시작해서, 매 단계마다 목표 구간을 향해 좁혀 간다. 0.4의 분수를 찾는 예시는 이렇다:
우리의 사용 사례에서는 정확한 수가 아니라 아주 아주 작은 구간(~10−17)을 목표로 한다. 하지만 과정은 사실상 거의 똑같다.
원래의 0.16327272727272726로 돌아가 보자. 버킷 안에서 처음 맞아떨어지는 값은 분모가 아주 작은 분수(대개 최소 분모)다:
step 00: left=0/1 right=∞ mediant=1 -> mediant ≥ U, move left
step 01: left=0/1 right=1/1 mediant=1/2 -> mediant ≥ U, move left
step 02: left=0/1 right=1/2 mediant=1/3 -> mediant ≥ U, move left
step 03: left=0/1 right=1/3 mediant=1/4 -> mediant ≥ U, move left
step 04: left=0/1 right=1/4 mediant=1/5 -> mediant ≥ U, move left
step 05: left=0/1 right=1/5 mediant=1/6 -> mediant ≥ U, move left
step 06: left=0/1 right=1/6 mediant=1/7 -> mediant < L, move right
step 07: left=1/7 right=1/6 mediant=2/13 -> mediant < L, move right
step 08: left=2/13 right=1/6 mediant=3/19 -> mediant < L, move right
... 57 more steps ...step 09: left=3/19 right=1/6 mediant=4/25 -> mediant < L, move right
step 10: left=4/25 right=1/6 mediant=5/31 -> mediant < L, move right
step 11: left=5/31 right=1/6 mediant=6/37 -> mediant < L, move right
step 12: left=6/37 right=1/6 mediant=7/43 -> mediant < L, move right
step 13: left=7/43 right=1/6 mediant=8/49 -> mediant < L, move right
step 14: left=8/49 right=1/6 mediant=9/55 -> mediant ≥ U, move left
step 15: left=8/49 right=9/55 mediant=17/104 -> mediant ≥ U, move left
step 16: left=8/49 right=17/104 mediant=25/153 -> mediant ≥ U, move left
step 17: left=8/49 right=25/153 mediant=33/202 -> mediant ≥ U, move left
step 18: left=8/49 right=33/202 mediant=41/251 -> mediant ≥ U, move left
step 19: left=8/49 right=41/251 mediant=49/300 -> mediant ≥ U, move left
step 20: left=8/49 right=49/300 mediant=57/349 -> mediant ≥ U, move left
step 21: left=8/49 right=57/349 mediant=65/398 -> mediant ≥ U, move left
step 22: left=8/49 right=65/398 mediant=73/447 -> mediant ≥ U, move left
step 23: left=8/49 right=73/447 mediant=81/496 -> mediant ≥ U, move left
step 24: left=8/49 right=81/496 mediant=89/545 -> mediant ≥ U, move left
step 25: left=8/49 right=89/545 mediant=97/594 -> mediant ≥ U, move left
step 26: left=8/49 right=97/594 mediant=105/643 -> mediant ≥ U, move left
step 27: left=8/49 right=105/643 mediant=113/692 -> mediant ≥ U, move left
step 28: left=8/49 right=113/692 mediant=121/741 -> mediant ≥ U, move left
step 29: left=8/49 right=121/741 mediant=129/790 -> mediant ≥ U, move left
step 30: left=8/49 right=129/790 mediant=137/839 -> mediant ≥ U, move left
step 31: left=8/49 right=137/839 mediant=145/888 -> mediant ≥ U, move left
step 32: left=8/49 right=145/888 mediant=153/937 -> mediant ≥ U, move left
step 33: left=8/49 right=153/937 mediant=161/986 -> mediant ≥ U, move left
step 34: left=8/49 right=161/986 mediant=169/1035 -> mediant ≥ U, move left
step 35: left=8/49 right=169/1035 mediant=177/1084 -> mediant ≥ U, move left
step 36: left=8/49 right=177/1084 mediant=185/1133 -> mediant ≥ U, move left
step 37: left=8/49 right=185/1133 mediant=193/1182 -> mediant ≥ U, move left
step 38: left=8/49 right=193/1182 mediant=201/1231 -> mediant ≥ U, move left
step 39: left=8/49 right=201/1231 mediant=209/1280 -> mediant ≥ U, move left
step 40: left=8/49 right=209/1280 mediant=217/1329 -> mediant ≥ U, move left
step 41: left=8/49 right=217/1329 mediant=225/1378 -> mediant ≥ U, move left
step 42: left=8/49 right=225/1378 mediant=233/1427 -> mediant ≥ U, move left
step 43: left=8/49 right=233/1427 mediant=241/1476 -> mediant ≥ U, move left
step 44: left=8/49 right=241/1476 mediant=249/1525 -> mediant ≥ U, move left
step 45: left=8/49 right=249/1525 mediant=257/1574 -> mediant ≥ U, move left
step 46: left=8/49 right=257/1574 mediant=265/1623 -> mediant ≥ U, move left
step 47: left=8/49 right=265/1623 mediant=273/1672 -> mediant ≥ U, move left
step 48: left=8/49 right=273/1672 mediant=281/1721 -> mediant ≥ U, move left
step 49: left=8/49 right=281/1721 mediant=289/1770 -> mediant ≥ U, move left
step 50: left=8/49 right=289/1770 mediant=297/1819 -> mediant ≥ U, move left
step 51: left=8/49 right=297/1819 mediant=305/1868 -> mediant ≥ U, move left
step 52: left=8/49 right=305/1868 mediant=313/1917 -> mediant ≥ U, move left
step 53: left=8/49 right=313/1917 mediant=321/1966 -> mediant ≥ U, move left
step 54: left=8/49 right=321/1966 mediant=329/2015 -> mediant ≥ U, move left
step 55: left=8/49 right=329/2015 mediant=337/2064 -> mediant ≥ U, move left
step 56: left=8/49 right=337/2064 mediant=345/2113 -> mediant ≥ U, move left
step 57: left=8/49 right=345/2113 mediant=353/2162 -> mediant ≥ U, move left
step 58: left=8/49 right=353/2162 mediant=361/2211 -> mediant ≥ U, move left
step 59: left=8/49 right=361/2211 mediant=369/2260 -> mediant ≥ U, move left
step 60: left=8/49 right=369/2260 mediant=377/2309 -> mediant ≥ U, move left
step 61: left=8/49 right=377/2309 mediant=385/2358 -> mediant ≥ U, move left
step 62: left=8/49 right=385/2358 mediant=393/2407 -> mediant ≥ U, move left
step 63: left=8/49 right=393/2407 mediant=401/2456 -> mediant ≥ U, move left
step 64: left=8/49 right=401/2456 mediant=409/2505 -> mediant ≥ U, move left
step 65: left=8/49 right=409/2505 mediant=417/2554 -> mediant ≥ U, move left
step 66: left=8/49 right=417/2554 mediant=425/2603 -> mediant ≥ U, move left
step 67: left=8/49 right=425/2603 mediant=433/2652 -> mediant ≥ U, move left
step 68: left=8/49 right=433/2652 mediant=441/2701 -> mediant ≥ U, move left
step 69: left=8/49 right=441/2701 mediant=449/2750 -> HIT
왕복 검증(round-trip check): 449/2750를 출력하면 0.16327272727272726 ✓
각 샘플은 분모를 하나씩 준다. 진짜 한도 D는 그 분모들 모두로 나누어떨어져야 한다. 예를 들어 449/2750과 11401/75000은 모두 분모 3,300,000으로 스케일된다.
왜 한도는 이들 모두로 나누어떨어져야 할까? 사용률(utilization)은 문자 그대로 “사용량 / 한도”이기 때문이다. float에서 분수를 복구하면 기약분수로 약분된다. 그래서 우리가 복구하는 분모는 진짜 한도의 약수일 수밖에 없다.
따라서 몇 개 샘플을 모아 분모들을 수집한 다음, 그들의 LCM을 구하면 된다. 처음에는 값이 이리저리 튀다가, 어느 순간 멈춘다. 다양한 사용량에서 LCM이 계속 같은 값으로 유지되면, 그 LCM이 바로 한도다. 여전히 운이 나쁠 가능성은 있지만, 몇 번만 샘플을 모으면 확률은 매우 작아진다.
마지막으로, 크레딧-토큰 공식과 모델 배수를 어떻게 얻었을까? 수동 데이터 수집을 잔뜩 한 다음, 내 확장을 개조해 채팅하는 동안 데이터를 저장하도록 만들고 자동 수집을 했다. 다 표로 모아 놓고 한참 쳐다봤다. Claude에게도 물어보고, GPT에게도 물어봤다. 가설을 세우고 검증했고, 결국 위의 표와 공식에 도달했다. 이 부분에 대해 더 말하고 싶지만 과정이 좀 혼돈스러웠고 메모도 안 남겼다. 중요한 건 최종 숫자들을 검증했고 정확히 맞아떨어진다는 점이다.
사이드 채널은 어디에나 있다. Anthropic의 누군가가 숫자 두 개를 반올림하지 않은 것만으로 정확한 가격표가 새어나갈 거라고는 아무도 예상 못 했을 거다.
가능하면 플랜을 쓰는 게 좋다. 대부분의 사람에게 Claude Code를 API 가격으로 쓰는 건 플랜 대비 재정적으로 말이 안 된다. 주된 예외는 조직적 이유로 API를 강제당하는 경우(엔터프라이즈/팀 구성, 조달 등)인데, 그런 경우엔 비교 자체가 덜 의미 있다.
Claude의 사용 한도가 궁금하다면(그리고 이 한도에 대해 길게 설명한 글의 끝까지 왔다는 건, 궁금하다는 뜻일 테니) 내 Claude Counter 확장을 써 보라. 캐시 타이머, 컴포저 박스 안에 바로 표시되는 사용량 바(완전 정밀도), 그 밖의 여러 기능을 보여준다.
글을 쓰는 시점에서, float 값들은 여전히 반올림되지 않았고 수상할 정도로 정밀하다. 이 글이 주목을 좀 받으면, 오래가진 않을 거라고 본다. 그러면 내 확장이 조금 나빠질 거라서 약간 슬플 것 같다. (공식 사용량 페이지가 쓰는 것과 같은 /usage 엔드포인트에 의존해야 할 텐데, 그건 반올림되어 있다.)
댓글: Hacker News 또는 Twitter.
1 5분 캐시는 1.25×, 1시간 캐시는 2×.
2 응, Max 플랜에서는 3개인 거 안다.
3 공정하게 말하자면, 처음에는 기존 확장 하나를 찾아서 계속 고치다가 더는 말이 안 되어서, 원본과 다른 목적을 가지고 처음부터 다시 썼다.
4 IEEE-754 float는 가수 비트 수가 고정이라 정밀도가 크기에 따라 달라진다. 큰 float일수록 반올림 버킷이 넓다: 0.1에서는 ~10⁻¹⁷, 1.0에서는 ~10⁻¹⁶, 1000에서는 ~10⁻¹³. 사용량 비율은 항상 0–1이므로 ~10⁻¹⁷ 구간이다.
5 1. 문제를 적는다. 2. 아주 열심히 생각한다. 3. 해답을 적는다.
“SternBrocotTree.svg” by Aaron Rotenberg, modified and used under CC BY-SA 3.0 (source: Wikimedia Commons).