케이스 스터디
CASE STUDY — AI

AI 자동화, 이번 달 얼마 나올까 — n8n 워크플로별 토큰·비용 추적과 한도 차단

도입 전 부서 / AI 자동화 도입구축 4일
n8nOpenAIPostgresSlack

AI 자동화를 도입할 때 가장 많이 받는 질문은 성능이 아닙니다. "그거 한 달에 얼마 나와요?" 입니다. 그리고 대부분의 팀은 첫 청구서를 받기 전까지 대답하지 못합니다.

이건 기술 문제가 아니라 기록 문제입니다. 토큰 사용량은 이미 AI 응답 안에 들어 있습니다. 모든 OpenAI 응답에는 usage 필드가 함께 옵니다. 빠진 건 그걸 워크플로 이름과 함께 쌓아두고, 단가를 곱해, 예산과 비교하는 습관뿐입니다. 이 케이스는 그 세 가지를 자동화합니다.

ai-cost-guard.workflowLIVE
CR매일 09:00PG사용량 집계$예산 대비 판정SL경고 · 차단

왜 지나고 나서야 알게 되는가

  • 응답에 있는 `usage`를 아무도 저장하지 않습니다. 대부분의 워크플로는 choices[0].message.content만 꺼내 쓰고 나머지는 버립니다. 비용 정보가 매번 눈앞을 지나가는데 저장이 안 됩니다.
  • 단가가 모델마다 다르고, 계속 바뀝니다. 입력 토큰과 출력 토큰의 단가도 보통 다릅니다(출력이 몇 배 비쌉니다). 그래서 코드 안에 숫자를 박아두면 몇 달 뒤 조용히 틀린 값을 보고하게 됩니다.
  • 공급사 대시보드는 워크플로별로 안 나눠줍니다. 이번 달 총액은 보여주지만 '어느 자동화가 범인인지'는 안 알려줍니다. 그래서 비용이 튀어도 무엇을 줄여야 할지 결정할 수가 없습니다.
그래서 이 케이스의 설계 원칙은 두 개입니다. 단가는 코드가 아니라 테이블에 둡니다(확인 날짜와 함께). 그리고 비용은 워크플로 단위로 쪼갭니다. 총액만 알면 걱정만 늘고, 워크플로별로 알면 결정을 내릴 수 있습니다.

어떻게 동작하나

  1. AI를 호출하는 워크플로마다 AI 노드 바로 뒤에 Postgres 노드를 하나 붙여, 응답의 usage에서 입력·출력 토큰을 꺼내 ai_usage 테이블에 한 줄 기록합니다.
  2. 단가는 ai_model_prices 테이블에 모델별로 넣어둡니다. 백만 토큰당 입력 단가와 출력 단가, 그리고 확인한 날짜를 함께 저장합니다.
  3. Schedule Trigger가 매일 오전 9시에 감시 워크플로를 깨웁니다.
  4. Postgres 노드가 이번 달 1일부터 지금까지의 사용량을 워크플로별로 집계하고, 단가표와 조인해 실제 비용을 계산합니다. 단가표에 없는 모델은 0원으로 세지 않고 '단가 미등록'으로 따로 표시합니다.
  5. Code 노드가 지금까지의 지출로 월말 예상 비용을 직선 추정합니다(현재 지출 ÷ 경과일 × 그달 총일수). 예산 대비 비율을 계산해 정상 / 임박(80%) / 초과(100%)를 판정합니다.
  6. 정상이고 단가 미등록도 없으면 빈 배열을 반환해 조용히 끝냅니다. 매일 "괜찮습니다" 알림이 오면 아무도 안 읽게 되기 때문입니다. 임박·초과이거나 단가 미등록이 있을 때만 Slack에 보고합니다.

필요한 것

  • n8n — 자체 호스팅 또는 Cloud. 기본 노드 네 개(Schedule Trigger, Postgres, Code, Slack)만 씁니다.
  • Postgres 데이터베이스 하나 — 테이블 두 개를 추가합니다. 호출 한 건당 한 줄이라 하루 수천 건이어도 부담이 없습니다.
  • Postgres 자격증명 — n8n → Credentials → Postgres. n8n이 Docker 안이고 DB가 호스트 머신이면 호스트에 localhost가 아니라 host.docker.internal을 넣어야 합니다.
  • Slack Bot User OAuth Tokenchat:write, channels:read 스코프. n8n → Credentials → Slack API에 등록.
  • 이미 AI를 호출하고 있는 워크플로 한 개 이상 — 이 케이스는 새 AI 워크플로를 만드는 게 아니라, 이미 돌고 있는 것에 계량기를 다는 작업입니다.
  • 사용 중인 모델의 현재 공시 단가 — 공급사 요금 페이지에서 직접 확인하세요. 백만 토큰당 입력 단가와 출력 단가 두 개가 필요합니다. 이 문서에는 특정 모델의 단가를 적지 않습니다. 적어두는 순간 틀린 값이 되기 때문입니다.

