Tech_News

AWS가 17억 달러를 청구했다? 예상 청구(Estimated Billing) 데이터를 맹신하면 안 되는 이유

TeEm0 2026. 7. 19. 16:15
728x90

1. 도입: 월 $5 쓰던 계정에 $1.7B이 찍혔다

얼마 전 Hacker News에 재밌으면서도 등골 서늘한 글이 올라왔다. 평소 월 $5 쓰던 AWS 계정에 이번 달 예상 청구액이 $1.7 billion(17억 달러)으로 찍혔다는 제보다. 스크린샷 하나로 1000점 넘게 받은 걸 보면 다들 남 일 같지 않았던 모양이다.

댓글 중에 AWS 내부에서 비슷한 사고를 직접 처리해봤다는 사람(donavanm)의 설명이 핵심을 짚는다. 정리하면 이렇다.

"단위(unit) 에러였다. 원래 GB당 5센트를 매기려 했는데 단위 지정을 빼먹었고, 빌링 시스템이 기본값인 바이트(byte)로 계산해버렸다. 결과적으로 바이트당 5센트가 되어 몇 시간 만에 수백만 달러짜리 청구서가 나왔다."

여기서 우리가 실무자로서 챙겨야 할 포인트는 "AWS도 실수하네 ㅋㅋ"가 아니다. 우리가 매일 쳐다보는 Cost Explorer, Budgets 알람, 예상 청구액이 실제로 어떻게 계산되고, 어디서 틀어질 수 있는가다. 국내 많은 팀이 Budgets 알람 하나 걸어두고 "이거 울리면 대응하면 되지"라고 안심하는데, 그 알람이 참조하는 데이터 자체가 예상값(estimate)이라는 걸 잊고 있다.

2. 핵심: 예상 청구 데이터는 어떻게 만들어지는가

AWS 빌링은 대략 3단계 파이프라인으로 이해하면 편하다.

  1. Metering(계측): 각 서비스가 "이 계정이 이 리전에서 이 SKU를 얼마만큼 썼다"는 사용량 레코드를 뿜어낸다. 이때 나오는 값은 가격이 아니라 순수 사용량이다. (예: 데이터 전송 100바이트)
  2. Pricing Plan 조인: 이 계측 레코드를 account id, region, sku 등을 키로 "가격 계획"과 조인한다. 가격 계획에는 단위 타입(unit type), 리전, 단위당 가격이 정의돼 있다.
  3. 계산 및 집계: 사용량 × 단위당 가격을 곱해 라인 아이템 금액을 만들고, 이걸 계속 누적해서 "이번 달 예상 청구액"을 갱신한다.

이번 사고는 2번에서 터졌다. 계측은 "100바이트"라고 정확히 보냈는데, 가격 계획의 단위 타입이 GB여야 할 게 Byte로 잡혀 있으면(혹은 비어서 기본값 바이트로 떨어지면), 단위 환산이 통째로 날아간다. GB 기준 5센트가 바이트 기준 5센트가 되니까 10억 배 차이가 나는 거다.

비유하자면 이렇다. 주유소에서 "리터당 1,700원"을 매기려 했는데 시스템이 "밀리리터당 1,700원"으로 읽으면, 40리터 넣고 6만8천원 낼 게 6천8백만원이 나오는 셈이다. 계량기(metering)는 정확했지만 단가표(pricing plan)의 단위가 틀렸다.

HN 댓글의 CobrastanJorji 지적이 특히 실무적으로 와닿는다.

"테스트는 있었을 거다. 다만 end-to-end 테스트가 없었을 것이다. 계측 팀 테스트는 '100바이트 작업했더니 100바이트 빌링 콜이 나갔다, 통과'를 확인하고, 빌링 팀 테스트는 'SKU#12345로 100GB 들어오면 $17이다, 통과'를 확인한다. 그런데 이 둘을 붙여서 테스트하진 않는다. 팀 관리 체계가 다르고 더 어렵기 때문이다."

여기서 얻을 교훈: 각 컴포넌트가 개별적으로 정상이어도, 그걸 이어붙인 최종 숫자는 검증 안 될 수 있다. 이건 AWS만의 문제가 아니라 우리 팀 비용 파이프라인에도 똑같이 적용된다.

3. 실무 관점: 예상 청구를 맹신하면 안 되는 이유와 흔한 함정

3-1. 예상값(estimated)과 확정값(final)은 애초에 다른 데이터다

Cost Explorer나 Billing 콘솔에서 보는 "이번 달" 숫자는 대부분 estimated다. 월이 마감되고 최종 청구가 확정되기 전까지는 계속 흔들린다. 이유는 여러 가지다.

  • 계측 데이터가 실시간이 아니라 지연되어 들어온다 (서비스에 따라 수 시간~하루 이상).
  • Savings Plans, RI 적용, 크레딧, 할인은 마감 시점에 재계산되는 경우가 있다.
  • 세금, 지원 플랜 비용 등은 나중에 붙는다.

