본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 08. 00:42

개인 개발로 AI 기능을 넣었더니 파산이 두려워져서, API를 '호출하지 않는' 비용 방어책을 3단계로 구현한 이야기

요약

개인 개발자가 AI 기능을 탑재할 때 발생하는 API 비용 폭증을 막기 위한 3단계 비용 방어 전략을 소개합니다. 캐싱, 로컬 DB 우선 매칭, 이용 상한 설정(Kill Switch)을 통해 효율적이고 안전한 AI 서비스 운영 방법을 다룹니다.

핵심 포인트

  • LRU 캐시를 활용한 동일 입력 문장 재사용
  • 로컬 식품 DB를 통한 API 호출 전 사전 검증
  • 식품명 손상을 방지하는 정교한 텍스트 분할 로직
  • Redis 기반의 사용자별 일일 이용 상한 설정

서론

학생 엔지니어가 Claude Code를 사용하여 근력 운동 관리 도구를 개인 개발하고 있습니다.

증량/감량을 칼로리 수지로 숫자로 관리하는 Web App이지만, 핵심 기능 중 하나가 AI를 이용한 식사 입력입니다.

"닭가슴살 200g과 흰쌀밥 한 공기와 계란 2개"

이렇게 적기만 하면, AI가 품목별로 분해하여 칼로리·PFC(단백질/지방/탄수화물)를 자동으로 기록해 줍니다.

하지만 AI를 그대로 탑재하면 무서운 일이 생긴다는 것을 깨달았습니다. LLM은 종량제 과금입니다. 모든 사용자의 API 비용을 개인 개발자인 제가 부담해야 합니다. 악의적인 사용자가 연타한다면 하룻밤 사이에 파산할 수도 있습니다.

그래서 "AI에 어떻게 던질까"보다 **"어떻게 던지지 않을까"**에 주안점을 두고, 비용 방어를 3단계로 구현한 이야기를 쓰겠습니다.

과제: LLM 비용은 방치하면 천정부지로 치솟는다

AI 식사 입력의 단순한 구현은 "입력 문장을 그대로 LLM에 던져서 구조화하여 반환한다"뿐입니다. 하지만 이렇게 하면:

  • 동일한 사용자가 동일한 식사를 반복 입력 → 매번 과금
  • "닭가슴살" 같은 단순한 식품이라도 매번 LLM을 호출 → 낭비
  • 상한선이 없으므로, 이론상 얼마든지 과금이 불어남

개인 개발로 "무료로 공개"하면서 AI를 탑재하려면, 이 부분을 설계로 어떻게든 해결해야만 합니다.

해결: 3단계 비용 방어

① 입력 문장 일치 캐시

먼저, 동일한 입력 문장은 두 번째부터 무료로 처리합니다 (lru-cache 사용). "평소 먹는 아침 식사"를 매일 아침 입력하는 사람에게는 이것만으로도 상당히 효과적입니다.

② 로컬 식품 DB로 "먼저 해결"하기 (←이것이 핵심)

AI에 던지기 전에, 수중에 있는 식품 DB(문부과학성 식품 성분표 + 커뮤니티 승인 식품)로 해결할 수 없는지 시도합니다. 해결할 수 있다면 API를 전혀 호출하지 않습니다.

// 의사 코드: API에 던지기 전에 로컬에서 해결하려고 시도
async function parseMeal(text: string) {
  const local = resolveLocally(text); // 식품 DB와 대조
  ...
}

포인트는 오번역(잘못된 변환)을 절대 만들지 않는 것입니다. 로컬 해결은 빠르고 무료이지만, 잘못된 데이터를 DB에 기록하면 본말전도입니다. 따라서 대조는 완전 일치 또는 전방 일치로 유니크하게 결정될 때만 채택하고, 조금이라도 모호하면 포기하고 API로 넘깁니다 (보수적인 Fallback).

「~와/과」 문제: 구분자가 식품명에 포함됨

여러 품목("닭가슴살 200g 흰쌀밥 한 공기 계란 2개")을 나누고 싶지만, "과/와"는 식품명에도 포함됩니다 (부 / 마토 / 고구). 단순히 구분하면 이름이 망가집니다.