1단계 — 테이블 두 개 만들기

-- 호출 한 건당 한 줄. 여기에 쌓이는 것이 곧 원장입니다.
CREATE TABLE IF NOT EXISTS ai_usage (
  id            bigserial   PRIMARY KEY,
  workflow_name text        NOT NULL,  -- 어느 자동화가 쓴 비용인지
  model         text        NOT NULL,  -- 호출한 모델 이름 (단가표와 조인되는 키)
  called_at     timestamptz NOT NULL DEFAULT now(),
  input_tokens  integer     NOT NULL DEFAULT 0,
  output_tokens integer     NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS ai_usage_time_idx ON ai_usage (called_at DESC);

-- 단가표. 코드가 아니라 여기에 둡니다.
CREATE TABLE IF NOT EXISTS ai_model_prices (
  model         text    PRIMARY KEY,
  input_per_1m  numeric NOT NULL,  -- 백만 입력 토큰당 USD
  output_per_1m numeric NOT NULL,  -- 백만 출력 토큰당 USD
  updated_at    date    NOT NULL   -- 단가를 확인한 날짜
);

-- 사용 중인 모델을 공급사 요금 페이지에서 확인해 넣으세요.
-- 아래는 형식 예시이며 실제 단가가 아닙니다.
-- INSERT INTO ai_model_prices VALUES ('사용중인-모델명', 0.00, 0.00, CURRENT_DATE);
updated_at을 반드시 채우세요. 반년 뒤 리포트 숫자가 이상할 때 가장 먼저 의심해야 할 것이 단가표의 신선도입니다. 확인 날짜가 없으면 그 판단을 할 수 없습니다.

2단계 — AI 노드 뒤에 계량기 달기

AI를 호출하는 워크플로를 열어 AI 노드 바로 뒤에 Postgres 노드를 추가합니다. Operation은 Execute Query로 두고 아래를 넣습니다. cs-triage 자리에 해당 워크플로 이름을 넣으세요.

INSERT INTO ai_usage
  (workflow_name, model, input_tokens, output_tokens)
VALUES (
  'cs-triage',
  '{{ $json.model }}',
  {{ $json.usage.prompt_tokens }},
  {{ $json.usage.completion_tokens }}
);

이 값들은 어디서 오는가 — OpenAI Chat Completions API(https://api.openai.com/v1/chat/completions)의 응답에는 본문(choices[0].message.content)과 나란히 `model`과 `usage`가 들어 있습니다. usage.prompt_tokens가 입력, usage.completion_tokens가 출력 토큰입니다. 즉 추가 API 호출이 전혀 필요 없습니다. 원래 버리고 있던 데이터를 줍는 것뿐입니다. 다른 공급사를 쓴다면 응답에서 사용량 필드 이름만 바꿔 넣으면 됩니다.

3단계 — 이번 달 집계 쿼리

감시 워크플로의 Month-to-Date Cost by Workflow 노드에 들어가는 쿼리입니다. 다운로드한 JSON에 이미 들어 있습니다. 워크플로 하나당 한 행이 나오고, 비용이 큰 순서로 정렬됩니다.

WITH priced AS (
  SELECT u.workflow_name,
         u.input_tokens,
         u.output_tokens,
         -- 단가표에 없는 모델은 0이 아니라 NULL. 0으로 세면 비용이 숨습니다.
         CASE WHEN p.model IS NULL THEN NULL
              ELSE (u.input_tokens::numeric  / 1000000) * p.input_per_1m
                 + (u.output_tokens::numeric / 1000000) * p.output_per_1m
         END AS cost_usd
  FROM ai_usage u
  LEFT JOIN ai_model_prices p ON p.model = u.model
  WHERE u.called_at >= date_trunc('month', now())   -- 이번 달 1일부터
)
SELECT workflow_name,
       count(*)                                 AS calls,
       count(*) FILTER (WHERE cost_usd IS NULL) AS unpriced_calls,
       COALESCE(sum(input_tokens), 0)           AS input_tokens,
       COALESCE(sum(output_tokens), 0)          AS output_tokens,
       round(COALESCE(sum(cost_usd), 0), 4)     AS cost_usd
FROM priced
GROUP BY workflow_name
ORDER BY cost_usd DESC;
LEFT JOIN + CASE WHEN p.model IS NULL이 중요합니다. 단가표에 없는 모델을 그냥 JOIN으로 빼버리면 그 호출은 리포트에서 통째로 사라지고, 비용은 실제보다 적게 보고됩니다. 모르는 건 0원이 아니라 '모른다'로 표시해야 합니다.

4단계 — 예산을 넘으면 아예 못 쓰게 막기

여기까지는 보고입니다. 실제로 막으려면 AI 노드 *앞에* 관문을 하나 둡니다. 비용이 큰 워크플로에만 붙이면 됩니다.

  1. AI 노드 앞에 Postgres 노드를 추가하고 Operation을 Execute Query로 둔 뒤 아래 쿼리를 넣습니다 — 이번 달 누적 비용 한 줄이 나옵니다.
  2. 그 뒤에 IF 노드를 놓고 조건을 {{ $json.mtd_cost_usd }} Number / Smaller than 예산값(예: 100)으로 설정합니다.
  3. IFtrue 출력을 원래의 AI 노드로 연결합니다. 예산 안이면 평소대로 동작합니다.
  4. IFfalse 출력은 Stop and Error 노드로 연결하고 메시지를 월 AI 예산 초과 — 실행 중단으로 둡니다. 이러면 실행이 에러로 기록되므로 Error Trigger나 앞서 만든 감시 워크플로가 바로 잡아냅니다.
SELECT round(COALESCE(sum(
         (u.input_tokens::numeric  / 1000000) * p.input_per_1m +
         (u.output_tokens::numeric / 1000000) * p.output_per_1m
       ), 0), 4) AS mtd_cost_usd
FROM ai_usage u
JOIN ai_model_prices p ON p.model = u.model
WHERE u.called_at >= date_trunc('month', now());
차단은 신중하게 거세요. 고객 응대처럼 멈추면 안 되는 워크플로에 이걸 달면, 예산 초과가 곧 서비스 중단이 됩니다. 차단은 배치성·내부용 워크플로에 걸고, 사람이 기다리는 워크플로에는 경고만 거는 것을 권합니다.
⬇︎ 워크플로우 다운로드 (ai-cost-guard.json)
Postgres 자격증명 하나, Slack 자격증명 하나, 채널 이름, 그리고 월 예산 숫자 하나만 넣으면 돌아갑니다. 워크플로우 상단 Sticky Note에 테이블 생성 SQL이 포함돼 있습니다.
검증 범위를 정확히 밝힙니다. 집계 쿼리는 PostgreSQL 17에서 픽스처로 실행해 확인했습니다 — 두 모델의 서로 다른 단가가 정확히 곱해지는지, 단가표에 없는 모델이 unpriced_calls로 분리되는지, 지난달 데이터가 이번 달 집계에 섞이지 않는지까지 포함합니다. Code 노드는 그 쿼리의 실제 출력을 입력으로 Node.js에서 실행해 정상 / 임박(86%) / 초과(101%) 세 구간과 '이상 없으면 빈 배열 반환' 경로를 모두 확인했습니다. 노드 타입·버전은 기존에 배포 중인 워크플로우와 동일한 조합입니다(scheduleTrigger 1.2 / postgres 2.5 / code 2 / slack 2.3). 실제 Slack 워크스페이스 게시와 실제 OpenAI 청구서 대조는 이 문서 작성 시점에 확인하지 않았습니다. 마지막 검증: 2026-08-15.

설정 순서 (30분)

  1. 테이블 생성 — 1단계 SQL을 DB에서 실행합니다. CREATE TABLE이 두 번, CREATE INDEX가 한 번 출력되면 성공입니다.
  2. 단가 입력 — 사용 중인 모델의 현재 공시 단가를 공급사 요금 페이지에서 확인해 ai_model_prices에 INSERT 합니다. 입력·출력 단가가 다르므로 각각 확인하세요. updated_at에는 오늘 날짜를 넣습니다.
  3. Postgres 자격증명 등록 — n8n → Credentials → Add credential → Postgres → 접속 정보 입력 → Test로 초록불 확인.
  4. Slack 앱 만들기 — api.slack.com/apps → Create New App → From scratch → OAuth & Permissions → Bot Token Scopes에 chat:write, channels:read 추가 → Install to Workspace → xoxb- 토큰 복사.
  5. Slack 자격증명 등록 — n8n → Credentials → Slack API → 토큰 붙여넣기 → Test.
  6. 봇 초대 — 알림 받을 Slack 채널에서 /invite @봇이름. 빠뜨리면 not_in_channel 에러가 납니다.
  7. 계량기 달기 — AI를 호출하는 워크플로를 하나씩 열어 AI 노드 뒤에 2단계의 Postgres 노드를 추가합니다. 워크플로 이름을 각각 맞게 넣으세요. 이 단계를 해야 데이터가 쌓이기 시작합니다.
  8. 쌓이는지 확인 — 해당 워크플로를 한 번 실행한 뒤 SELECT * FROM ai_usage ORDER BY called_at DESC LIMIT 5;로 행이 들어왔는지 봅니다. 토큰 수가 0이면 응답 필드 경로가 다른 것이니 AI 노드의 출력 JSON을 열어 usage 위치를 확인하세요.
  9. 감시 워크플로 import — 위 다운로드 버튼으로 JSON 저장 → n8n → Workflows → ...Import from File.
  10. 예산 설정Budget Check + Format 노드를 열어 맨 위 MONTHLY_BUDGET_USD 값을 실제 월 예산으로 바꿉니다.
  11. 채널 이름 교체 + 자격증명 연결Post to #ai-cost 노드에서 REPLACE_WITH_YOUR_CHANNEL_NAME_OR_ID를 실제 채널명으로 바꾸고, 두 노드에 자격증명을 연결합니다.
  12. 테스트Execute Workflow를 누릅니다. 아직 사용량이 적어 정상 판정이면 아무 일도 안 일어나는 게 맞습니다. 메시지를 보고 싶으면 MONTHLY_BUDGET_USD를 아주 작은 값(예: 0.01)으로 잠시 바꿔 실행해 보고, 확인 후 되돌리세요.
  13. 활성화Activate 토글을 켭니다. 매일 오전 9시에 점검합니다.

임계값 조정

  • `MONTHLY_BUDGET_USD` (기본 100) — 월 예산. 이 케이스에서 실제로 바꾸게 되는 거의 유일한 값입니다.
  • `WARN_RATIO` (기본 0.8) — 월말 예상이 예산의 몇 할을 넘으면 경고할지. 0.8이면 80%에서 알립니다. 대응할 시간이 필요하다면 0.7로 낮추세요.
  • `ALWAYS_REPORT` (기본 false) — true로 두면 정상일 때도 매일 리포트가 옵니다. 도입 첫 달에는 감을 잡기 위해 켜두고, 익숙해지면 끄는 것을 권합니다.
  • `USD_TO_KRW` (기본 1380) — 원화 병기용 환율입니다. 참고용 괄호 표시에만 쓰이고 판정에는 영향을 주지 않습니다. 정확한 청구 금액은 공급사 청구서를 보세요.

예외 상황이 있으면 어떻게 되나

  • 단가표에 없는 모델을 호출한 경우 — 비용 0원으로 숨기지 않고 '단가 미등록 N건'으로 Slack에 경고합니다. 정상 상태여도 이 경우엔 알림이 발송됩니다. 모델을 바꾸고 단가표 갱신을 잊는 일이 실제로 자주 일어나기 때문입니다.
  • 월 초라 표본이 적을 때 — 직선 추정이라 1~2일차에는 예상치가 크게 흔들립니다. 첫 며칠의 초과 경고는 참고만 하시고, 판단은 5일 이후 수치로 하세요.
  • 재시도로 인한 중복 과금 — n8n이 실패한 AI 호출을 재시도하면 토큰은 두 번 소모되고, 기록도 두 줄 남습니다. 이건 버그가 아니라 실제로 두 번 청구되기 때문에 맞는 동작입니다. 재시도가 잦다면 그 자체가 비용 문제입니다.
  • 스트리밍 응답 — 스트리밍으로 받으면 usage가 마지막 청크에만 오거나 아예 안 올 수 있습니다. 이 케이스는 비스트리밍(일반 Chat Completions) 호출을 전제로 합니다. 스트리밍을 쓴다면 해당 호출은 별도 처리가 필요합니다.
  • 임베딩·이미지 등 단가 체계가 다른 호출 — 토큰 기반이 아닌 호출은 이 테이블 구조로 정확히 계산되지 않습니다. 별도 모델명으로 기록해 두고 단가표에서 제외한 뒤, '단가 미등록'으로 눈에 띄게 두는 편이 낫습니다.
  • 환율 변동 — 원화 표기는 고정 환율이라 실제 청구액과 차이가 납니다. 예산 판정은 USD 기준으로만 이뤄지므로 판단에는 영향이 없습니다.
워크플로별
비용 분리
80%
사전 경고 시점
매일 09:00
점검 주기
30분
셋업 시간
최근에서야 RAG라는 것을 알게 되었습니다. n8n을 이용해서 만들어보려고 합니다. 그런데 어떤 AI를 결제해야 할 지 모르겠습니다.국내 개발자 커뮤니티

당신의 업무도
여기 들어갈 수 있어요.

가장 반복적인 업무 하나만 알려주세요. 자동화 시나리오를 그려드립니다.