조직 형태에 맞춰진 비용 귀속 구축하기
한 사람(조직에 담당자가 있다면 재무 파트너 포함)이 표준 스키마(FOCUS)와 서버리스 런타임을 연결하는 작업을 주로 수행하도록 안내합니다. 패턴은 정해져 있지만, 형태는 그들의 것입니다. 여러분의 임무는 그들의 데이터에서 형태를 찾아 기록하고, 그 형태에 패턴을 적용하는 것입니다. 절대 일반 제품을 설치하는 것이 아닙니다.
각 패턴이 왜 올바른지 설명하는 글은 다음에서 확인할 수 있습니다: https://mooracle.io/blog/build-custom-finops-tool/ (파트 1-7). 사용자가 "왜"라고 묻으면 해당 파트를 가리키고, 세션은 "어떻게"에 집중하세요.
불변 규칙(Invariants)
다음은 프로필에 관계없이 절대 변하지 않습니다. 생성된 코드를 사용자에게 보여주기 전에 이 규칙에 위배되지 않는지 확인하세요.
- 모든 분할은 총합을 유지한다. 비용 열별로
SUM(출력) == SUM(입력)입니다. 분할자(splitter)는 돈을 이동시키지만, 생성하거나 파괴하지 않습니다. - 쿼리 시점에 할당한다. 조직 범주는 뷰(view)에서 조회(lookup)를 통해 첨부됩니다. 웨어하우스 행에
team을 직접 쓰지 마세요. - Chargeback에는
EffectiveCost를 사용한다.BilledCost는 청구서와 조정하고,ListCost는 절감액을 측정합니다. 절대 혼합하지 마세요. - 전체 월을 재구축한다. 각
(billing_period, provider)파티션은 전달 시마다 원시(raw) 데이터에서 지워지고 다시 작성됩니다. 델타(Deltas)나 병합(Merges)은 없습니다. - 어제까지만 처리한다. 오늘 또는 미래 날짜의 데이터는 웨어하우스에 들어가지 않습니다.
- 모든 분할은 하나의 팬아웃(fan-out)이다. 분할되는 행은 정기 로드 내에서 가중치를 조인하여 가중치당 하나의 행이 됩니다. 키의 가중치 합계는 1.0입니다. 공평 분할(Fair-share), Kubernetes 및 공급업체 분할도 모두
split_weights테이블을 통해 동일한 방식으로 작동합니다. 로드에서 제외되었다가 나중에 다시 추가되는 것은 없으므로, 잊어버릴 제외 항목이 없고, 어떤 것도 두 번 계산되지 않습니다. - 모든 실행은 숫자를 증명한다. Raw는 공급자의 매니페스트가 나열한 파일들을 정확히 보유하며, 차단 합계 검사(blocking totals check)는 청구서를 두 기본 테이블(base table)과 비교하고, 불일치 시 실행을 실패 처리합니다(그리고 쓴 내용을 지웁니다). 뷰가 존재하면, 모든 실행과 마감 시에도
services_monthly가 정확히base_monthly를 제공하는지 확인합니다(뷰 검사는 실패해도 지우지 않습니다). - 비용을 절대 잃지 않는다. 라우팅은 실패할 수 있다. 분할을 안전하게 수행할 수 없으면 비용은 청구된 그대로 온전히 남습니다. 건너뛴 분할은 눈에 띄지만, 손실된 비용은 조용히 사라집니다.
- Raw 읽기는 정확한 월 기준이다.
WHERE billing_period = 'YYYY-MM'과 함께 엔진의 스캔 상한(Athena의 경우 워크그룹 바이트 상한)을 적용합니다. 전체 기록 읽기는base_monthly(메가바이트 단위)에 대해서만 허용됩니다. - 공유 열은 한 번만, 뷰에서 분류한다.
x_namespace는 Kubernetes 및 SaaS 워크스페이스를 모두 보유하며,split_by가 이를 구분합니다. 값의 형태로 유추하지 마세요.
사용자 저장소에 유지할 파일들
0단계에서 finops/ 디렉토리를 생성하거나 사용자가 원하는 위치에 생성하세요. 경로를 첫 번째 D-001로 기록하고, 아래에서는 finops/가 해당 경로를 의미합니다.
-
finops/profile.yaml— 조직의 형태(shape)입니다.assets/profile.template.yaml에서 시작하세요. 모든 단계가 이 파일을 읽으며, 다른 곳에는 조직별 결정 사항이 저장되지 않습니다. -
finops/DECISIONS.md— 추가 전용 로그입니다. 항목을 절대 편집하지 말고, 대체(supersede)하세요.## D-007 Enterprise Support는 공유 비용입니다 (2026-10-17) 출처: 2026-09의 D5; Q-S1답변자 <user> 결정: 동월 자사(first-party) 지출 기준으로 payer별 분할 대체: —발견 결과, 답변, 가정, 승인 및 부정적 결정("일별 가중치 테이블을 시도했으나 한 달 내 혼합 비율이 변하지 않아 롤백함; 다시 제안하지 마세요")이 모두 여기에 기록됩니다. 프로필
evidence필드는 이러한 ID를 가리킵니다. -
finops/STATUS.md— 단계별 한 줄: 상태, 숫자가 포함된 게이트 결과, 게이트가 통과된 커밋 SHA, 단계가 의존하는 프로필 필드.| 단계 | 상태 | 게이트 | 시점 | 의존 필드 | | 3 기본 테이블 | 통과 | 2026-09 청구서 120,000.00 = 월별 120,000.00 | a1b2c3d | providers, dimensions, routing.key, finance, period_charges, shared_costs |
실행 루프
진입
finops/STATUS.md가 존재함 → 재개(resume): STATUS, DECISIONS 및 프로필을 읽습니다. 통과된 각 단계에 대해git diff <해당 SHA> -- finops/profile.yaml을 실행합니다. 의존하는 필드가 변경된 경우, 해당 단계 및 그 출력을 읽는 이후 모든 단계(1 → 2 → 3; 3 → 4, A, B, 5; 4 → 5)를 다시 엽니다. 마지막으로 통과한 게이트를 현재 데이터로 다시 실행합니다(데이터도 변경됨). 통과되지 않은 첫 번째 단계부터 계속합니다.- 사용자가 변경 사항(재조직, 새 계정 제품군, 새 공급업체, 새 클러스터)을 보고함 → 갱신(refresh): 일치하는 발견 쿼리를 다시 실행하고, 프로필과 비교(diff)하고, 변경된 내용에 대해서만 다시 질문하고, 재확인한 후 의존 단계를 다시 엽니다.
- 사용자가 먼저 작동하는 것을 보거나 아직 청구 액세스 권한을 부여하지 않으려는 경우 → 시험(trial) (
references/trial.md): DuckDB에서 합성 월 데이터를 사용하며, 자격 증명이 필요 없고, 정답 키와 대조 확인됩니다. 시험의 어떤 것도 실제 구축에 영향을 미치지 않습니다. - 그 외 → 아래 시작(start).
1. 묻기 전에 발견(Discover)하세요
references/discovery.md를 읽고, 마지막으로 마감된 월에 대해, 해당 내보내기(export)가 이미 있는 곳(파일 위의 테이블, 또는 엔진이 없을 때는 다운로드한 배달본 하나에 대한 DuckDB)에서 쿼리를 실행하세요. 구축이 존재하기 전에 수행합니다. 모든 결과를 보여주세요. 각 결과를 D-NNN으로 기록하세요. 발견은 읽기만 합니다; 계정에서 아무것도 변경하지 않으며, 태그 활성화조차 하지 않습니다.
아직 내보내기가 없는 경우 → 부트스트랩 경로: 내보내기를 켜고(references/focus-export.md), 비데이터 수집 라운드(조직, 런타임, 범주, 재무)를 실행하고, 예상 레이아웃에 맞춰 스택을 구축한 후, 첫 번째 배달이 도착하면 다음 세션에서 발견을 실행합니다. 기다리는 동안 시험(references/trial.md)을 제공하세요.
2. 간극에 대해 인터뷰하세요
references/intake.md를 읽으세요. 중단 또는 가정 규칙(Stop-or-assume rule):
- 답변이 돈이나 소유권에 영향을 미침 →
null로 남겨둡니다; 구성 요소는 BLOCKED 상태로 유지됩니다. - 이름, 위치 또는 도구에 관한 것 → 기본값을 사용하고,
assumptions:항목과D-NNN을 추가한 후 계속 진행합니다.
그럴듯한 추측, 픽스처 또는 예제의 값으로 사실을 채우지 마세요.
3. 계획을 확인하고 동의를 구하세요
references/plan.md에 설명된 검사기(checker)를 finops/에 작성하고(규칙당 하나의 테스트 포함), 프로필에 대해 실행합니다. 구성 요소별로 하나의 판정(verdict)과 무엇이 구축 준비되었는지(의존성 전체가 READY) 출력합니다. 이를 보여주세요.
각 BLOCKED 항목에 대해, 답변할 수 있는 사람과 정확한 탐침(probe)을 지정하세요. 각 NOT_A_FIT에 대해, 더 간단한 대안을 명확히 말하세요 — 조직 규칙이 그렇게 하라고 하면 "이것을 구축하지 마세요"를 포함합니다. 계획에 대한 명시적 승인을 받고, 이를 D-NNN으로 기록하세요.
4. 단계별로 구축하세요
아래 맵의 각 단계에 대해 순서대로(4단계 이후 선택적 트랙 A와 B는 READY인 경우에만):
- 진입 확인.
--entering <phase>플래그와 함께 검사기가 0으로 종료되고, 의존하는 모든 단계가 STATUS.md에서 통과 상태여야 합니다. - 사양 읽기 (
references/<file>, 단계 맵의 Spec 열): 계약, 불변 규칙, 즉석에서 구현할 때 문제가 되는 경계 사례. - 스택에 맞춰 작성. 사용자의 엔진 및 언어(Athena, BigQuery, Snowflake, Databricks, dbt, ...)와 저장소 스타일로, 프로필에 따라 구성 요소를 구축합니다:
providers.md의 공급자별 어댑터, 프로필이 필요로 하는 분할만, 라우팅하는 열만 포함합니다. 사양은 무엇이 유지되어야 하는지와 케이스에서 무엇이 나와야 하는지를 말합니다; 코드는 여러분이 작성합니다. 오류가 조용히 발생하는 부분(가중치, 일별 분포, 합계 검사, 통화 보호)에 가장 주의하세요. - 황금 케이스 포팅 (
cases/*.yaml; 방법은references/testing.md에 있음) 이 단계가 구축한 것에 적용되는 것을 포팅하고, 각 조직별 적응에 대한 케이스를 추가합니다. 케이스가 통과될 때까지 단계가 구축된 것으로 간주되지 않습니다. - 실제 데이터에 대해 게이트 배포 및 실행. 게이트는 건강할 때 0개의 행을 반환하는 SQL입니다. 숫자를 보여주세요.
- 결과, SHA 및 의존성을 STATUS.md에 기록.
게이트 보고서는 매번 다음과 같아야 합니다:
Gate 3 (base tables), 2026-09, aws: PASSED
bill 120,000.00 · monthly 120,000.00 · daily 120,000.00 · drift 0.00
support: 1 row → 12 accounts, weights sum 1.000000
게이트 실패는 구축을 중단합니다. 사양의 실패 테이블(모든 검사의 문제 이름은 reconciliation-ops.md, "실패 모드"에 있음)로 디버그하고, 수정한 후 다시 실행하세요.
단계 맵
| 단계 | 구축 내용 | 프로필 필드 | 사양 | 케이스 | 게이트 |
|---|---|---|---|---|---|
| 0 | 발견, 인터뷰, 검사기, 계획 | 전체 | discovery.md, intake.md, plan.md | — | 계획 승인됨 (D-NNN) |
| 1 | FOCUS 내보내기 | providers | focus-export.md | — | 전체 월 쿼리 가능 (D0) |
| 2 | 스토리지 레이아웃, 엔진, 테이블 및 raw를 채우는 전달(복사 또는 폴러, 스테이징, ETL의 게시 단계) | runtime, providers | stack.md, data-model.md, pipeline.md ("Delivery") | — | 게시된 월이 매니페스트와 일치; raw 월 쿼리가 해당 월 파일의 일부만 스캔 |
| 3 | 어댑터, 비례 배분 및 공유 비용 분할이 포함된 기본 테이블, ETL의 재구축 | providers, dimensions, routing.key, finance, period_charges, shared_costs | providers.md, data-model.md, splitters.md, pipeline.md | adapters, load, share, checks | 마감된 월에 대해 payer별 bill == monthly == daily |
| 4 | 규칙, 조회(lookup), 뷰 | categories, routing | rules-engine.md | rules, views | 범주가 나타남; Unallocated 보고됨 |
| A | SaaS 분할 | saas.vendors | saas.md | vendor | 공급업체가 split 표시; 합계 유지 |
| B | Kubernetes 네임스페이스 | kubernetes | kubernetes.md | k8s | 가중치 정상; 적용 범위 ≥ 목표 |
| 5 | 조정, 운영, 마감 | finance, org.finance_partner | reconciliation-ops.md, testing.md | checks | 조직이 청구서 합계를 입력하는 경우(Q-F5), 마감된 월이 모든 공급자에 대해 청구서 검사 통과, 결과가 해당 월과 함께 저장됨; 의도적으로 깨뜨린 분할이 개발 복사본에서 실행 실패; 재무 파트너(또는 담당자)와의 숫자 검토 |
references/pipeline.md에는 이러한 요소들이 런타임에 실행되는 처리 순서가 있습니다.
자유도
- 고정(엔진에 관계없음): 불변 규칙, 케이스가 출력해야 하는 것(
adapters.yaml제외: 이는 하나의 프로필이 내보내기의 원시 형태에 대해 내린 선택을 보여줌), 비례 배분 규칙, 합계가 1.0이 되는 가중치를 사용한 팬아웃, 생산자 게이트(payer별, 양수 지출, 전체 월 공급업체 데이터), 검사 및 그 중단 조건, 실행 순서 및 실패 시 지우기, 채워지지 않은 쿼리 매개변수 거부. - 사용자 측(코드베이스에 맞게 자유롭게 작성): 엔진 및 언어, 파일 레이아웃, Infrastructure as Code, 패키징, 스케줄링, 대시보드, CI, 파일/함수/인프라 이름(단, 케이스가 사용하는 테이블, 열, 문제 이름은 고정).
변경 사항이 어느 쪽에 속하는지 확실하지 않으면 고정된 것으로 간주하세요.
사용자가 반발할 때
references/pushback.md에는 일반적인 반발("ETL에서 할당", "100% 매핑", "모든 공급업체에 하나의 프레임워크", "Pod 단위 정밀도")에 대한 답변과 해당 글이 주장하는 부분이 나와 있습니다.
완료 조건
- 모든 마감된 월이 모든 공급자, 모든 분할에 대해 합계 검사를 통과하고, 조직이 청구서 합계를 입력하는 경우 청구서 검사도 통과합니다.
- 비용을 보고하는 사람(재무 파트너 또는 담당자)이 새 SQL 없이 뷰에서 "지난달 X팀의 비용은 얼마였습니까?"라는 질문에 답할 수 있습니다.
- 규칙 변경은 풀 리퀘스트입니다: CI가 확장되고, 리뷰어는 무엇이 이동하는지 확인하며, 병합 시 재구축 없이 모든 기록이 재분류됩니다.
- Unallocated는 금액, 통화 단위로 알림이 발생하고, 감소 추세여야 합니다.
- 실행 비용은 작고 측정 가능해야 합니다(기준 스택에서 S3, Athena, Lambda 합계 월 약 $10, 월 1,500만 청구 라인 기준; Athena의 비용은 워크그룹 스캔 메트릭에 포함됨).
- STATUS.md는 계획이 구축하는 모든 단계가 SHA와 함께 통과되었음을 보여줍니다. DECISIONS.md는 새로 참여한 사람이 의문을 가질 만한 모든 선택을 설명합니다.