그래서 "어제까지 $300이었는데 오늘 갑자기 $500이네?" 같은 상황은 사고가 아니라 정상일 수 있다. 반대로 이번 HN 사례처럼 진짜 버그일 수도 있다. 이 둘을 구분하려면 estimate 하나만 봐선 안 되고 사용량 자체를 봐야 한다.

3-2. 흔한 함정: 데이터 나오는 시점을 착각하고 짠 알람

가장 자주 겪는 함정은 CUR(Cost and Usage Report)나 Cost Explorer API를 배치로 긁는데, 데이터가 아직 안 채워진 구간을 조회해서 "비용 0"으로 오탐/미탐하는 경우다. 예를 들어 오늘 날짜를 End에 넣으면 이런 걸 보게 된다.

$ aws ce get-cost-and-usage \
    --time-period Start=2024-06-01,End=2024-06-01 \
    --granularity DAILY \
    --metrics "UnblendedCost"

An error occurred (ValidationException) when calling the GetCostAndUsage operation:
Start date (and hour) should be before end date (and hour)

Cost Explorer의 기간은 End가 exclusive라서 Start와 End를 같게 주면 위 에러가 난다. 하루치를 보려면 End를 다음 날로 줘야 한다. 이거 모르고 Start=오늘, End=오늘로 짜서 배치가 조용히 죽는 케이스, 실무에서 흔하다.

3-3. 사용량과 금액을 분리해서 본다 (단위 에러를 잡는 핵심)

이번 17억 달러 사고의 본질은 "단위 환산이 깨져서 같은 사용량인데 금액만 폭발"한 거였다. 이걸 우리 쪽에서 감지하려면 금액만 보면 안 되고 UsageQuantity(사용량)를 같이 봐야 한다.

아래는 서비스별로 사용량과 비용을 함께 뽑아, 단위당 금액이 비정상적으로 튀는지 눈으로 확인하는 예시다.

$ aws ce get-cost-and-usage \
    --time-period Start=2024-06-01,End=2024-06-02 \
    --granularity DAILY \
    --metrics "UnblendedCost" "UsageQuantity" \
    --group-by Type=DIMENSION,Key=SERVICE \
    --query 'ResultsByTime[0].Groups[?Metrics.UnblendedCost.Amount!=`0`].[Keys[0],Metrics.UnblendedCost.Amount,Metrics.UsageQuantity.Amount]' \
    --output table

-------------------------------------------------------------------
|                        GetCostAndUsage                          |
+---------------------------+-----------------+-------------------+
|  Amazon S3                |  0.4213         |  120.5            |
|  AmazonCloudWatch         |  0.0021         |  3402.0           |
|  Amazon EC2               |  12.8842        |  24.0             |
+---------------------------+-----------------+-------------------+

만약 여기서 S3 사용량(UsageQuantity)은 평소와 같은데 Amount만 갑자기 몇 자릿수 뛰었다면, 이건 우리가 뭘 더 쓴 게 아니라 단가 쪽이 이상하다는 신호다. 이번 AWS 사고 유형이 딱 이거다. 반대로 UsageQuantity 자체가 폭발했으면 우리 쪽 코드가 뭔가 폭주(예: 무한 루프로 API 호출)한 거고.

참고로 UsageQuantity는 서비스마다 단위가 뒤섞여 있어서(GB, 시간, 요청 수 등) 서비스를 뭉뚱그려 합산하면 의미가 없다. 반드시 서비스/사용 타입(USAGE_TYPE) 단위로 쪼개서 봐야 한다.

3-4. 조직/문화적 함정도 있다

HN 스레드에서 전직 AWS 엔지니어들이 나눈 얘기도 실무자 입장에선 흘려들을 게 아니다. 한쪽에선 "이런 사고는 COE(Correction of Errors) 보고서를 강제해서 재발 방지 액션이 붙는다"고 하고, 다른 쪽에선 "인센티브 구조상 조용히 버그 고치는 것보다 터지고 나서 영웅처럼 수습하는 게 이득이라 예방을 안 한다"는 얘기가 나온다.

어느 쪽이 진실이든, 우리가 배울 건 하나다. 공급자(AWS)의 내부 검증을 신뢰의 최종 방어선으로 삼지 마라. 우리 청구서는 우리가 검증해야 한다. 벤더 알람만 믿고 있으면, 벤더가 실수했을 때 그대로 얻어맞는다.

4. 신뢰할 수 있는 비용 모니터링 아키텍처 설계

정리하면 계층을 나눠서 방어선을 여러 개 두는 게 핵심이다.

4-1. 1차 방어선: AWS Budgets — 빠르지만 거칠다

Budgets는 예상 청구액 기준으로 임계치 넘으면 알려준다. 반응이 빠르지만 estimate 기반이라 오차가 크다. "뭔가 크게 잘못됐다"를 놓치지 않는 용도로만 쓴다.

$ aws budgets create-budget \
    --account-id 123456789012 \
    --budget '{
      "BudgetName": "monthly-hard-cap",
      "BudgetLimit": {"Amount": "1000", "Unit": "USD"},
      "TimeUnit": "MONTHLY",
      "BudgetType": "COST"
    }' \
    --notifications-with-subscribers '[{
      "Notification": {
        "NotificationType": "FORECASTED",
        "ComparisonOperator": "GREATER_THAN",
        "Threshold": 120,
        "ThresholdType": "PERCENTAGE"
      },
      "Subscribers": [{"SubscriptionType": "EMAIL", "Address": "oncall@example.com"}]
    }]'

