크레딧 시스템을 구축할 때 아무도 고려하지 않는 부분들
요약
제품에 크레딧/토큰 기반 청구 시스템을 구축할 때 간과하기 쉬운 8가지 핵심 기술적 고려 사항들을 다룹니다. 단순한 컬럼 추가를 넘어, 잔액의 복잡성(여러 버킷), 고객별 기준일 관리, 플랜 변경 처리, 동시 요청 충돌 방지 등 고도화된 시스템 설계가 필요합니다.
핵심 포인트
- 잔액은 여러 규칙을 가진 독립적인 버킷들의 합계로 관리해야 합니다.
- 고객의 갱신 주기는 '월간'이 아닌 개별 고객의 기준일(Anchor Date)에 맞춰야 합니다.
- 플랜 변경 시점과 사용량 기록(History) 관리가 필수적입니다.
- 동시 요청 처리 시, 크레딧 차감 및 정산 로직을 원자성(Atomic)으로 보장해야 합니다.
- 속도 제한(Rate Limits)은 총량 외에 시간/분 단위로 별도 적용되어야 합니다.
만약 귀하의 제품이 크레딧, 토큰, 분(minutes) 또는 API 호출을 판매한다면, 팀원 중 누군가가 아마도 "사용자 테이블에 credits 컬럼을 추가하면 될 거야"라고 말했을 것입니다. 이는 합리적인 첫 단계입니다. 하지만 동시에 대부분의 팀들이 소유할 계획이 없었던 청구 시스템을 구축하기 시작하는 지점이기도 합니다.
실제 고객이 유입되면 이 컬럼은 어떻게 변모하는지 살펴보겠습니다.
1. 잔액은 하나의 숫자가 아니다
월간 플랜의 고객은 초기 할당량(allowance)을 가지고 있으며, 이는 리셋됩니다. 그런 다음 상위 충전 팩(top-up pack)을 구매하는데, 이것은 리셋되어서는 안 됩니다. 게다가 30일 후에 만료되는 가입 보너스를 제공할 수도 있습니다. 이제 "고객이 가진 크레딧은 얼마인가?"라는 질문은 자체 규칙을 가진 여러 개의 버킷(bucket)에 대한 합계가 되며, 어떤 버킷부터 소진할지 결정해야 합니다. 보통은 가장 빨리 만료되는 버킷입니다.
2. "월간"이란 각 고객의 월을 의미한다
할당량은 매월 1일이 아니라 고객의 청구일에 갱신됩니다. 17일에 가입한 고객은 17일에 갱신합니다. 9일에 업그레이드한 고객은 이제부터 9일에 갱신하거나, 할당 비율(prorate)에 따라 17일을 유지할 수 있습니다. 따라서 귀하의 갱신 작업은 모든 고객의 기준 날짜(anchor date)를 알아야 합니다.
3. 기간 중 플랜 변경
오늘 업그레이드했을 때 할당량은 어떻게 되나요? 전체 새 금액을 지금 받게 하나요, 비율에 맞춰 계산된 부분(prorated share)을 받게 하나요, 아니면 다음 갱신 시점에 새 금액을 받게 하나요? 다운그레이드는 더 심각합니다. 만약 이미 더 작은 플랜이 포함하는 것보다 많이 사용했다면 어떨까요? 어떤 것을 선택하든, 언제 어떤 플랜이 적용되었는지에 대한 기록(history)이 필요하며, 그렇지 않으면 나중에 청구서를 설명할 수 없습니다.
4. 두 개의 요청, 하나의 마지막 크레딧
귀하의 앱은 잔액을 확인하고, 작업을 실행한 다음, 차감합니다. 병렬 요청(parallel requests)의 경우, 두 작업이 모두 "남은 크레딧 10개"를 볼 수 있고, 둘 다 실행되며, 둘 다 차감합니다. 결과적으로 고객은 −10에 있게 됩니다. 저렴한 작업이라면 괜찮을 수 있습니다. 하지만 비디오 렌더링이나 이미지 생성 배치(batch)의 경우, 이는 실제 돈입니다. 이를 수정하려면 작업이 시작되기 전에 용량을 확보하고 나중에 정산해야 합니다.
5. 실패한 작업
작업 전에 크레딧을 보유하거나 공제했는데 작업이 실패하면 고객에게 돌려줘야 합니다. 만약 사용자의 워커가 작업 도중에 충돌하면, 아무도 환불 처리를 해주지 않습니다. 따라서 보류된(holds) 크레딧은 만료 기한이 필요하며, 이를 정리해 줄 무언가가 있어야 합니다.
6. 버스트 (Bursts)
월별 할당량만으로는 한 고객의 통제 불가능한 스크립트가 10분 동안 월 전체를 소진하는 것을 막을 수 없습니다. 따라서 총량 외에도 고객별 속도 제한(rate limits)이 필요하며, 종종 분당 및 시간당 등 여러 기간에 걸쳐 적용해야 합니다.
7. 사용량이 부족해지기 전에 고객에게 알림
고객들은 작업 실패 시 100%가 되었다는 사실을 아는 것보다 80%일 때 충전하는 것을 더 선호합니다. 이는 기간별로 고객별 임계값(thresholds)을 추적하고, 해당 라인을 넘은 후 매 요청마다 알리는 것이 아니라 한 번만 통지해야 함을 의미합니다.
8. 청구서 작성을 위한 사용량 추적
기간이 끝날 때, 재무팀은 정확한 숫자를 원합니다: 이 고객이 얼마나 사용했는지, 얼마가 포함되었고, 얼마가 초과 비용인지 말입니다. 그 숫자는 기간이 마감되면 변하지 않고 유지되어야 합니다.
종합하면
이 중 어느 것도 단독으로는 어렵지 않습니다. 하지만 이것들이 합쳐지면, 가장 비싼 운영 작업 앞에 위치하는 작고 상태를 가지며(stateful), 동시성에 민감한(concurrency-sensitive) 시스템을 만들게 되며, 가격 정책이 바뀔 때마다 변경됩니다. 바로 이 부분이 원래 추정치에는 거의 포함되지 않는 부분입니다.
요약
이것이 우리가 Meterbase를 구축하는 문제입니다. 메터(meters), 플랜(plans), 할당량(allowances)을 한 번 설정하면, 코드는 작업 전과 후에 각각 한 번의 호출만 합니다:
import { Meterbase } from "mbase-sdk"
const meterbase = new Meterbase({ apiKey: process.env.METERBASE_API_KEY! })
...
할당량, 충전(top-ups), 플랜 변경, 보류(holds), 속도 제한 및 임계값 웹훅은 이 두 호출 뒤에 위치합니다. Meterbase는 청구 시스템이 아닙니다: Stripe나 Paddle가 여전히 돈을 수취하며, 사용자는 기간별로 각 고객의 사용량을 읽어와서 청구해야 합니다.
만약 지금 이것을 구축하고 있거나, 이미 구축했지만 유지 관리에 지쳤다면, 어떻게 처리했는지 듣고 싶습니다: meterbase.tech.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기