그래서 2단계로 구성했습니다:

  • 우선 기호 구분("、", "/" 등)만으로 대조
  • 그것으로 해결되지 않으면, 기호 + "과/와"로 분할하여 재시도

①에서 해결되면 "과/와"가 포함된 식품명을 망가뜨리지 않고, 안 될 때만 ②에서 느슨하게 나눕니다. 나누어진 파편은 완전 일치하지 않으므로, 안전하게 API로 넘어갑니다 (잘못된 데이터 없음).

③ 하루 이용 상한 (물리적 킬 스위치)

최후의 보루로서, 전체 및 사용자별 하루 이용 횟수를 Upstash Redis로 카운트하여 상한을 초과하면 차단합니다. 스토어 장애 시에는 "차단하지 않는" Fail-open 방식으로 설정하여, 일반적인 이용을 방해하지 않도록 했습니다.

// IP는 해시화하여 저장 (생 IP는 남기지 않음)
const key = `ai:${jstDateKey()}:${ipHash(req)}`;
const { count } = await incr(key);
...

LLM 호출 본체: Claude Haiku × tool use

실제로 API를 호출하는 부분은 저렴한 모델(Claude Haiku)에 tool use로 스키마를 고정하여 구조화하도록 하고 있습니다. 각 품목을 { name, amount, unit, kcal, p, f, c }로 반환하게 하고, name에는 양을 포함하지 않습니다 (양은 amount + unit으로 분리). 이렇게 하면 후속 UI에서 양을 변경할 때 영양 성분을 선형적으로 재계산할 수 있고, 등록 시에도 단위당(per-unit)으로 정규화하여 재사용할 수 있습니다.

효과: 사용될수록 저렴해진다

①~③에 더해, AI가 분석한 식품은 식품 DB에 자동으로 등록됩니다. 즉, 한 번 AI가 해결한 식품은 다음부터 ②의 로컬 해결을 통해 무료가 됩니다. 프로덕트가 사용될수록 평균 API 소비가 낮아지는 구조가 되었습니다.

부록: 커뮤니티 DB의 "승인"도 자동화했다

식품은 모두가 함께 키워나가는 공유 DB(게시 → 소유자 승인 → 모두에게 공개) 구조로 운영하고 있습니다. 하지만 승인할 때마다 DB 관리 화면을 여는 것은 번거로운 일입니다. 또한 알림이 없으면 게시물이 쌓여만 가고 아무에게도 공개되지 않는 문제가 발생했습니다.

그래서 운영도 자동화했습니다:

사용자가 식품을 공유
→ Supabase의 Database Webhook
→ Vercel 함수를 통한 이메일 알림 (Resend)
...

이메일의 버튼을 누르는 것만으로 승인할 수 있어, 스마트폰으로 몇 초 만에 처리할 수 있습니다. 링크는 ${id}:${action}를 비밀키로 HMAC 서명하여 위변조를 차단하고 있습니다.

기술 스택

  • 프론트엔드: Vite + React + TypeScript
  • 인증/DB/동기화: Supabase (Auth, Postgres, RLS, Database Webhooks)
  • LLM: Anthropic Claude (Haiku, tool use)
  • 레이트 리밋 (Rate Limiting): Upstash Redis
  • 이메일: Resend
  • 호스팅: Vercel (서버리스 함수)
  • 개발: Claude Code

요약

개인 개발에서 AI를 탑재한다면, 비용 대책은 '캐시 (Cache)'만으로는 부족합니다.

  • 로컬 지식으로 해결 가능한 입력은 애초에 API로 보내지 않는다 (②번 단계가 가장 효과적임) - 단, 로컬 해결은 잘못된 데이터가 즉시 오염으로 이어지므로
  • 모호하다면 반드시 API에 맡기는 보수적인 설계
  • 마지막으로 하루 상한선을 두는 물리적 킬 스위치 (Kill Switch)

'얼마나 똑똑하게 던질 것인가'보다 '얼마나 던지지 않고 해결할 것인가'. 이것이 개인 개발자로서 AI와 함께 살아가는 요령이라고 생각합니다.

제작 중인 도구는 여기 있습니다 (학생이 Claude Code로 개발 중) 👇

진행 상황은 X에서 공유하고 있습니다 → @kaihatsu_lab

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0