여기서 FORECASTED는 AWS가 이번 달 말 예상액을 계산해서 판단한다. 초반 데이터가 적을 때 예측이 널뛰기 때문에, ACTUAL 타입 알람도 하나 더 걸어두는 걸 권장한다. Forecast만 믿으면 초반에 false positive로 알람 피로가 쌓인다.

4-2. 2차 방어선: Cost Anomaly Detection — 패턴 이탈 감지

AWS Cost Anomaly Detection은 서비스별 지출 패턴을 학습해서 평소와 다른 스파이크를 잡아준다. 이번 HN 댓글에서 akdev1l이 "청구액이 1000만% 뛰면 누가 좀 봐야 하는 거 아니냐"고 했는데, 딱 이 도구가 그 역할을 노린다. 절대 금액 임계치가 아니라 상대적 이상 탐지라 단위 에러 같은 폭발형 사고에 강하다.

4-3. 3차 방어선: CUR + 자체 검증 — 가장 신뢰할 수 있는 근거

CUR(Cost and Usage Report)은 라인 아이템 수준의 원장(ledger)이다. S3에 떨어지고 Athena로 쿼리할 수 있다. 여기서 사용량과 단가를 분리해서 검증하는 게 단위 에러를 잡는 마지막 열쇠다.

-- Athena: 사용 타입별 '단위당 실효 단가'가 튀는 항목 찾기
SELECT
    line_item_product_code            AS service,
    line_item_usage_type              AS usage_type,
    SUM(line_item_usage_amount)       AS usage_qty,
    SUM(line_item_unblended_cost)     AS cost_usd,
    SUM(line_item_unblended_cost)
      / NULLIF(SUM(line_item_usage_amount), 0) AS effective_unit_price
FROM cur_table
WHERE line_item_usage_start_date
      >= date_trunc('day', current_timestamp - interval '1' day)
GROUP BY 1, 2
ORDER BY effective_unit_price DESC
LIMIT 20;

effective_unit_price(실효 단가 = 비용 / 사용량)를 어제와 그제로 비교해서, 특정 usage_type의 실효 단가가 갑자기 몇 자릿수 뛰면 그게 바로 "단위가 깨졌다"는 증거다. Budgets나 Cost Explorer의 총액만 보고 있으면 이걸 못 잡지만, 실효 단가로 내려가면 원인 위치까지 좁혀진다.

5. 인프라 엔지니어를 위한 비용 검증 체크리스트

  • estimate와 actual을 구분해서 본다. 이번 달 콘솔 숫자는 흔들린다는 걸 팀에 공유. "어제보다 늘었다"만으로 놀라지 않기.
  • 금액과 사용량(UsageQuantity)을 항상 함께 조회한다. 금액만 튀고 사용량은 그대로면 단가/단위 문제, 사용량이 튀면 우리 쪽 문제.
  • Budgets는 FORECASTED + ACTUAL 두 개를 건다. Forecast 단독은 초반 오탐이 심하다.
  • Cost Anomaly Detection을 켠다. 절대 임계치가 못 잡는 패턴 이탈용.
  • CUR을 Athena에 붙이고 실효 단가(비용/사용량) 대시보드를 만든다. 자릿수 단위 이상 탐지가 목적.
  • Cost Explorer API의 End는 exclusive라는 걸 배치 코드에 반영한다. Start date should be before end date 에러로 배치 죽는 거 방지.
  • 벤더 알람을 최종 방어선으로 삼지 않는다. HN 사례가 증명했듯 공급자도 단위 하나로 17억 달러를 틀린다.

정리

한 줄 요약: AWS 예상 청구는 "사용량 × 가격 계획"으로 계산되는데, 가격 계획의 단위 하나가 틀어지면 사용량이 정확해도 금액이 수십억 배 폭발할 수 있다. 그러니 금액만 보지 말고 사용량과 실효 단가를 분리해서 검증하는 자체 방어선을 반드시 갖춰라.

누가 언제 써야 하나: Budgets 알람 하나에 비용 관리를 의존하고 있는 모든 팀. 특히 월 청구 규모가 커서 하루 오차가 큰 조직이라면, CUR + Athena 실효 단가 검증은 선택이 아니라 필수다. 최소한 Cost Anomaly Detection이라도 오늘 켜두자. 이번 17억 달러 사건은 남 일처럼 보이지만, 정작 우리 계정에서 비슷한 게 터졌을 때 "이게 진짜 청구되는 건지 아닌지" 30초 안에 판단할 근거가 있느냐가 진짜 실력 차이다.

참고 자료

※ 본문의 명령어 출력 예시 수치는 형식 설명을 위한 예시이며 실제 요금이 아니다. 각 API의 정확한 파라미터/동작은 위 공식 문서에서 최종 확인하기 바란다.

728x90