본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 29. 22:21

왜 AI 하루 사용 비용이 서버 한 달 비용보다 더 많이 나왔을까?

요약

비전문가가 Claude Code로 빠르게 구축한 SaaS에서 발생한 막대한 LLM API 비용 폭증 사례를 다룹니다. 원인은 LLM 호출은 성공했으나 DB 저장 단계에서 오류가 발생하여, 재시도 메커니즘이 동일한 무거운 배치 작업을 반복 실행했기 때문입니다.

핵심 포인트

  • LLM 호출 성공 후 후속 작업 실패 시 재시도 로직 주의
  • 재시도 메커니즘이 비용 폭증의 주범이 될 수 있음
  • 비전문가의 빠른 배포와 엔지니어링 관리의 중요성
  • 배치 작업의 원자성(Atomicity) 확보 필요

늘 똑같은 이야기입니다. 우리 CFO(최고재무책임자)가 이틀 만에 프로덕션(production)에 배포해 버린 SaaS(Software as a Service)를 제가 운영하고 있습니다. 엔지니어가 아닌 임원이 Claude Code를 사용해 무언가를 빠르게 만들어냈고, 엔지니어인 저는 백엔드(back end)를 하나씩 뜯어보고 있습니다. 확인할 때마다 무언가 계속 튀어나옵니다.

이번에는 "비밀 키(secrets)가 어디에 저장되어 있는지"의 문제도 아니었고, "테스트 코드가 단 하나도 없는" 문제도 아니었습니다. 이번에는 돈이 타버렸습니다.

어느 날 LLM(대규모 언어 모델) API 비용 그래프를 보고 있는데, 단 하루의 수치가 마치 후지산처럼 솟아올라 있었습니다. 다른 날들은 모두 바닥에 붙어 있는데, 그날 하루만 하늘을 찌르고 있었습니다. 한 달 전체 청구 금액의 대략 절반이 그 하루에 몰려 있었습니다.

솔직히 말해서, 그 숫자를 봤을 때 가슴이 철렁 내려앉았습니다. 왜냐하면 AI 사용 단 하루의 비용이 서버 한 달 전체 비용보다 더 많이 들었기 때문입니다. 한 달 동안 전체 서버 플릿(fleet)을 운영하는 것이 AI가 단 하루 동안 대화하게 두는 것보다 저렴합니다. 어떻게 이런 일이 가능할까요?

그래서 저는 그것을 만든 사람(CFO)에게 가서 물었습니다. "그날 무엇을 하셨나요?"

답변은 이랬습니다.

**"솔직히 말해서, 제가 뭘 했는지 기억이 안 나요."

세상에나.

하지만 이것은 누구를 비난하려는 이야기가 아닙니다(음, 절반 정도는 그렇습니다). 더 깊이 파고들수록 저는 다음과 같은 결론에 도달했습니다. 그들이 기억하지 못하는 것은 당연합니다. 돈을 태운 것은 사람이 아니었습니다. 그것은 재시도 메커니즘(retry machinery)이었습니다.

추적. 처음에는 그저 하루 종일 몰아붙인 것이라고 생각했습니다

제 첫 번째 추측은 대략 이랬습니다. "그날 수많은 기능을 만들었고, 그것들을 프로덕션에서 반복해서 테스트하면서, 매번 비싼 LLM을 호출했겠구나. 수많은 작은 실수들이 모여 치명적인 결과를 초래한 것이군."

그럴듯해 보였습니다. 그날의 커밋 히스토리(commit history)는 아침부터 저녁까지 꽉 차 있었고, AI 생성 흐름 주변으로 20개 이상의 변경 사항이 있었습니다. 따라서 "인간의 반복 작업으로 인한 서서히 타오르는 비용(slow burn)"이라는 가설은 설득력이 있었습니다.

하지만 실제로 앱 측 로그(작업 큐(task queue), DB, 요청(requests))를 파헤쳐 보니 상황은 완전히 달랐습니다. 그것은 서서히 타오르는 것이 아니었습니다. 동일한 무거운 배치(batch) 작업이 기계에 의해 전체적으로, 반복해서 재실행되고 있었습니다. 단일 테넌트(tenant)에 대해, 보통 한 번 실행되어야 할 작업이 21번이나 실행되었습니다.

사람은 하루에 같은 버튼을 21번이나 누르지 않습니다. 버튼을 누르고 있던 것은 사람이 아니었습니다.

가장 무서운 점은 "성공한 뒤에 넘어졌다"는 것입니다

이것이 이번 사건의 핵심이므로, 천천히 설명하겠습니다.

배치(batch) 작업은 여러 LLM을 순차적으로 호출하고 그 결과를 DB에 저장했습니다. 대략적인 흐름은 다음과 같습니다:

  1. 여러 LLM에 대량의 쿼리(query)를 발송합니다 (이 단계에서 비용이 발생합니다)
  2. 반환된 결과를 DB에 기록합니다

문제는 2단계에서 발생했습니다. 쓰기(write) 작업이 추가되었어야 할 컬럼(column)을 참조했지만, 해당 컬럼이 아직 존재하지 않았던 것입니다. DB에 컬럼이 없었기 때문에 column does not exist 오류가 발생했고, 작업은 500 에러를 반환했습니다.

"실패했다"라는 말을 들으면 보통 "호출이 터져서 기회를 날려버렸다"는 장면을 떠올리기 마련입니다. 하지만 그렇지 않았습니다. 모든 LLM 호출은 성공했습니다. 모두 200 OK 응답을 받았습니다. 이는 모든 호출에 대해 정상적으로 비용이 청구되었다는 것을 의미합니다. 비용을 지불했고, 결과도 받아냈지만, 바로 마지막 단계인 저장 단계에서 발을 헛디딘 것입니다.

