청구서보다 먼저 앱이 지키기 시작했다 — AIO Helper의 AI 비용 상한 사용 방지 기능
요약
AIO Helper는 SEO 분석 SaaS의 운영 비용 절감을 위해 AI API 사용 상한 기능을 도입했습니다. 이 메커니즘은 예상 비용이 잔액을 초과하는 요청을 AI 호출 전에 차단하여 불필요한 지출을 방지합니다. 또한, 잔액 10% 미만 시 경고 기능도 추가하여 비용 관리를 강화했습니다.
핵심 포인트
- AI API 사용 상한 기능을 도입해 예상 비용 초과 요청을 사전에 차단함.
- 사용량 기록 및 상한 확인 로직을 통합 처리하여 효율성을 높임.
- 잔액이 10% 미만일 때 경고 알림 기능을 추가하여 사용자에게 비용 인지도를 높임.
우에하라 마사요시(上原正吉, EarthLink Network Co., Ltd.)입니다. Claude Code를 개발의 주체로 삼아 20개가 넘는 제품을 혼자 동시에 개발하고 운영하고 있습니다. 이것은 현장에서 측정된 기록입니다.
2026년 4월 16일, SEO 분석 SaaS인 'AIO Helper'의 API 서버에 AI API의 월간 사용액이 사이트별 상한(기본 $50)을 초과하기 전에 처리를 중단하는 메커니즘을 도입했습니다. 예상 비용이 잔액을 초과하는 요청은 AI를 호출하기 전에 막습니다. 사용량 기록은 AI 호출이 성공한 후에 이루어집니다. 각 용어는 본문 첫 등장 시 설명합니다.
- 사용량 기록과 상한 확인을 하나의
INSERT문으로 통합하여, 두 개의 처리로 나누지 않았습니다. 다만, 동시 실행 시의 엄격한 상한 보장은 실제 Postgres 환경에서 미확인입니다. 또한, 예상 비용을 초과한 실비나 동시 실행에 의한 초과는 사후 기록으로는 막을 수 없습니다. - 4월 23일에는 잔액이 10% 미만이 되었을 때 경고하는 기능을 추가했습니다(구현 후 240건의 테스트가 통과되었으며, 당시 실행 기록도 있습니다). - 확인 절차를 거친 것은 문장을 생성하는 AI를 호출하는 세 가지 기능(페이지 목표 자동 생성, 제목 및 설명문 제안, 일괄 제안)입니다. 일괄 제안에서는 페이지별로 호출 직전에 재확인합니다. 검색용 벡터를 만드는 AI 호출은 이 시점에서는 대상이 아니었습니다.
대상은 AIO Helper라는 자사의 SaaS(Software as a Service, 인터넷을 통해 이용하는 소프트웨어)입니다. SEO(Search Engine Optimization, 검색 결과에서 찾기 쉽게 하는 최적화) 운영을 지원합니다. Google Search Console 등으로부터 사이트 데이터를 가져와 페이지별 개선안을 제시합니다. 구성은 사용자가 조작하는 관리 화면과 처리를 담당하는 API 서버(seo-api)의 두 가지입니다. 제품 전체 구조는 AIO Helper 소개 기사에 정리되어 있습니다.
AI(Artificial Intelligence, 학습된 모델로 추론이나 생성을 수행하는 기술)를 사용하는 것은 API 서버 측에서 이루어지며, 2026년 4월 기준으로 문장을 생성하는 AI를 호출하는 기능은 다음 세 가지였습니다. 모두 생성형 AI의 API(Application Programming Interface, 시스템 간에 기능을 호출하는 창구)를 호출합니다.
- 페이지 목표 자동 생성(
POST /v1/page-goals/auto-generate)
). 페이지의 제목이나 본문으로부터 목표 키워드, 페이지 목적, 예상 독자를 초안으로 만듭니다. - 제목 및 설명문 제안(POST /v1/suggest)
). 1페이지 분량의 개선안을 제시합니다. - 일괄 제안(POST /v1/suggest/batch)
). 클릭 수가 많은 페이지부터 순서대로 모아서 제안을 합니다.
이 외에도 관련 문서를 찾기 위한 검색용 벡터(embedding, 문장을 수치 배열로 변환한 것)를 만드는 호출이 있습니다. 제안의 두 기능이 내부적으로 사용하는 것과, 벡터를 재구축하는 API(POST /v1/embeddings/rebuild)
). 4월의 메커니즘은 이들을 대상으로 하지 않았습니다(후술할 '확인되지 않은 점'에서 설명합니다).
사용자에게는 분석이 빨리 끝나고 수작업으로 조사하는 시간이 줄어드는 것이 가치입니다.
하지만 AI API는 호출할 때마다 비용이 증가합니다. 사용자가 늘어날 때뿐만 아니라, 버그로 인해 같은 처리를 반복했을 때도 청구액은 늘어납니다. 관리 화면에 사용량을 표시하는 것만으로는, 인지한 시점에는 이미 비용이 발생하고 있습니다.
그래서 다음 두 가지를 만들었습니다.
- 사용하기 전에 멈추는 메커니즘. 사이트별로 월간 상한을 설정하여, 잔액이 부족한 요청은 AI API로 보내지 않습니다. - 사용한 후에 추적하는 장부. 사이트, 모델, 호출 대상, 토큰 수, 비용을 문장 생성 호출 1회마다 기록합니다.
사이트별 월간 상한은 4월 16일 시점에 API(PUT /v1/sites/:siteId/budget)로 변경했으며, 4월 23일에는 관리 화면에 'AI Budget' 탭도 추가했습니다. 상한과 사용 기록은 API 서버의 데이터베이스에 있으며, 문장을 생성하는 AI를 호출하는 세 가지 기능은 AI를 호출하기 직전에 동일한 테이블을 조회합니다.
목적은 단순히 월 $50에서 막는 것이 아닙니다. 어떤 기능이 얼마나 비용을 사용했는지 추적할 수 있도록 하고, 사용 방식을 바꾼 후에 실제로 줄었는지 확인하는 것입니다. '막는다'와 '개선한다'를 동일한 기록에서 할 수 있는 상태를 목표로 했습니다.
처리 순서는 다음과 같습니다.
- API 서버가 문장을 생성하는 AI를 호출하는 세 가지 기능 중 하나의 요청을 받습니다.
- 사용 모델과 입출력 예상치를 바탕으로 비용을 계산합니다.
- 당월 이용액과 예상 비용을 데이터베이스 내에서 비교합니다.
- 잔액이 부족하면 외부 AI API는 호출하지 않습니다. 페이지 목표 자동 생성 및 1페이지 제안은 HTTP(Hypertext Transfer Protocol, 웹 통신 규약) 402를 반환합니다. 일괄 제안은 잔액이 부족한 페이지를 건너뛰고, 건너뛴 페이지 수를 기록하며 HTTP 200으로 결과를 반환합니다.
- 잔액이 충분하면 AI API를 호출하고, 성공 후에 실제 사용량을 이용 대장(台帳)에 기록합니다. 기록할 때도 동일한 SQL문 안에서 상한을 확인합니다.
이 순서에서 중요한 것은 중지 판단을 'AI API가 오류를 반환한 후의 사후 처리'로 하지 않는 것입니다. 외부로 보낸 후에는 비용을 취소할 수 없습니다. 앱의 입구에서 막음으로써 비로소 상한이 제어로서 기능하게 합니다.
반면, 이 기록은 청구서(請求書)를 대신하는 것은 아닙니다. 앱은 자신이 파악하고 있는 토큰 수와 가격표를 기반으로 견적을 냅니다. 프로바이더 측의 최종 청구액과 완전히 일치한다고 할 수 없습니다. 앱 내부 대장은 빨리 막기 위한 숫자이고, 청구서는 최종적으로 지불할 숫자라는 역할을 분리합니다.
발단은 테스트 감사였습니다. AIO Helper 관리 화면에는 '금월 AI 이용액'을 표시하는 패널이 있었지만, 상한을 강제하는 테스트는 0건이었습니다. 이용액을 계산할 수 있어도, 상한을 초과하기 전에 막지 못하면 종량 과금(従量課金) 생성 AI 모델을 계속 호출하게 됩니다.
그래서 처음에 한 것은 요금 계산과 데이터 모델의 분리였습니다. 가격표는 외부 API에 문의하지 않고 코드에 정적으로 보유하기로 결정했습니다. 이유는 세 가지가 있습니다.
- AI API 응답에 비용이 포함되어 있지 않습니다. AIO Helper는 OpenAI의 API를 직접 호출하지만, 응답에는 토큰 수만 포함됩니다.
- 가격은 그렇게 자주 변하지 않습니다. 정적인 가격표도 유지보수가 현실적입니다.
- 과금 판단을 외부 호출에 의존하고 싶지 않습니다. 비용을 알기 위한 API 호출이 실패하면 과금 판단을 할 수 없게 된다는 순환 고리를 피합니다.
// ai-cost.ts — 미지의 모델은 일부러 높게 견적함
const FALLBACK_PRICE = { input: 0.02, output: 0.08 };
// calcCost()는 6자리로 반올림하고, 음수 값은 0으로 클램프하며, 절대 throw하지 않습니다.
...
FALLBACK_PRICE를 의도적으로 높게($0.02/$0.08 per 1K) 설정한 것이 핵심입니다. 새로운 모델을 추가했는데 가격표 등록을 잊으면 비용이 0으로 간주되어 상한이 무너집니다. 그러므로 미지의 모델은 '높다'고 취급하는 것이 안전합니다.
과금을 막는 본체는 budget.ts에 두었습니다. 테이블은 두 개입니다.
-- seo_site_budgets: 사이트별 월간 상한 (기본 $50)
-- monthly_usd_cap NUMERIC(10,2)
-- seo_ai_usage: 1 호출당 기록 전용 대장
...
금액을 FLOAT가 아닌 NUMERIC로 한 이유는 수백만 번 쌓였을 때의 반올림 오차를 피하기 위함입니다. 가장 고민했던 부분은 동시에 두 개의 요청이 상한에 아슬아슬하게 도착했을 경우의 제어였습니다. SELECT FOR UPDATE와 트랜잭션을 사용하는 안건 대신, 상한 확인을 포함하는 단일의 INSERT 문을 선택했습니다.
INSERT INTO seo_ai_usage (site_id, model, endpoint, input_tokens, output_tokens, cost_usd)
SELECT $1::text, $2::text, $3::text, $4::int, $5::int, $6::numeric
WHERE (
...
$7은 사이트에 예산 행이 없을 때의 기본 상한($50)입니다. 사전 잔액 확인은 checkBudget()가 수행하며, 부족하면 상위 AI 호출을 부르지 않고 막습니다(1페이지 단위의 2 기능은 HTTP 402 Payment Required를 반환합니다). AI 호출 성공 후의 recordUsage()는 이 INSERT가 0 행을 반환하면 { ok: false }
를 반환합니다. 이 시점에서는 이미 비용이 발생했기 때문에, 요청을 실패로 처리하지 않고 경고 로그만 출력합니다. 원장(ledger)에는 기록되지 않으므로, 해당 호출의 비용은 전액 원장에서 차감됩니다. 원장의 사용액이 증가하지 않기 때문에, 이후 상한 확인에도 이 비용은 반영되지 않습니다. cap = 0
는 '1엔도 쓰지 못하게 하는' 정지 설정으로 기능하도록 했습니다.
상한 체크를 별도의 SELECT 문으로 처리하지 않고, INSERT 문의 WHERE 절에 상관 서브쿼리(correlated subquery)로 포함시켰습니다. 이를 통해 하나의 문 안에서는 집계와 쓰기 사이에 애플리케이션 측의 별도 처리가 들어가지 않습니다.
다만, 독립적으로 확인한 것은 이 SQL 구조까지입니다. 동시에 시작된 두 개의 문이 반드시 선행 행을 집계에 포함하는지는 실제 Postgres를 사용한 경쟁 테스트(concurrent test)에서 확인하지 못했습니다. 엄격한 상한 보장이 필요하다면, 예산 행 잠금(lock), 직렬화 가능 격리 수준(serializable isolation level), 또는 자문자적 잠금(advisory lock) 등을 통해 사이트 단위의 처리 순서를 명시해야 합니다.
이 설계에서는 테스트 환경에도 문제가 있었습니다. 테스트는 pg-mem(메모리 내에서 작동하는 Postgres)로 진행했지만, date_trunc('month', NOW())가 미구현이었기 때문에 당시 기록으로는 18건 중 12건이 500으로 실패했습니다.
12 out of 18 tests in integration-ai-cost-cap.test.ts failed
Only the 3 pure calcCost unit tests pass
기록 시점에서는 데이터베이스에 접근하지 않는 calcCost의 단위 테스트만 통과했습니다. 500과는 다른 요인으로 실패한 테스트도 있었으며, 기록에 남아 있는 것은 PUT budget가 입력 검증 문제로 400을 반환한 건입니다.
당시 테스트 기록에서는 db.public.registerFunction()으로 date_trunc를 등록하면 실패가 5건으로 줄었습니다. 나머지는 INSERT...SELECT 안에서 값의 타입을 해결할 수 없는 문제입니다. node-postgres는 타입이 지정되지 않은 값을 문자열로 보내기 때문에, $1::text처럼 명시적인 타입 변환을 추가하여 해결했습니다.
당시 테스트 기록에서는 의도적으로 date_trunc의 WHERE 절을 망가뜨리는 실험도 했습니다. 18건 중 17건은 월별 추출 조건이 망가져도 통과했고, '지난달분은 세지 않는다'는 테스트만 실패했습니다. 전월 행을 60일 전 날짜로 추가하여 집계되지 않는 것을 확인하는 테스트입니다. 월별 필터를 잘못 삭제했을 경우, 이 한 건밖에 감지할 수 없다는 것을 알게 되었습니다. 중요한 조건을 검증하는 테스트가 한 곳에 집중되어 있다는 것을 깨달았습니다.
상한으로만 막는 방식으로는 사용하는 쪽에서 갑자기 402를 반환받고 왜 막혔는지 알기 어렵습니다. 그래서 잔액이 10%를 밑돌면 경고 플래그를 거는 soft-warning을 추가했습니다.
export const WARNING_REMAINING_PCT = 0.1;
function isNearLimit(cap: number, remaining: number): boolean {
if (cap <= 0) return false; // 키르 스위치는 '경고'가 아니다
...
cap <= 0을 명시적으로 제외한 것은, '사용 중지 상태'와 '상한이 가까운 상태'를 별도의 알림으로 처리하기 위함입니다. 당시 실행 기록에서는 실패하는 테스트 4건을 먼저 작성하고, 경고 조건 구현 후에 240건의 테스트가 통과했습니다. 변경은 feat: AI budget soft-warning flag at <10% remaining로 커밋했습니다.
경고와 중지를 분리함으로써 화면이나 알림 문구도 나눌 수 있습니다. 잔액이 적다면, 사용자는 처리량을 줄이거나 관리자에게 상한 변경을 요청할 수 있습니다. 상한을 0으로 한 경우는 관리자가 의도적으로 막은 상태입니다. 같은 노란색 경고로 보여주면, 사용자는 '기다리면 돌아올 것'이라고 오해합니다.
상한에 도달했다는 사실만 통지해서는 다음에 무엇을 고쳐야 할지 알 수 없습니다. 운영에서 필요한 것은 최소 다음 5가지입니다.
월별로 얼마나 사용했는지. 이는 월간 상한액과 비교하기 위한 기본 수치입니다. - 오늘 얼마나 늘었는지. 급격한 증가는 사용자 증가뿐만 아니라 재시도 루프나 버그의 가능성도 있습니다. - 어떤 기능이 사용되었는지. 호출 대상을 기록해 두지 않으면, 감축 후보를 선택할 수 없습니다. - 1회당 비용과 결과물 1건당 단가. 호출 횟수가 늘어나더라도 제안 건수가 같은 비율로 증가한다면 의미가 다릅니다. - 변경 전후로 어떻게 바뀌었는지. 모델이나 입력 데이터를 바꾼 날을 기록하고, 그 이후의 단가와 비교합니다.
4월의 seo_ai_usage는 이 중 상위 3가지(사이트/모델/호출 대상/토큰 수/비용/날짜)를 열로 가지고 있습니다. 일별 증가 추이나 변경 전후를 비교하는 화면은 아직 없습니다. 그럼에도 불구하고 1회 단위 기록이 남아 있다면, 나중에 집계하여 감축을 '기분'이 아닌 숫자로 비교할 수 있습니다.
본 기사를 '동시 실행에서도 절대 예산을 초과하지 않는 완성판'이라고는 할 수 없습니다. 이유는 네 가지가 있습니다.
- 실제 Postgres에서 경쟁 테스트를 완료하지 않았습니다. 단일 SQL 문으로 통합한 것과 사이트 단위의 완벽한 순서 제어는 다릅니다. - 기록에 남지 않는 호출이 있습니다. 기록은 AI 호출이 성공한 경우에만 하며, 실패한 호출은 기록하지 않도록 설계되었습니다. 다만, 페이지 목표 자동 생성 시에는 AI가 응답한 후 응답을 읽는 과정에서 실패하면 오류를 반환하고 기록에 남기지 않습니다. 비용은 발생했지만, 기록에는 남아있지 않습니다. - 검색용 벡터 생성은 상한 외였습니다. 제안의 두 기능이 내부적으로 만드는 embedding과 벡터를 재작성하는 API는 상한 확인이나 기록을 하지 않았습니다. 2026년 9월에 재작성 API에는 상한 확인과 기록을, 제안 내부의 embedding에는 기록을 추가했습니다. - 예산용 데이터베이스가 사용 불가능할 때의 동작도 미확인입니다. 처리를 계속하면 비용 상한을 지킬 수 없고, 중지하면 사용자 작업을 멈추게 합니다. 어느 것을 우선할지 결정하고, 장애 시 테스트와 알림을 준비해야 합니다.
따라서 이 구현은 종착점이 아니라, 비용을 제어 대상으로 삼기 시작한 첫 단계입니다. 후속 기사에서 청구 데이터와의 대조, 고정비 특정, 변경 후 재측정을 진행할 것입니다. 이 순서를 건너뛰면 감축 효과를 숫자로 설명할 수 없습니다.
이 사용량 제한 통제와 기록은 5월 이후의 비용 개선에 사용될 측정/상한/기록의 원형이 되었습니다. 다음 달에는 Cost Explorer가 $0을 반환하는 문제를 조사하고, 그 후 '측정 → 수정 → 재측정'을 기록으로 남기는 운영 방식으로 진행할 것입니다. 그 생각은 4월에 이미 있었습니다.
한편, 이 시스템이 다루는 것은 AIO Helper의 AI API 사용액입니다. Elastic Container Service의 상시 가동이나 NAT(Network Address Translation, 내부 네트워크에서 외부로 통신을 중계하는 방식) Gateway의 고정비 등 클라우드 전체의 청구는 막을 수 없습니다. 애플리케이션 외부에서 발생하는 비용에는 Cost Explorer나 예산 모니터링을 별도로 준비해야 합니다.
- 미지(未知)는 '높게' 추정합니다. 가격표에 없는 모델을 0원으로 취급하면 상한이 새어 나갑니다. 폴백 가격은 의도적으로 높게 설정합니다. - 집계와 쓰기를 한 문장으로 통합하더라도, 동시 실행 시의 보장은 별도로 검증할 것입니다. 엄격한 상한이 필요하다면 실제 Postgres를 이용한 경쟁 테스트와 처리 순서를 보장하는 메커니즘이 필요합니다. - 중지 설정과 잔액 경고는 분리합니다.
cap = 0(정지)을 '곧' 경고에 섞지 않습니다. - 청구서가 나오기 전부터 1회 단위 비용을 남깁니다. 호출 대상과 토큰 수를 첨부하여 앱 측에서 기록해 두면, 나중의 감축 전략을 숫자로 세울 수 있습니다.
이 클라우드 비용과의 싸움에 대한 글은 귀속(帰属)・실측・구조 가드・정점 관측이라는 방법론을 순차적인 시리즈로 공개할 예정입니다.
관심 있는 분들은 '좋아요'와 기사 구독을 부탁드립니다.
이 방법론을 기반으로 한 비용 관리 서비스 Costwary를 https://costwary.com에서 제공하고 있습니다.
그 외의 자사 제품은 https://www.eln.ne.jp/products에 모아두었습니다.
우에하라 마사요시(上原正吉)입니다. EarthLink Network Co., Ltd.에서 AI 개발을 하고 있습니다. 2025년부터 Claude Code를 개발의 주체로 삼아, 현재 20개가 넘는 제품을 혼자 동시에 개발하고 운영하고 있습니다. 이 연재에서는 그 현장에서 실제로 일어난 일(잘된 일도, 실패한 것도)을 숫자를 함께 쓰면서 기록해 가겠습니다.
또한, AI를 활용하여 업무나 개발 방식을 재편하려는 회사나 팀을 대상으로 AI 활용 컨설팅도 받고 있습니다. 상담은 www.eln.ne.jp에서 부탁드립니다.
EarthLink Network는 회사의 모든 업무를 AI로 돌리기 위해 필요한 것을 자체적으로 만들고 있습니다. 현재 제작 중인 제품 목록과 개요는 여기에서 모아볼 수 있습니다.
회사와 각 제품의 자세한 내용은 공식 웹사이트 www.eln.ne.jp를 참고해 주십시오.
🔗 이 글은 note에 게시된 기사의 재게시입니다. 정식 버전(canonical)은 여기입니다: https://note.com/chooser/n/nbc8f02432bcc
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기