레이트리밋과 토큰 회계 게이트웨이 만들기
목표
LLM 게이트웨이를 직접 만들어 요청 단위가 아닌 토큰 단위의 제한과 회계를 구현하고, 테넌트별 예산 통제까지 완성한다.
왜 중요한가
분당 60요청 제한은 LLM 앞에서 거의 무의미합니다. 어떤 테넌트가 매 요청에 200,000토큰을 넣으면 요청 수는 제한 안이지만 비용과 GPU 점유는 다른 테넌트의 수백 배이고, 그 요청들이 KV 캐시를 독식해 다른 요청들을 선점시킵니다. 그래서 LLM 게이트웨이의 첫 번째 설계 원칙은 토큰을 세는 것입니다. 이 실습에서 특히 까다로운 부분이 4번과 8번입니다. 출력 토큰은 생성이 끝나야 알 수 있으므로, 예산 검사를 완료 후에만 하면 동시에 들어온 요청들이 모두 예산 안이라고 판단하고 통과한 뒤 함께 초과합니다. max_tokens 를 상한으로 미리 예약하고 완료 후 정산하는 방식이 이 문제를 풉니다. 실제 서비스에서 비용 사고가 나는 지점이 정확히 여기입니다.
단계
/root/gw/gateway.py를 127.0.0.1:8171 에 띄우고POST /v1/generate를 8170 백엔드로 전달한다. 응답이 백엔드와 동일해야 한다. 테넌트t1의 API 키를/root/gw/key.txt에,t2의 키를/root/gw/key2.txt에 각각 한 줄로 저장해 둔다 — 이후 스텝의 채점이 이 두 파일을 쓴다.X-API-Key헤더로 테넌트를 식별한다. 키-테넌트 매핑은/opt/fixtures/llms/apikeys.json에 있다. 키가 없거나 모르는 키면 401 이다.- 테넌트별 분당 요청 제한 10 을 적용한다. 11번째 요청이 429 여야 한다.
/root/gw/rpm.txt에allowed=10 rejected=1을 적는다. GET /v1/usage가{"tenant":"...","prompt_tokens":<n>,"completion_tokens":<n>,"requests":<n>}를 준다. 두 토큰 값이 모두 0보다 커야 한다.- 분당 토큰 제한 2000 을 토큰 버킷으로 적용한다. 한도를 넘기는 요청이 429 를 받아야 한다.
/root/gw/tpm.txt에limit=2000 consumed=<n> rejected=<n>을 적고 rejected 는 1 이상이어야 한다. - 모든 429 응답에
Retry-After헤더가 1 이상 60 이하의 정수로 붙어야 한다./root/gw/retry.txt에retry_after=<정수>를 적는다. /opt/fixtures/llms/pricing.json의 모델별 단가로 비용을 계산해/root/gw/cost.csv에tenant,model,prompt_tokens,completion_tokens,cost_usd헤더와 2행 이상을 적는다.- 테넌트
t1의 일간 예산을 0.01 달러로 두고 초과 시 402 를 준다. 예약은max_tokens기준으로 미리 잡고 완료 후 실제 사용량으로 정산한다./root/gw/budget.txt에budget_usd=0.01 spent_usd=<수> blocked=true를 적는다.
참고
- 127.0.0.1:8170 의 백엔드는 이 파드에 미리 떠 있지 않습니다.
POST /generate에{"prompt":..., "max_tokens":n}을 받아{"text":..., "usage":{"prompt_tokens":n,"completion_tokens":n}}를 돌려주면 되므로, 앞선 토큰 스트리밍 실습에서 만든 서버를 그대로 쓰거나 같은 계약의 최소 서버를 직접 띄우고 시작하세요. - 토큰 버킷: 용량과 리필 속도 두 값으로 정의합니다. 용량이 버스트 최대치, 리필이 지속 가능한 평균입니다.
- 스트리밍에서 중간에 끊긴 요청도 그때까지의 출력 토큰을 계상해야 합니다.
- 흔한 실수 1: 예산 검사를 완료 후에만 하는 것 — 동시 요청이 모두 통과한 뒤 함께 초과합니다.
- 흔한 실수 2: 입력과 출력 토큰을 합쳐서 세는 것 — 단가가 다르므로 비용이 틀립니다.
게이트웨이로 백엔드 프록시하기
/root/gw/gateway.py 를 127.0.0.1:8171 에 띄우고 POST /v1/generate 를 8170 백엔드로 전달한다. 응답이 백엔드와 동일해야 한다. 테넌트 t1 의 API 키를 /root/gw/key.txt 에, t2 의 키를 /root/gw/key2.txt 에 각각 한 줄로 저장해 둔다 — 이후 스텝의 채점이 이 두 파일을 쓴다.
먼저 그대로 통과시키는 프록시를 만들고 기능을 하나씩 얹습니다.
API 키로 테넌트 식별하기
X-API-Key 헤더로 테넌트를 식별한다. 키-테넌트 매핑은 /opt/fixtures/llms/apikeys.json 에 있다. 키가 없거나 모르는 키면 401 이다.
키가 없거나 모르는 키면 401 입니다. 키를 테넌트에 매핑해 두세요.
분당 요청 제한 걸기
테넌트별 분당 요청 제한 10 을 적용한다. 11번째 요청이 429 여야 한다. /root/gw/rpm.txt 에 allowed=10 rejected=1 을 적는다.
고정 윈도우가 가장 단순합니다. 초과하면 어떤 상태 코드인지 생각해 보세요.
입력·출력 토큰 회계하기
GET /v1/usage 가 {"tenant":"...","prompt_tokens":<n>,"completion_tokens":<n>,"requests":<n>} 를 준다. 두 토큰 값이 모두 0보다 커야 한다.
둘을 따로 세야 합니다. 단가가 다르기 때문입니다.
분당 토큰 제한 걸기
분당 토큰 제한 2000 을 토큰 버킷으로 적용한다. 한도를 넘기는 요청이 429 를 받아야 한다. /root/gw/tpm.txt 에 limit=2000 consumed=<n> rejected=<n> 을 적고 rejected 는 1 이상이어야 한다.
토큰 버킷이 잘 맞습니다. 조용하던 테넌트의 버스트를 허용하면서 장기 평균을 지킵니다.
429 에 재시도 시점 담기
모든 429 응답에 Retry-After 헤더가 1 이상 60 이하의 정수로 붙어야 한다. /root/gw/retry.txt 에 retry_after=<정수> 를 적는다.
남은 시간을 계산해 정수 초로 줍니다. 이게 없으면 클라이언트들이 파도를 만듭니다.
모델별 단가로 비용 계산하기
/opt/fixtures/llms/pricing.json 의 모델별 단가로 비용을 계산해 /root/gw/cost.csv 에 tenant,model,prompt_tokens,completion_tokens,cost_usd 헤더와 2행 이상을 적는다.
출력이 입력보다 비싼 것이 보통입니다. 단가표는 픽스처에 있습니다.
예산 초과 차단과 사용량 보고하기
테넌트 t1 의 일간 예산을 0.01 달러로 두고 초과 시 402 를 준다. 예약은 max_tokens 기준으로 미리 잡고 완료 후 실제 사용량으로 정산한다. /root/gw/budget.txt 에 budget_usd=0.01 spent_usd=<수> blocked=true 를 적는다.
차단 대신 더 싼 모델로 강등하는 선택지도 있습니다. 여기서는 차단으로 구현합니다.