식당 상황에 비유하자면 이렇습니다: 코스 요리를 다 먹고 계산까지 마쳤는데,

함정 1: 배포 순서가 거꾸로 되었습니다.
새로운 컬럼(column)이 이미 존재한다고 가정하고 코드를 운영 환경(production)에 배포했지만, 해당 컬럼을 추가하는 마이그레이션(migration)이 아직 운영 환경에 적용되지 않은 상태였습니다. 코드(code)가 먼저, 스키마(schema)가 나중인 상황입니다. 그런 순서로 진행되면, 코드는 존재하지 않는 컬럼을 찾으려 시도하고 결정론적(deterministically)으로 실패합니다. 그리고 여기서 "결정론적"이라는 말이 핵심입니다. 이것은 아무리 여러 번 재시도하더라도 절대로 스스로 해결되지 않는 종류의 실패입니다.

함정 2: 실패했을 때, 작업 큐(task queue)가 친절하게도 이를 다시 실행합니다.
관리형 작업 큐(managed task queue)는 작업이 500 에러와 함께 종료되는 것을 보고, "오, 실패했네. 대신 다시 실행해 줄게"라며 자동으로 동작합니다. 일시적인 네트워크 오류(transient network blip)라면 이는 올바른 친절입니다. 하지만 이번 실패는 "컬럼이 존재하지 않음"이었습니다. 아무리 재실행을 해도 컬럼은 생겨나지 않습니다. 작업 큐는 친절함 때문에 해결 불가능한 실패를 무한히 반복했습니다.

게다가 해당 배치(batch) 작업이 멱등성(idempotent)을 갖추지 못했기 때문에(이미 처리된 작업을 건너뛰지 못함), 매 재실행마다 처음부터 다시 시작되었습니다. 따라서 매 라운드마다 전체 LLM 비용이 발생했습니다.

결정론적 실패(Deterministic failure) × 자동 재시도(automatic retry) × 비멱등성(non-idempotent). 이 세 가지가 맞물리면 돈이 조용히 타들어 갑니다. 그 사람이 기억하지 못하는 것도 당연합니다. 그들은 아무것도 하지 않았으니까요. 버튼을 누르고 있었던 것은 바로 큐(queue)였습니다.

제가 이 상황을 설명했을 때, CFO는 얼굴을 찌푸리며 말했습니다. "흐음?" (엔지니어가 아닌 사람에게 "성공했는데, 비용은 청구되었고, 그러고 나서 성공 결과는 버려졌다"는 말은 정말 받아들이기 힘든 사실입니다.)

교훈: 재시도는 친절이 아니다

나 자신을 위해 이 교훈들을 기록해 두려 합니다. 왜냐하면 이와 같은 상황에 처한 누구라도(누군가가 운영 중인 운영 환경을 물려받은 사람이라면 누구라도) 똑같은 교훈을 얻게 될 것이기 때문입니다.

  • 결정론적 실패 (Deterministic failure)는 재시도한다고 해서 나아지지 않습니다. 스키마 불일치 (Schema mismatch), 4xx 클래스의 "당신이 틀렸습니다" 오류 등은 아무리 많이 던져봐도 결과는 같습니다. 이러한 오류는 즉시 "중단 (abort)" 처리해야 하며, 항상 재시도 상한선 (retry ceiling)을 설정해야 합니다. 재시도는 만능 보험이 아닙니다.
  • 부작용 (Side effect)이 클수록 멱등성 (Idempotent)이 더 필요합니다. 비용이 발생하는 작업(결제 API, LLM 호출)을 수행하는 모든 배치 (Batch) 작업은 첫날부터 "이미 완료된 작업은 건너뛰기" 기능이 필요합니다. 이 기능이 없다면 재실행은 "다시 하기"가 아니라 "이중 과금"이 됩니다.
  • "스키마, 그다음 코드" 순서로 배포하세요. DB 변경 사항을 먼저 적용한 다음, 이를 사용하는 코드를 배포하십시오. 순서를 반대로 하면 그 간극 사이에 결정론적 오류를 대량 생산하게 됩니다.
  • 비용을 관찰 (Observable)할 수 없다면, "다 태워버린 후에야" 알아차리게 됩니다. 우리가 이 문제를 발견할 수 있었던 유일한 이유는 제가 우연히 비용 그래프를 확인했기 때문입니다. 연기 감지기(Smoke detectors)—운영(Prod)과 테스트(Test)를 위한 별도의 키, 예산 알림(Budget alerts)—가 없다면 청구서가 도착하기 전까지는 아무도 알아차리지 못합니다.

바이브 코딩 (Vibe coding)은 비엔지니어가 프로덕션을 구축하는 장벽을 확실히 낮추어 주었습니다. 하지만 "어떻게 망가질 수 있는지"와 "어떻게 비용이 많이 발생할 수 있는지"를 파악하는 것은 여전히 별개의 기술입니다. 그 부분은 여전히 이를 물려받은 엔지니어의 몫입니다.

기능을 만드는 데는 이틀이면 충분할 수 있습니다. 하지만 "실패를 우아하게 재시도"하려던 순간이 "성공을 버리고 이중 과금"하는 상황으로 변질되는 것을 방지하는 법은 이틀 만에 배울 수 없습니다.

재시도가 항상 친절한 것은 아닙니다.

저는 서버 비용보다 더 큰 AI 청구서를 다시는 보고 싶지 않습니다. 그래서 저 자신에게 주는 경고로서 이 글을 남깁니다.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0